AntFleet

Anatomy · 6017bf3f-2

Synchronous child_process calls block the Node event loop on every request

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

The vulnerable code

dashboard/app/api/secrets/route.ts:30-75

30const VALID_SECRET_NAME = /^[A-Z][A-Z0-9_]{1,}$/
31
32function ghAvailable(): boolean {
33 try {
34 execSync('gh auth status', { stdio: 'pipe' })
35 return true
36 } catch {
37 return false
38 }
39}
40
41function ghRepo(): string | null {
42 try {
43 const repo = execSync('gh repo set-default --view', { stdio: 'pipe' }).toString().trim()
44 if (repo && !repo.startsWith('no default')) return repo
45 } catch {}
46 try {
47 const repo = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', { stdio: 'pipe' }).toString().trim()
48 if (repo) return repo
49 } catch {}
50 return null
51}
52
53function ghArgsRepo(): string[] {
54 const repo = ghRepo()
55 return repo ? ['-R', repo] : []
56}
57
58function listSecrets(): string[] {
59 try {
60 const out = execFileSync('gh', ['secret', 'list', ...ghArgsRepo(), '--json', 'name', '-q', '.[].name'], {
61 stdio: 'pipe',
62 cwd: process.cwd(),
63 }).toString().trim()
64 return out ? out.split('\n').filter(Boolean) : []
65 } catch {
66 return []
67 }
68}
69
70export async function GET() {
71 if (!ghAvailable()) {
72 return NextResponse.json({
73 error: 'GitHub CLI not authenticated. Run: gh auth login',
74 ghReady: false,
75 }, { status: 503 })

The reasoning

Opus

`VALID_SECRET_NAME` regex allows starting underscore-free names but the comment/intent suggests stricter env-var rules; collisions with reserved/system names not blocked

lowapi-contractmedium
  • dashboard/app/api/secrets/route.ts:27-28
GitHub itself reserves names starting with `GITHUB_` and disallows names starting with a digit; the regex correctly forbids leading digits but permits `GITHUB_FOO`, which gh will reject — producing a 500 with raw error message (see other finding) instead of a clean 400. Minor, but combined with the error-leak finding it amplifies confusion. Also `{1,}` means minimum total length 2; GitHub allows 1-char-after-prefix but practical: fine.

Recommendation

Reject names matching /^GITHUB_/i and (optionally) length > 100 with a 400 before calling gh.

GPT-5

Duplicate gh helper functions across files increases drift risk

lowmaintainabilityhigh
  • dashboard/app/api/auth/route.ts:4-11
  • dashboard/app/api/auth/route.ts:13-23
  • dashboard/app/api/auth/route.ts:25-28
  • dashboard/app/api/secrets/route.ts:32-39
  • dashboard/app/api/secrets/route.ts:41-51
  • dashboard/app/api/secrets/route.ts:53-56
Identical helper logic is defined in multiple files, increasing maintenance burden and the risk of inconsistent behavior over time.

Recommendation

Extract shared gh helpers into a common module and import them from both routes.

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

30const VALID_SECRET_NAME = /^[A-Z][A-Z0-9_]{1,}$/
31
32function ghAvailable(): boolean {
33 try {
34 execSync('gh auth status', { stdio: 'pipe' })
35 return true
36 } catch {
37 return false
38 }
39}
40
41function ghRepo(): string | null {
42 try {
43 const repo = execSync('gh repo set-default --view', { stdio: 'pipe' }).toString().trim()
44 if (repo && !repo.startsWith('no default')) return repo
45 } catch {}
46 try {
47 const repo = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', { stdio: 'pipe' }).toString().trim()
48 if (repo) return repo
49 } catch {}
50 return null
51}
52
53function ghArgsRepo(): string[] {
54 const repo = ghRepo()
55 return repo ? ['-R', repo] : []
56}
57
58function listSecrets(): string[] {
59 try {
60 const out = execFileSync('gh', ['secret', 'list', ...ghArgsRepo(), '--json', 'name', '-q', '.[].name'], {
61 stdio: 'pipe',
62 cwd: process.cwd(),
63 }).toString().trim()
64 return out ? out.split('\n').filter(Boolean) : []
65 } catch {
66 return []
67 }
68}
69
70export async function GET() {
71 if (!ghAvailable()) {
72 return NextResponse.json({
73 error: 'GitHub CLI not authenticated. Run: gh auth login',
74 ghReady: false,
75 }, { status: 503 })

Closure

Closed 1 week ago

SHA: 4b9b49251c8c9808bf147d55aa2930352af2e8c0

View closure receipt on GitHub →

Tweet thread template

tweet 1 of 8162 / 280

Two frontier models reviewed PR #25 on 6f7fc663. Both found this bug: low performance: Synchronous child_process calls block the Node event loop on every request

tweet 2 of 8126 / 280

The vulnerable code (dashboard/app/api/secrets/route.ts:30-75): (full snippet at https://www.antfleet.dev/anatomy/6017bf3f-2)

tweet 3 of 8280 / 280

What Opus saw: "GitHub itself reserves names starting with `GITHUB_` and disallows names starting with a digit; the regex correctly forbids leading digits but permits `GITHUB_FOO`, which gh will reject — producing a 500 with raw error message (see other finding) instead of a cl…

tweet 4 of 8150 / 280

What GPT-5 saw: "Identical helper logic is defined in multiple files, increasing maintenance burden and the risk of inconsistent behavior over time."

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-2)

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-2

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