Relationships
#1388 An empty stored apiKey counts as authenticated — saveIdentityCache writes apiKey:'' on a virgin machine, permanently satisfying the mandatory-account gate with no credential
Opened by magistr · 7/24/2026
Summary
setAuthenticated() is computed as bearerToken !== undefined, where bearerToken is the stored apiKey verbatim. An empty string is not undefined, so an auth.json containing apiKey: "" satisfies the mandatory-account gate on every team feature. This is not a hypothetical file: saveIdentityCache() writes exactly that on a machine that has never logged in.
Where (source)
src/cli/load_identity.ts:56-57—bearerToken = credentials?.apiKey.src/cli/mod.ts:1401—setAuthenticated(autoResolverIdentity.bearerToken !== undefined).src/infrastructure/persistence/auth_repository.ts:173—apiKey: existing?.apiKey ?? ""in the recordsaveIdentityCachebuilds.src/cli/mod.ts:1360-1374— reachessaveIdentityCacheon the personal-key path wheneverSWAMP_API_KEYis set and the cached credentials carry no username.- The behavior is pinned by an existing test:
auth_repository_test.ts:352-378assertsloaded.apiKey === "".
Also note the gate accepts any non-empty junk: requireScope returns early for anything lacking the swamp_org_ prefix (auth_context.ts:87), so a one-character SWAMP_API_KEY passes both the account gate and the scope gate.
Deterministic repro
A — the empty key satisfies the gate
export SWAMP_HOME="$(mktemp -d)"; mkdir -p "$SWAMP_HOME/config"
cat > "$SWAMP_HOME/config/auth.json" <<'JSON'
{"serverUrl":"https://swamp-club.com","apiKey":"","apiKeyId":"id",
"username":"repro","collectives":[],"apiKeyFingerprint":""}
JSON
swamp vault create env my-vaultObserved:
Error: Unknown vault type: env. Available types: local_encryption. ...That is the type error, meaning the account gate passed. With the same command and no auth.json at all, the result is the paywall ("Non-local vaults are a team feature..."). The only difference between the two runs is a credential that is the empty string.
B — how the file gets there without logging in
On a machine with no auth.json, run any single command with a personal SWAMP_API_KEY set. mod.ts:1367 calls saveIdentityCache, which creates auth.json with apiKey: "" (the env key itself is deliberately not persisted). Unset the env var afterwards and the CLI reports itself as authenticated forever, holding no credential at all.
Impact
The gate that is supposed to require an account can be satisfied by a file containing no account. Two practical consequences:
- The friendly local error is skipped and replaced by a worse remote one. Instead of "requires a free swamp-club.com account, run
swamp auth login", the user proceeds into the operation and gets an opaque server 401 — or, for the client-side-only gates (vault create,datastore setup extension), no error at all, because those paths never call swamp-club. - It is trivially self-inflicted. Anyone who uses
SWAMP_API_KEYonce on a fresh machine ends up in this state; nothing about it is visible inswamp auth whoami, which will report a username whileapiKeyis empty.
I am not filing this as a privilege-escalation report, because the gated operations are local and no server-side authorization depends on this flag. It is a correctness bug that makes the account requirement unreliable in both directions.
Expected
- Treat an empty (or whitespace-only)
apiKeyas absent:setAuthenticated(Boolean(bearerToken)), and haveAuthRepository.load()returnundefinedrather than a record whoseapiKeyis"". - Do not write a credential record with an empty
apiKeyat all — if there is no key to cache, cache identity under a separate key or skip the write. - Consider validating the shape of
SWAMP_API_KEY(non-empty, recognized prefix) rather than accepting any string.
Environment
- Verified against source
main@10943ef. Gate shipped ina519305(#1934), 2026-07-23; installed stable20260722.210030.0-sha.7ff1e2f0predates it. - macOS 14.
Closed
No activity in this phase yet.