feat(scripts): mint-api-key.sh + docs/operations/api-key-minting.md#28
Merged
khaliqgant merged 2 commits intomainfrom Apr 23, 2026
Merged
feat(scripts): mint-api-key.sh + docs/operations/api-key-minting.md#28khaliqgant merged 2 commits intomainfrom
khaliqgant merged 2 commits intomainfrom
Conversation
Adds a wrapper around the admin-bearer + POST /v1/api-keys + secret-store
flow that's been bitten three times today:
1. Picking the wrong scope set silently succeeds at mint, fails at
runtime — sage's SAGE_RELAYAUTH_API_KEY had scopes
["cloud:specialist:invoke"] but actually needed
["relayauth:identity:create:*", "relayauth:token:create:*",
"relayfile:fs:read:*", "relayfile:fs:write:*"] for its
mintRelayfileToken path. Surfaced as repeated 403 insufficient_scope
from production once the apiKeyAuth Workers fix landed.
2. Echoing the new key value to terminal/shell history before piping
into gh secret set leaks the credential.
3. Hand-typing the request body shape per service caller invites
typos that aren't caught until runtime.
The script:
- generates the admin bearer using the existing generate-dev-token.sh
helper (HS256, signed with SIGNING_KEY)
- POSTs to /v1/api-keys with the operator's name + scopes
- prints the new api-key id + scopes (safe), never the value
- pipes the value directly into one of {gh secret set | file with mode
600 | --print-key for explicit piping | mktemp fallback}
- optionally revokes a prior api-key id for clean rotation
- scrubs ADMIN_BEARER + the key value from env on exit
Doc at docs/operations/api-key-minting.md captures:
- canonical sage rotation example (the actual scope set that works)
- per-service required scopes (sage, specialist-worker, operator admin)
- operator emergency admin key recipe (provision before phase-122 step 3
sunsets HS256 admin bearers)
- explanation of why scope-subset enforcement matters
- recovery procedure when the value was lost mid-mint
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ot identity:create:* POST /v1/identities requires relayauth:identity:manage:*. The scope matcher treats 'manage' as implying create/read/write/delete, but not the reverse — so an api-key with identity:create:* cannot call that route. The previous example would succeed at mint and fail at runtime with 403 insufficient_scope. Caught while rotating sage's key during phase 122 wrap-up, 2026-04-23. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
scripts/mint-api-key.shand adocs/operations/api-key-minting.mdrunbook to wrap the admin-bearer +POST /v1/api-keys+ secret-store flow safely.Why
Three real footguns hit today (2026-04-23):
SAGE_RELAYAUTH_API_KEYwas minted with["cloud:specialist:invoke"]but the runtime needed["relayauth:identity:create:*", "relayauth:token:create:*", "relayfile:fs:read:*", "relayfile:fs:write:*"]. Mint succeeded, runtime failed with 403insufficient_scope.gh secret setleaked it to shell history.What it does
scripts/generate-dev-token.shhelperPOST /v1/api-keyswith--name+--scopes-json--print-keyis explicitly passed.--to-gh-secret REPO:NAME(piped togh secret set),--to-file PATH(mode 600), or a fallback mktemp tempfile--revoke-prior <id>rotates cleanly after the new key is in placeDoc
docs/operations/api-key-minting.mdcovers: