Skip to main content
← Back to list
01Issue
FeatureShippedSwamp Club
Assigneesstack72

Relationships

#1261 Scope enforcement and whoami introspection for collective tokens (phase 2 of #960)

Opened by stack72 · 7/18/2026· Shipped 7/18/2026

Context

Phase 2 of #960. Phase 1 (#1228) shipped the domain model, persistence, and auth threading. Phase 1 addendum (#1232) expanded the scope registry to the full taxonomy. This phase adds enforcement and whoami introspection.

No breaking changes. Every existing token has extensions:* which passes all scope checks. No changes to the token creation API or UI. No changes to personal API keys.

What this covers

1. requireScope() guard (lib/app/collective-api-token-commands.ts)

A function that takes a CollectiveTokenContext and a required scope string. If the token lacks the scope, returns a 403 Response with a message naming the missing scope (e.g. "This token requires the extensions:push scope"). If the token has the scope (directly or via wildcard), returns null (pass).

2. Enforcement at routes

Apply requireScope() to the 4 routes that currently accept collective tokens:

  • routes/api/v1/extensions/push.ts — require extensions:push
  • routes/api/v1/extensions/confirm.ts — require extensions:push
  • routes/api/v1/extensions/[...path].ts handlePostPromote — require extensions:promote
  • routes/api/v1/extensions/search.ts — require extensions:read

Do NOT add enforcement to yank/unyank/deprecate/undeprecate/visibility — those operations don't accept collective tokens today.

3. Whoami introspection (routes/api/whoami.ts)

Add scopes to the collective token branch of the whoami response. This surfaces the token's granted scopes so CLI users and scripts can inspect what their token can do.

  • For existing tokens: returns scopes: ["extensions:*"]
  • For future scoped tokens: returns the actual scopes array (e.g. ["extensions:push", "extensions:read"])

Do NOT expose legacyToken in the response — that is an internal implementation detail.

4. Tests

  • App tests: requireScope() passes with matching scope, rejects with missing scope, passes with wildcard extensions:*
  • Route tests: scoped token gets 200 on permitted operation and 403 on unpermitted; extensions:* passes all checks
  • Whoami test: collective token response includes scopes array

What this does NOT change

  • Token creation API — still takes name and expiresAt only, no scopes parameter (phase 3)
  • UI — no changes to CollectiveApiKeyManager (phase 3)
  • Personal API keys — completely untouched
  • Login — untouched

User impact

Effectively none. The only observable change is two new fields in the whoami response when using a collective token. The scope enforcement is live but invisible — every existing token has extensions:* which passes all checks. There is no way for a user to hit a 403 from scope enforcement until phase 3 ships (when the creation API accepts scopes).

Verification

  • deno task check passes
  • deno task test passes (excluding pre-existing extension_tarball_analyzer failures)
  • Existing tokens continue to work on all operations
  • No breaking changes to any API response shapes (additive fields only on whoami)
02Bog Flow
OPENTRIAGEDIN PROGRESSSHIPPED+ 1 MOREASSIGNED+ 5 MOREREVIEW+ 3 MOREPR_MERGED+ 1 MORENOTIFICATION_SKIPPED

Shipped

7/18/2026, 9:55:37 PM

Click a lifecycle step above to view its details.

03Sludge Pulse
stack72 assigned stack727/18/2026, 9:01:20 PM

Sign in to post a ripple.