backstage-permissions

backstage-permissions is a skill for Claude Code from bendaamerahmed/backstage-idp-plugin. It costs 36 tokens per session (2,521 once invoked), scanned A, original, MIT.

A guide for adding permission checks to Backstage, an open-source developer portal. It covers deciding who may use backend actions and making the server enforce those decisions.

In plain words
What is it for?
Use it to define permissions, create a policy for decisions, enforce access in backend routes, and show permission results in the interface.
Why use it?
It helps prevent relying on the user interface alone for security, since interface checks can be bypassed.

Skill for Claude Code

Written for Claude Code: when-to-use in frontmatter.

Part of the backstage-idp plugin — 15 skills, 1 agent shipped together

Good fit Use it to define permissions, create a policy for decisions, enforce access in backend routes, and show permission results in the interface.

Compare 6 skills from other repositories ↓
Install with agentmods
npx agentmods add skills/bendaamerahmed/backstage-idp-plugin/backstage-permissions
Install

Getting it into your agent

One page per mod, every tool's command on it. A separate URL per tool would split the same page into five that compete with each other.

Any agent
npx skills add bendaamerahmed/backstage-idp-plugin --skill backstage-permissions
Clone the repo
git clone --depth 1 https://github.com/bendaamerahmed/backstage-idp-plugin

Made for: Claude Code.

Or install backstage-idp, the plugin that ships this one along with the rest of its 15 skills, 1 agent.

Wrote this? Show the measurements

A badge with what this costs and how it scanned, read live from this page, so it follows the numbers instead of freezing them. Markdown for a README, HTML for a documentation site or a project page.

agentmods badge for backstage-permissions

README.md
[![agentmods](https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-permissions/github.svg)](https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-permissions)
Your own site
<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-permissions"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-permissions/github.svg" alt="Measured on agentmods" height="20"></a>

Or the 80×15 button, for a site that already has a row of RSS and ATOM ones. Only the verdict fits; the numbers stay here.

agentmods 80×15 button for backstage-permissions

Your own site · 80×15
<a href="https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-permissions"><img src="https://agentmods.dev/badge/skills/bendaamerahmed/backstage-idp-plugin/backstage-permissions.svg" alt="Reviewed on agentmods" width="80" height="20"></a>
Per session 36 Skills are progressive disclosure: only the name and description are preloaded; the body loads when the skill is used.
When invoked 2,521 The whole file, excluding the scripts and references it only reads on demand.
Security scan A 1 finding. A grade says what 26 rules found in the file — not that it is safe.
Origin original No closer match found in the catalogue.
Token cost

What it costs to keep this loaded

Counted locally with the o200k_base tokenizer, which is exact for GPT models; Claude uses its own tokenizer and its counts differ. Treat this as one consistent yardstick across the catalogue rather than a bill. Prices are per million input tokens.

ModelPer sessionOnce invoked
Fable 5.1 $0.00036 $0.02521
Opus 5 $0.00018 $0.01260
Sonnet 5 $0.00007 $0.00504
Haiku 4.5 $0.00004 $0.00252

Measured 11d ago against content hash fa4110269d17, method: parsed. Prices are Anthropic first-party input rates as of 2026-09-11, from the pricing page.

Security

Grade A, and why

backstage-permissions scanned grade A with 1 finding against 26 rules in 11 categories — prompt injection, anti-refusal, data exfiltration, privilege escalation, supply chain, agent snooping, system-prompt leakage, SSRF and excessive agency — measured 11d ago.

A static scan of the body, not an audit. Every finding is printed with the line that produced it so you can judge whether it matters here. A mod is markdown that instructs an agent; that is exactly why what it instructs is worth reading.

Makes network callslowCapability

Not a fault in itself. Listed so you know the mod talks to something, and to what.

- Hit the protected route directly with a real user token (`curl -H "Authorization: Bearer <token>"`),
plugins/backstage-idp/skills/backstage-permissions/SKILL.md · 155 lines

