Permissions
Understand how PackagePass decides who can publish, install, and manage packages.
PackagePass authorization is built from subjects, resources, actions, grants, and token capabilities. The same model protects the control API and the npm-compatible registry, but each surface accepts different token kinds.
The mental model
A request is allowed when the authenticated principal has a matching token capability or grant for the requested action and resource.
Principal
Subject
Resource
Action
package:install, package:publish, or grant:manage.Where permissions are enforced
| Surface | Accepted token kinds | Permission check |
|---|---|---|
| Control API | api | API routes require a valid API-class token. CI and publish tokens are also accepted by the control plane. |
| npm registry | install | Registry package and tarball requests authenticate the token, then require package:install on the package or package version. |
| Bootstrap admin | bootstrap admin token | Bypasses normal grants and capabilities. Use it only for initial tenant setup and emergency administration. |
| Denied registry request | any token | Unauthorized packument and tarball requests return 404 instead of 403 so package names are not leaked. |
Decision flow
How an authorization decision is made
The policy engine evaluates cheap, explicit checks first, then falls back to grants for the subject and resource.
- 1
Authenticate the bearer token
The request becomes either a bootstrap admin principal, a token principal, or anonymous. Revoked and expired tokens authenticate as anonymous. - 2
Reject cross-tenant use
If the token belongs to one tenant and the resource belongs to another, authorization stops with a tenant mismatch. - 3
Check token capabilities
Token capabilities are evaluated before grants. A matching inline capability is enough to allow the action. - 4
Expand the resource
PackagePass checks the requested resource plus broader parents such as package, scope, and tenant. - 5
Find matching grants
A grant must be in the same tenant, not revoked, for the same subject, attached to one candidate resource, and include the requested action. - 6
Apply enforced conditions
The current policy engine enforces expiresAt. Other condition fields are stored with the grant but are not enforced by authorization yet.
Resource inheritance
Grants can be attached narrowly or broadly. When PackagePass checks a package version, broader package, scope, and tenant grants can satisfy the request.
| Requested resource | Checked grant resources | Meaning |
|---|---|---|
tenant | tenant | A tenant grant is tenant-wide. |
scope | scope, then tenant | A tenant grant can satisfy scope checks. |
package | package, then scope, then tenant | A scope or tenant grant can satisfy package checks. |
package_version | package_version, then package, then scope, then tenant | A version-specific grant is narrowest; broader package, scope, or tenant grants still apply. |
Actions
| Action | Allows |
|---|---|
package:read | Read package metadata through the control API. A package:install grant also satisfies package:read checks. |
package:install | Read npm packuments and download tarballs from the registry. |
package:publish | Create package metadata and publish release or preview tarballs. |
package:deprecate | Mark a version as deprecated. |
package:yank | Yank a version and remove dist-tags pointing at it. |
package:manage_dist_tags | Set and remove dist-tags. |
grant:manage | Create grants and manage package metadata such as description, readme, and archive status. |
token:mint | Create API, install, or CI tokens. |
audit:read | Read audit events. |
Subject references
| Reference | Use |
|---|---|
user:alice@example.com | A person identified by email. |
customer:globex | A customer account or tenant in your product. |
agent:code-reviewer | A non-human actor such as an AI agent, service, or CI job. |
group:platform | A team or group. |
Grant creation resolves the subject by id, email, or slug. If the user, customer, agent, or group does not exist yet, PackagePass creates it while creating the grant.
Grants and tokens
Know the difference between policy and credentials
Grants define what a subject may do. Tokens prove which subject is making the request and may also carry inline capabilities.
Grant
subject, resource, actions, and optional conditions. Grants are best for durable customer, agent, user, and group access.Token capability
API token default
Browser-approved CLI token
CLI login token. The intended default lifetime is 30 days; existing tokens keep their original expiry. Revoke it from Dashboard > Tokens when local CLI access is no longer needed.Install token default
Condition enforcement
expiresAt. Fields such as versionRange, ipAllowlist, and environment can be stored on grants, but they are not authorization controls yet.Common permission patterns
packagepass grant add @acme/payments \
--subject customer:globex \
--action install
packagepass token create install \
--package @acme/payments \
--subject customer:globex \
--ttl 30dawait packagepass.grants.create({
subject: { type: "agent", slug: "release-bot", kind: "service" },
resource: { type: "scope", name: "@acme" },
actions: ["package:publish"]
});await packagepass.grants.create({
subject: { type: "agent", slug: "code-reviewer" },
resource: {
type: "package_version",
name: "@acme/payments",
version: "4.8.0"
},
actions: ["package:install"],
conditions: {
expiresAt: "2026-05-26T00:00:00.000Z"
}
});await packagepass.tokens.createInstallToken({
name: "code reviewer payments install",
subject: { type: "agent", slug: "code-reviewer" },
resources: [{
type: "package",
name: "@acme/payments",
actions: ["package:install"]
}],
expiresAt: new Date(Date.now() + 7 * 864e5).toISOString()
});Debugging
Explain a decision before guessing
Use policy explain when a subject cannot install, publish, or manage a package. It returns allow, matchedGrants, and reason.
packagepass policy explain @acme/payments \
--subject customer:globex \
--action installconst decision = await packagepass.policy.explain({
subject: { type: "customer", slug: "globex" },
action: "package:install",
resource: { type: "package", name: "@acme/payments" }
});
console.log(decision.allow);
console.log(decision.reason);
console.log(decision.matchedGrants);