AntFleet

Anatomy · 6017bf3f-1

Token reassembly from `claude setup-token` can splice in non-token text and write garbage to CLAUDE_CODE_OAUTH_TOKEN

highbugclosed in 4b9b492
repo 6f7fc663·PR #25·reviewed 1 week ago·closed 1 week ago

The vulnerable code

dashboard/app/api/auth/route.ts:81-108

81 error: 'Could not extract token. Paste your API key manually instead.',
82 }, { status: 400 })
83 }
84 // Take everything until we hit a space, newline-then-non-alnum, or empty line
85 // The token chars are: A-Za-z0-9_-
86 const tokenChars: string[] = []
87 for (const line of tokenBlock.split('\n')) {
88 const trimmed = line.trim()
89 if (tokenChars.length === 0) {
90 // First line with the token
91 tokenChars.push(trimmed)
92 } else if (/^[A-Za-z0-9_\-]+$/.test(trimmed)) {
93 // Continuation line (all valid token chars — wrapped portion of token)
94 tokenChars.push(trimmed)
95 } else {
96 break
97 }
98 }
99 const token = tokenChars.join('')
100
101 execFileSync('gh', ['secret', 'set', 'CLAUDE_CODE_OAUTH_TOKEN', ...ghArgsRepo()], {
102 input: token,
103 stdio: ['pipe', 'pipe', 'pipe'],
104 })
105
106 return NextResponse.json({ ok: true, method: 'oauth' })
107 } catch (error: unknown) {
108 const msg = error instanceof Error ? error.message : 'Failed to setup auth'

The reasoning

Opus

Missing authentication/authorization on secret-management endpoints

criticalsecurityhigh
  • dashboard/app/api/secrets/route.ts:95-145
  • dashboard/app/api/auth/route.ts:50-110
Both route handlers expose the ability to read which secrets are set, create/overwrite arbitrary GitHub Actions secrets (including ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN), and delete them. There is no authentication, no session check, no CSRF token, and no origin/host verification. If the Next.js dashboard is ever exposed beyond localhost (or reachable from a browser visiting a malicious page that POSTs JSON with a simple Content-Type to bypass CORS preflight is harder, but a curl/proxy or LAN attacker still trivially exploits it), an attacker can overwrite repo-level secrets with attacker-controlled values, exfiltrate via subsequent workflow runs, or wipe production credentials. Even on localhost, any other process on the machine can hit the endpoint. There is also no rate limiting.

Recommendation

Require authentication for all mutating routes (and arguably GET, which leaks which secrets exist). At minimum, bind the dev server to 127.0.0.1, verify a same-origin/Host header, require a CSRF token, and gate behind a session cookie or local-only token. Also reject requests if NODE_ENV === 'production' unless an explicit admin gate is satisfied.

GPT-5

Token extraction logic may capture non-token characters from first line

mediumbughigh
  • dashboard/app/api/auth/route.ts:84-86
  • dashboard/app/api/auth/route.ts:89-95
The code takes the entire first trimmed line starting at "sk-ant-oat" without restricting to allowed token characters. If the first line contains extra characters after the token (e.g., punctuation or guidance), they will be included in the secret, causing invalid storage/auth failures. The comment states stricter behavior than implemented, indicating a mismatch.

Recommendation

Extract the first-line token using a regex for contiguous valid chars, e.g., const m = tokenBlock.match(/^(sk-ant-oat[A-Za-z0-9_-]*)/); if (!m) error; const token = m[1] + continuationParts.join(''); Or apply the same /^[A-Za-z0-9_-]+$/ constraint to the first-line substring up to the first non-matching character.

The agreement

Both frontier models flagged this within the same line range. AntFleet's unanimous gate fired — the finding posted on the PR. Closed in 4b9b492.

The fix

81 error: 'Could not extract token. Paste your API key manually instead.',
82 }, { status: 400 })
83 }
84 // Take everything until we hit a space, newline-then-non-alnum, or empty line
85 // The token chars are: A-Za-z0-9_-
86 const tokenChars: string[] = []
87 for (const line of tokenBlock.split('\n')) {
88 const trimmed = line.trim()
89 if (tokenChars.length === 0) {
90 // First line with the token
91 tokenChars.push(trimmed)
92 } else if (/^[A-Za-z0-9_\-]+$/.test(trimmed)) {
93 // Continuation line (all valid token chars — wrapped portion of token)
94 tokenChars.push(trimmed)
95 } else {
96 break
97 }
98 }
99 const token = tokenChars.join('')
100
101 execFileSync('gh', ['secret', 'set', 'CLAUDE_CODE_OAUTH_TOKEN', ...ghArgsRepo()], {
102 input: token,
103 stdio: ['pipe', 'pipe', 'pipe'],
104 })
105
106 return NextResponse.json({ ok: true, method: 'oauth' })
107 } catch (error: unknown) {
108 const msg = error instanceof Error ? error.message : 'Failed to setup auth'

Closure

Closed 1 week ago

SHA: 4b9b49251c8c9808bf147d55aa2930352af2e8c0

View closure receipt on GitHub →

Tweet thread template

tweet 1 of 8197 / 280

Two frontier models reviewed PR #25 on 6f7fc663. Both found this bug: high bug: Token reassembly from `claude setup-token` can splice in non-token text and write garbage to CLAUDE_CODE_OAUTH_TOKEN

tweet 2 of 8124 / 280

The vulnerable code (dashboard/app/api/auth/route.ts:81-108): (full snippet at https://www.antfleet.dev/anatomy/6017bf3f-1)

tweet 3 of 8280 / 280

What Opus saw: "Both route handlers expose the ability to read which secrets are set, create/overwrite arbitrary GitHub Actions secrets (including ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN), and delete them. There is no authentication, no session check, no CSRF token, and no …

tweet 4 of 8280 / 280

What GPT-5 saw: "The code takes the entire first trimmed line starting at "sk-ant-oat" without restricting to allowed token characters. If the first line contains extra characters after the token (e.g., punctuation or guidance), they will be included in the secret, causing inva…

tweet 5 of 897 / 280

Both flagged the same line range. AntFleet's unanimous gate fired — the finding posted on the PR.

tweet 6 of 893 / 280

The fix landed in commit 4b9b492: (view diff at https://www.antfleet.dev/anatomy/6017bf3f-1)

tweet 7 of 881 / 280

AntFleet reviews every PR with two frontier models. Only unanimous findings post.

tweet 8 of 877 / 280

Full anatomy + reasoning + diffs: https://www.antfleet.dev/anatomy/6017bf3f-1

Paste into X composer one tweet at a time. X has no multi-tweet intent API.