How it starts

The opening of the file, as written. The whole thing — 155 lines — stays where its author put it; the contents beside it link to each section on GitHub.

Backstage permissions

Add real authorization to a Backstage instance: permission definitions, a policy that decides, and backend enforcement points that obey. The UI never enforces anything.

Preconditions

  • Repo uses the new backend system (createBackend, backend.add()). Detect first; the legacy backend wiring for permissions is different and unsupported on current lines.
  • Frontend generation known (NFS: createApp from @backstage/frontend-defaults, createFrontendPlugin, blueprints, /alpha exports — vs legacy createPlugin, <FlatRoutes>). It changes where UI checks are placed, not whether they matter.
  • backstage.json release line known; permission APIs moved (see step 5) and the installed packages are the source of truth, not memory.
  • You know which principal the endpoint serves: end users, service-to-service, or both.
  • Assume yarn from the repo root unless the repo says otherwise.

Procedure

  1. Survey what exists. Grep for permission: in app-config*.yaml, @backstage/plugin-permission-backend and @backstage/plugin-permission-backend-module-allow-all-policy in packages/backend/src/index.ts, and existing PermissionPolicy implementations under packages/backend/src/extensions/. See backstage-repo-discovery.
  2. Enable the framework. Set permission.enabled: true in app-config.yaml and register backend.add(import('@backstage/plugin-permission-backend')). With enabled: false the framework short-circuits to ALLOW and your policy is never called — every check you write is dead code until this flag is on.
  3. Define permissions in the plugin's -common package, never the backend package — the frontend imports them too. Use createPermission from @backstage/plugin-permission-common: a unique dotted name (<plugin>.<resource>.<action>), attributes: { action: 'create' | 'read' | 'update' | 'delete' }, and export a <plugin>Permissions array so integrators can enumerate them.
  4. Choose basic vs resource. A basic permission asks "may this user do X at all" (creation, where no resource exists yet). A resource permission adds resourceType and can be answered per-object. Resource type strings are global across the instance — namespace them.
  5. Register with the permissions registry. In the plugin's register/init, take coreServices.permissionsRegistry and call addPermissions([...]). For resource permissions call addResourceType({ resourceRef, permissions, rules, getResources }), where resourceRef comes from createPermissionResourceRef and getResources maps refs to objects (returning undefined for missing ones). This service replaced createPermissionIntegrationRouter; if the repo still uses that function, migrate it. Read the installed @backstage/backend-plugin-api types for the exact shapes before writing.
  6. Enforce in every backend route that mutates or exposes protected data. Get credentials with httpAuth.credentials(req, { allow: ['user'] }), then permissions.authorize([{ permission, resourceRef }], { credentials }), and throw NotAllowedError from @backstage/errors on AuthorizeResult.DENY. One check per route, at the top, before any read or write.
  7. For lists and paginated reads, use authorizeConditional. On AuthorizeResult.CONDITIONAL, convert the returned conditions into your storage filter with createConditionTransformer(permissionsRegistry.getPermissionRuleset(resourceRef)) and push the filter into the query. Never fetch everything and filter in memory.
  8. Write the policy as a backend module. createBackendModule({ pluginId: 'permission', moduleId: 'permission-policy' }), depend on policyExtensionPoint from @backstage/plugin-permission-node/alpha, and call policy.setPolicy(new YourPolicy()) in init. Implement PermissionPolicy.handle(request, user): narrow with isPermission(request.permission, somePermission) for one permission, or isResourcePermission(request.permission, 'catalog-entity') to cover a whole family. Make the catch-all return explicit and deliberate — that single line is your instance's default posture.
  9. Return conditional decisions for ownership. For the catalog, use createCatalogConditionalDecision(request.permission, catalogConditions.isEntityOwner({ claims: user?.info.ownershipEntityRefs ?? [] })) — conditions from @backstage/plugin-catalog-backend/alpha, permissions from @backstage/plugin-catalog-common/alpha. Conditional decisions are only valid for resource permissions; returning one for a create-style permission is an error.
  10. Add custom rules only when no existing rule fits. createPermissionRule from @backstage/plugin-permission-node with name, description, resourceRef, a zod paramsSchema, apply (in-memory predicate) and toQuery (storage filter). apply and toQuery must express the same predicate or conditional reads and single-resource checks will disagree. Wrap with createConditionFactory for use in policies and register via permissionsRegistry.addPermissionRules in a backend module.
  11. Reflect, do not enforce, in the UI. usePermission({ permission, resourceRef }) from @backstage/plugin-permission-react returns { loading, allowed }; use it to disable or hide controls. RequirePermission wraps a route element. On NFS, wrap inside the component supplied to the page extension rather than editing App.tsx routes. Omitting resourceRef for a resource permission yields allowed: false, and results are stale-while-revalidate — never branch on either for security.
  12. Remove the allow-all module (@backstage/plugin-permission-backend-module-allow-all-policy) from packages/backend/src/index.ts once a real policy is registered. Two policy providers is a startup failure, and leaving allow-all wins silently in some orders.
  13. Test the denied path first. Unit-test the policy by calling handle() directly with a constructed permission and user, asserting DENY and asserting the exact conditions object for conditional cases. Route-test with the backend test utils' permissions mock (mockServices.permissions.mock()) with authorize resolved to DENY and assert HTTP 403 — an allow-only test suite proves nothing.

