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.
npx skills add bendaamerahmed/backstage-idp-plugin --skill backstage-permissionsgit clone --depth 1 https://github.com/bendaamerahmed/backstage-idp-pluginWrote 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.
[](https://agentmods.dev/skills/bendaamerahmed/backstage-idp-plugin/backstage-permissions)<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.
<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>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.
| Model | Per session | Once 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 |
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>"`), 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:
createAppfrom@backstage/frontend-defaults,createFrontendPlugin, blueprints,/alphaexports — vs legacycreatePlugin,<FlatRoutes>). It changes where UI checks are placed, not whether they matter. backstage.jsonrelease 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
yarnfrom the repo root unless the repo says otherwise.
Procedure
- Survey what exists. Grep for
permission:inapp-config*.yaml,@backstage/plugin-permission-backendand@backstage/plugin-permission-backend-module-allow-all-policyinpackages/backend/src/index.ts, and existingPermissionPolicyimplementations underpackages/backend/src/extensions/. Seebackstage-repo-discovery. - Enable the framework. Set
permission.enabled: trueinapp-config.yamland registerbackend.add(import('@backstage/plugin-permission-backend')). Withenabled: falsethe framework short-circuits to ALLOW and your policy is never called — every check you write is dead code until this flag is on. - Define permissions in the plugin's
-commonpackage, never the backend package — the frontend imports them too. UsecreatePermissionfrom@backstage/plugin-permission-common: a unique dottedname(<plugin>.<resource>.<action>),attributes: { action: 'create' | 'read' | 'update' | 'delete' }, and export a<plugin>Permissionsarray so integrators can enumerate them. - 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
resourceTypeand can be answered per-object. Resource type strings are global across the instance — namespace them. - Register with the permissions registry. In the plugin's
register/init, takecoreServices.permissionsRegistryand calladdPermissions([...]). For resource permissions calladdResourceType({ resourceRef, permissions, rules, getResources }), whereresourceRefcomes fromcreatePermissionResourceRefandgetResourcesmaps refs to objects (returningundefinedfor missing ones). This service replacedcreatePermissionIntegrationRouter; if the repo still uses that function, migrate it. Read the installed@backstage/backend-plugin-apitypes for the exact shapes before writing. - Enforce in every backend route that mutates or exposes protected data.
Get credentials with
httpAuth.credentials(req, { allow: ['user'] }), thenpermissions.authorize([{ permission, resourceRef }], { credentials }), and throwNotAllowedErrorfrom@backstage/errorsonAuthorizeResult.DENY. One check per route, at the top, before any read or write. - For lists and paginated reads, use
authorizeConditional. OnAuthorizeResult.CONDITIONAL, convert the returned conditions into your storage filter withcreateConditionTransformer(permissionsRegistry.getPermissionRuleset(resourceRef))and push the filter into the query. Never fetch everything and filter in memory. - Write the policy as a backend module.
createBackendModule({ pluginId: 'permission', moduleId: 'permission-policy' }), depend onpolicyExtensionPointfrom@backstage/plugin-permission-node/alpha, and callpolicy.setPolicy(new YourPolicy())ininit. ImplementPermissionPolicy.handle(request, user): narrow withisPermission(request.permission, somePermission)for one permission, orisResourcePermission(request.permission, 'catalog-entity')to cover a whole family. Make the catch-allreturnexplicit and deliberate — that single line is your instance's default posture. - 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. - Add custom rules only when no existing rule fits.
createPermissionRulefrom@backstage/plugin-permission-nodewithname,description,resourceRef, a zodparamsSchema,apply(in-memory predicate) andtoQuery(storage filter).applyandtoQuerymust express the same predicate or conditional reads and single-resource checks will disagree. Wrap withcreateConditionFactoryfor use in policies and register viapermissionsRegistry.addPermissionRulesin a backend module. - Reflect, do not enforce, in the UI.
usePermission({ permission, resourceRef })from@backstage/plugin-permission-reactreturns{ loading, allowed }; use it to disable or hide controls.RequirePermissionwraps a route element. On NFS, wrap inside the component supplied to the page extension rather than editingApp.tsxroutes. OmittingresourceReffor a resource permission yieldsallowed: false, and results are stale-while-revalidate — never branch on either for security. - Remove the allow-all module (
@backstage/plugin-permission-backend-module-allow-all-policy) frompackages/backend/src/index.tsonce a real policy is registered. Two policy providers is a startup failure, and leaving allow-all wins silently in some orders. - 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()) withauthorizeresolved to DENY and assert HTTP 403 — an allow-only test suite proves nothing.
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.
- 11d ago First seen · 155 lines · 36 tokens per session scan A fa4110269d17
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.
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.
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.
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.
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.
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.
perf
Analyze Elixir/Phoenix performance — N+1 queries, assign bloat, ecto optimization, genserver bottlenecks. Use when slowness, timeouts, or high memory reported.