SPB Git

Repository Public MIT

PERMISSIONS.md 9.9 KB 142 lines markdown

1# Permissions23Source of truth: `packages/registry-core/src/constants.ts` (scopes), `apps/web/src/server/api.ts` (route table), `apps/web/src/server/auth.ts` (rules). If this file and the code disagree, the code is right and this file has a bug.45## Principals67| Principal | How it authenticates | What it carries |8|---|---|---|9| Anonymous | nothing | read access to public content, abuse reports |10| Session | `skills_session` cookie after email + password login | every non-elevated right of the user; elevated rights if the user is owner or admin; token creation and rotation need a password entry within 15 minutes |11| Token | `Authorization: Bearer skr_…` | exactly its scopes, within exactly its namespaces |12| Local operator | shell on the server, `pnpm admin …` | everything; never reachable over the network |1314## Scopes1516| Scope | Allows | In the default publisher token |17|---|---|---|18| `artifacts:read` | Read artifacts in allowed namespaces, including non-public ones | yes |19| `artifacts:create` | Create artifacts in allowed namespaces | yes |20| `artifacts:update` | Update mutable metadata: summary, description, links, keywords, visibility | yes |21| `artifacts:deprecate` | Deprecate, undeprecate or archive artifacts | no |22| `uploads:create` | Begin release uploads and send archive bytes | yes |23| `releases:publish` | Finalize an upload into an immutable release | yes |24| `releases:update` | Edit release notes only | yes |25| `releases:revoke` | Revoke a compromised release | no |26| `tokens:read` | List the owner's tokens (metadata only) | no |27| `tokens:write` | Create or revoke tokens, never broader than the calling token | no |28| `audit:read` | Read the owner's audit trail | no |29| `admin:moderate` | ELEVATED. Reports, quarantine review, takedown, platform audit trail | no |30| `admin:settings` | ELEVATED. Collection curation | no |3132Rules that apply on top of scopes:3334- Every write to an artifact or release also requires that the user owns the namespace (or holds `org_admin` or `publisher` in the owning organization) **and**, for a token, that the namespace is in the token's list.35- Elevated scopes are only honoured for users whose platform role is `owner` or `admin`. Elevated tokens expire within 24 hours and cannot be issued or rotated by another token.36- A token can always revoke or rotate itself without any token scope.37- Public visibility, deprecation, archival, revocation and takedown require an explicit `confirm` / `confirmPublic` flag in the request; without it the API answers 428.3839## Route matrix4041All paths are under `/api/v1`. "Bucket" is the rate-limit bucket; the key is the token id, else the user id, else the client IP.4243| Method | Path | Auth | Scope | Bucket | Idempotency-Key |44|---|---|---|---|---|---|45| GET | `/health/live` | public | — | — | — |46| GET | `/health/ready` | public | — | — | — |47| GET | `/openapi.json` | public | — | — | — |48| GET | `/artifacts` | public | — | anon-read | — |49| GET | `/search` | public | — | search | — |50| GET | `/categories` | public | — | anon-read | — |51| GET | `/collections` | public | — | anon-read | — |52| GET | `/collections/{slug}` | public | — | anon-read | — |53| GET | `/artifacts/{id}` | public | — | anon-read | — |54| GET | `/artifacts/{namespace}/{slug}` | public | — | anon-read | — |55| GET | `/artifacts/{namespace}/{slug}/versions` | public | — | anon-read | — |56| GET | `/artifacts/{namespace}/{slug}/history` | public | — | anon-read | — |57| GET | `/artifacts/{namespace}/{slug}/versions/{version}` | public | — | anon-read | — |58| GET | `/artifacts/{namespace}/{slug}/versions/{version}/download` | public | — | download | — |59| GET | `/artifacts/{namespace}/{slug}/versions/{version}/checksum` | public | — | anon-read | — |60| GET | `/artifacts/{namespace}/{slug}/versions/{version}/files?path=` | public | — | anon-read | — |61| GET | `/artifacts/{namespace}/{slug}/versions/{version}/install?client=` | public | — | anon-read | — |62| POST | `/artifacts` | required | `artifacts:create` | token-write | yes |63| PATCH | `/artifacts/{id}` | required | `artifacts:update` | token-write | — |64| POST | `/artifacts/{id}/releases` | required | `uploads:create` | upload | yes |65| GET | `/uploads/{id}` | required | `uploads:create` | token-write | — |66| PUT | `/uploads/{id}` | required | `uploads:create` | upload | — |67| POST | `/uploads/{id}/finalize` | required | `releases:publish` | publish | yes |68| POST | `/artifacts/{id}/deprecate` | required | `artifacts:deprecate` | token-write | — |69| POST | `/artifacts/{id}/undeprecate` | required | `artifacts:deprecate` | token-write | — |70| POST | `/artifacts/{id}/archive` | required | `artifacts:deprecate` | token-write | — |71| POST | `/releases/{id}/revoke` | required | `releases:revoke` | token-write | — |72| PATCH | `/releases/{id}` | required | `releases:update` | token-write | — |73| POST | `/validate-manifest` | required | any | token-write | — |74| GET | `/me` | required | any | token-write | — |75| GET | `/me/tokens` | required | `tokens:read` | token-write | — |76| POST | `/me/tokens` | required | `tokens:write` | token-write | — |77| DELETE | `/me/tokens/{id}` | required | self: any; other tokens: `tokens:write` | token-write | — |78| POST | `/me/tokens/{id}/rotate` | required | self: any; other tokens: `tokens:write` | token-write | — |79| GET | `/me/audit` | required | `audit:read` | token-write | — |80| POST | `/reports` | public | — | report | — |81| GET | `/admin/reports` | required | `admin:moderate` | token-write | — |82| POST | `/admin/reports/{id}/resolve` | required | `admin:moderate` | token-write | — |83| GET | `/admin/quarantine` | required | `admin:moderate` | token-write | — |84| POST | `/admin/releases/{id}/review` | required | `admin:moderate` | token-write | — |85| POST | `/admin/artifacts/{id}/moderate` | required | `admin:moderate` | token-write | — |86| GET | `/admin/audit` | required | `admin:moderate` | token-write | — |8788Public read routes still honour credentials: with a session or a suitable token, the same routes also return the caller's private, draft and quarantined items.8990Not exposed over HTTP at all: owner bootstrap, user creation, namespace creation, password recovery, identity verification of a namespace. These exist only in the server console.9192## Rate-limit buckets9394| Bucket | Limit | Window |95|---|---|---|96| anon-read | 240 | 60 s |97| search | 90 | 60 s |98| download | 120 | 60 s |99| mcp | 240 | 60 s |100| token-write | 120 | 60 s |101| upload | 30 | 1 h |102| publish | 30 | 1 h |103| report | 5 | 1 h |104| login | 8 | 15 min (per IP and email) |105| auth-fail | 20 | 10 min (per IP; locks out bearer authentication) |106107Responses carry `RateLimit-Limit`, `RateLimit-Remaining` and `RateLimit-Reset`. Exceeding a limit returns 429 with `Retry-After`. Limits are held in process memory (ADR-0004).108109## Roles110111Platform roles are a column on `users`. Organization roles are rows in `memberships`.112113| Role | Status in V1 | Rights |114|---|---|---|115| Anonymous | Implemented + tested | Read public artifacts, releases, files, collections; download; file abuse reports |116| Member (platform) | Implemented + tested | Everything anonymous can do, plus publish to namespaces they own, manage their own tokens, read their own audit trail |117| Publisher (organization) | Authorization check implemented; no interface to create organizations or memberships | Publish to namespaces owned by the organization |118| Organization admin | Same as above | Same publish rights as publisher in V1; member management is Planned |119| Reviewer (platform or organization) | Schema only | Grants nothing in V1 |120| Platform admin | Implemented; role is assigned by the operator in the database | Moderation (`admin:moderate`), curation (`admin:settings`), read private artifacts through a session. No implicit right to publish in someone else's namespace |121| Platform owner | Implemented + tested | Same as platform admin. Exactly one owner can exist (database constraint). Created only by `pnpm admin:bootstrap` |122| Local operator | Implemented | Server console: bootstrap, create users and namespaces, issue or revoke any user's tokens, recover accounts, verify namespace identity, seed and curate collections, cleanup |123124Planned: organization management, member invitations, ownership transfers with two-party confirmation, a reviewer role that can approve quarantined releases without full moderation rights.125126## Troubleshooting 401 and 403127128| Status | `error.code` | Meaning | What to do |129|---|---|---|---|130| 401 | `unauthorized` | No credential sent | Add `Authorization: Bearer <token>` or run `skills login` |131| 401 | `invalid_token` | Malformed, unknown, wrong secret, IP not on the allowlist, or the user is disabled | Check the stored token and the calling IP; create a new token |132| 401 | `token_expired` | Past `expiresAt` | Create a new token (an expired token cannot be rotated) |133| 401 | `token_revoked` | Revoked or replaced by rotation | Use the replacement or create a new token |134| 403 | `insufficient_scope` | The token lacks the scope named in `details.required` | Issue a token with that scope |135| 403 | `namespace_forbidden` | The namespace is not yours or not on this token | Use a token that lists the namespace |136| 403 | `reauth_required` | Session password entry is older than 15 minutes | Re-enter the password |137| 403 | `admin_required` / `elevation_forbidden` | Elevated action without an administrator session or elevated token | Use the dashboard or an elevated console-issued token |138| 403 | `csrf` | Cookie-authenticated write without same-origin and CSRF token | Use a bearer token for scripts |139| 403 | `password_change_required` | Initial password not yet changed | Sign in on the website and change it |140| 428 | `confirmation_required` | Destructive or exposing action without confirmation | Resend with `confirm: true` / `confirmPublic: true` after a human approved it |141| 429 | `rate_limited` | Too many requests or too many failed authentications from this IP | Wait for `Retry-After` |142
Files main
Menu

Theme