Read the full file on GitHub · 155 lines

Changes

What this file has done since we first saw it

Hashed on every crawl. A supply-chain change to an agent config is a question of when, not whether, so the history is kept rather than the latest state alone.

  1. 11d ago First seen · 155 lines · 36 tokens per session scan A fa4110269d17

Subscribe to this mod's changes

backstage-permissions is a skill published in the GitHub repository bendaamerahmed/backstage-idp-plugin (1 stars, last pushed 1mo ago), licensed MIT. It adds 36 tokens to every session and 2,521 once invoked, about $0.0002 per session on Opus 5. A static security scan graded it A with 1 finding (makes network calls). No closer match exists in the catalogue, so it is treated as the original; first seen 2026-08-31.

Related

Other skills, from other repositories

ash-framework

Ash Framework — resources, actions, policies, aggregates, calculations, AshPhoenix.Form, LiveView, migrations. Use when generating resources via mix ash.codegen, editing changes, checks, types, validations, or domain code interfaces.

oliver-kriska/claude-elixir-phoenix · 48 tokens

deploy

Elixir/Phoenix deployment patterns — Dockerfile, fly.toml, runtime.exs, mix release, rel/ overlays. Use when configuring Fly.io, Docker, CI/CD, health checks, or production migrations.

oliver-kriska/claude-elixir-phoenix · 46 tokens

liveview-patterns

Build LiveView: async data (assignasync), PubSub (check connected?), phx-change events, form components/modals/uploads, streams for lists, livepatch. Use when handling interactions, debugging events, or tracking Presence.

oliver-kriska/claude-elixir-phoenix · 51 tokens

tidewave-integration

Tidewave MCP runtime tools — debugging, smoke testing, live state inspection, SQL queries, hex docs. Use when evaluating code in a running Phoenix app.

oliver-kriska/claude-elixir-phoenix · 38 tokens

oban

Oban job processing — workers, perform/1 (OSS) and process/1 (Pro), queues, cron, retries, unique jobs, idempotency, Oban Pro (Workflow, Batch, Chunk, Smart Engine), Testing. Use when writing Oban workers, queue config, or debugging jobs.

oliver-kriska/claude-elixir-phoenix · 64 tokens

perf

Analyze Elixir/Phoenix performance — N+1 queries, assign bloat, ecto optimization, genserver bottlenecks. Use when slowness, timeouts, or high memory reported.

oliver-kriska/claude-elixir-phoenix · 43 tokens