Skip to main content
← Back to list
01Issue
BugClosedSwamp CLITeam
AssigneesNone

Relationships

#1389 A collective token stored in auth.json (rather than SWAMP_API_KEY) is permanently scope-less — resolution only runs for the env var, and scopes are never persisted for collective tokens

Opened by magistr · 7/24/2026

Summary

Startup scope resolution runs only when SWAMP_API_KEY is present in the environment. A collective token that lives in auth.json instead therefore never has its scopes resolved, and — because nothing ever writes scopes into auth.json for a collective token — the fallback path finds nothing either. The result is a credential that is permanently and unrecoverably scope-less: every team-feature gate throws "lacks the X scope", forever, no matter what the token actually holds.

Where (source)

  • src/cli/mod.ts:1337 — the whole resolution block is gated on [HOST-1]("SWAMP_API_KEY"):
    if (!hookMode && [HOST-1]("SWAMP_API_KEY")) { ... }
    This is the only place loadScopeCache / whoami / setAuthScopes(response.scopes) are reached.
  • src/cli/mod.ts:1389-1392 — the non-env fallback reads creds?.scopes out of auth.json.
  • But that field is never populated for a collective token:
    • src/libswamp/auth/whoami.ts:141 short-circuits on if (!response.collectiveToken), so collective tokens skip the credential save entirely.
    • src/libswamp/auth/whoami.ts:91-102createAuthDeps.saveCredentials calls saveIdentityCache(serverUrl, username, collectives, fingerprint) with no scopes argument, and auth_repository.ts:170-178 rebuilds the record from scratch with ...(scopes ? {scopes} : {}), dropping any that were there.
  • src/cli/auth_context.ts:53-55setCollectiveToken still fires (it is a local prefix test on the stored key), so the credential is classified as collective while its scopes stay undefined.
  • src/cli/auth_context.ts:85-98 — collective + undefined scopes is exactly the throw condition.

Deterministic repro

export SWAMP_HOME="$(mktemp -d)"; mkdir -p "$SWAMP_HOME/config"

Write an auth.json whose apiKey is a collective token (a value beginning with the org prefix), with no scopes field — which is the only shape the CLI itself ever produces:

{"serverUrl":"https://swamp-club.com","apiKey":"swamp_org_...","apiKeyId":"id",
 "username":"repro","collectives":[],"apiKeyFingerprint":"swamp_org_gg"}

Then, with SWAMP_API_KEY not set:

swamp vault create @swamp/aws-sm v1

Observed:

Error: Your collective token lacks the vault:* scope.

No whoami is attempted, no cache is consulted, and there is no sequence of commands that populates scopes for this credential. The suggested remedy in the error ("create a collective token ... and set SWAMP_API_KEY") is, as it happens, the only thing that works — but the message presents it as a fix for a token that lacks a scope, not as "this credential must be delivered via the environment variable."

Impact

Two ways in:

  • A user who puts their collective token into auth.json by hand (reasonable — it is where credentials live, and servers.json/auth.json are otherwise the CLI's own storage) gets a credential that fails every team feature with a message about scopes.
  • Any future code path that persists a collective token to auth.json inherits the same dead state.

The failure is silent about its real cause. The token is fine, the scopes are fine, the delivery mechanism is the problem — and nothing in the error mentions the delivery mechanism.

Expected

  • Run scope resolution against whatever credential is in effect, not only against the environment variable — i.e. lift the SWAMP_API_KEY condition at mod.ts:1337 and resolve for creds.apiKey regardless of source.
  • Persist scopes for collective tokens too, or state plainly that collective tokens are env-var-only and say so in the error when one is found in auth.json.
  • #1381, #1382, #1384 — three other routes to the same misleading "lacks the X scope" string. Consider making that message name its cause in all four cases.

Environment

  • Verified against source main @ 10943ef. Gate shipped in a519305 (#1934), 2026-07-23; installed stable 20260722.210030.0-sha.7ff1e2f0 predates it.
  • macOS 14.
02Bog Flow
OPENTRIAGEDIN PROGRESSCLOSED

Closed

7/24/2026, 9:48:56 PM

No activity in this phase yet.

03Sludge Pulse
Editable. Press Enter to edit.

stack72 commented 7/24/2026, 9:48:56 PM

This is correct - a token stored in auth.json is a personal access token and has no scopes at this time! This is by design

magistr commented 7/24/2026, 9:58:14 PM

Author follow-up after checking the manual, in fairness to triage: the documented path for a collective token is the environment variable only. /manual/how-to/authenticate-with-api-keys says to 'Export a collective token (prefixed swamp_org_) as SWAMP_API_KEY', and neither that page nor /manual/reference/api-key-authentication suggests placing one in auth.json.

So this is an undocumented usage rather than a documented-and-broken one, which lowers the severity from what I filed. What remains worth fixing is the diagnosis, not the capability: the CLI accepts the credential, classifies it as a collective token via the local prefix test, and then reports 'lacks the vault:* scope' — a statement about scopes for what is really a delivery-mechanism problem. Either resolve scopes for whatever credential is in effect regardless of source, or detect a swamp_org_-prefixed key in auth.json and say plainly that collective tokens must be supplied via SWAMP_API_KEY.

Note also that /manual/reference/api-key-authentication documents auth.json as the credential store without mentioning that a collective token placed there behaves differently, which is what makes the trap easy to walk into.

Sign in to post a ripple.