Skip to content

(janitor/dedupe): consolidate internal API secret auth - #7192

Merged
kilo-code-bot[bot] merged 1 commit into
mainfrom
janitor/dedupe-internal-api-auth
Oct 19, 2026
Merged

kilo-code-bot[bot] merged 1 commit into
mainfrom
janitor/dedupe-internal-api-auth

Conversation

@kilo-code-bot

@kilo-code-bot kilo-code-bot Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

Classification

Shared behavior — the same domain concept ("verify a request carries the internal API secret") was reimplemented per-route.

What changed

Seventeen /api/internal/* route handlers in apps/web each repeated the identical auth check:

const secret = req.headers.get('X-Internal-Secret'); // or 'x-internal-api-key'
if (!INTERNAL_API_SECRET || !secret || !timingSafeEqual(secret, INTERNAL_API_SECRET)) {
  return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}

The same secret was transported under two different header spellings (x-internal-api-key vs x-internal-secret), and three files carried their own local secretMatches helper. This is the kind of drift that makes a future auth change (new secret, added callback token, constant-time hardening) land inconsistently.

Consolidated into one owner, authorizeInternalApiRequest in apps/web/src/lib/internal-api-auth.ts, which accepts either header and returns the 401 response (or null), matching the existing authorizeSupportRequest pattern. Call sites now read:

const unauthorized = authorizeInternalApiRequest(req);
if (unauthorized) return unauthorized;

Intentional duplication left alone

  • glanceable-agents-snapshot: uses an HMAC-normalized secretMatches (deliberate hardening).
  • kiloclaw/billing-side-effects: logs a structured side-effect before rejecting.
  • Bearer-token routes (mcp-catalog/token, auto-routing-benchmark/*): different transport/secret.
  • support/_auth.ts: already centralized under SUPPORT_API_SECRET.

Verification

  • tsgo --noEmit -p apps/web/tsconfig.json — clean
  • oxlint --config .oxlintrc.json on changed files — 0 warnings / 0 errors
  • oxfmt on changed files — no diffs
  • No behavior change: same secret, same comparison, same 401 body; helper accepts a superset of headers (both spellings).

Seventeen /api/internal routes repeated the same INTERNAL_API_SECRET
header check (read header, timingSafeEqual against config, 401 on
mismatch), with two header spellings (x-internal-api-key and
x-internal-secret) and a duplicated secretMatches helper in three
files. Centralize it in authorizeInternalApiRequest, which accepts
either header, and remove the per-route boilerplate.
@kilo-code-bot kilo-code-bot Bot added the janitor Automated dead-code/duplication cleanup label Oct 6, 2026
@kilo-code-bot
kilo-code-bot Bot enabled auto-merge (squash) October 6, 2026 07:23
@kilo-code-bot

kilo-code-bot Bot commented Oct 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Reviewed the consolidation of 17 /api/internal/* secret checks into authorizeInternalApiRequest; the refactor preserves the exact constant-time comparison, !INTERNAL_API_SECRET guard, and 401 response, and no auth bypass or runtime regression was found.

Notes

  • The helper accepts both x-internal-api-key and x-internal-secret for the same INTERNAL_API_SECRET. This is intentional and documented in the PR description and helper comment; because the same secret value is still required in constant time, it does not broaden access.
  • timingSafeEqual from @kilocode/encryption guards differing lengths, so no exception path was introduced.
  • All changed routes still consume NextResponse, and no stale INTERNAL_API_SECRET, timingSafeEqual, or secretMatches references remain in the changed files. Existing route tests use header spellings the helper accepts.
  • No potential memory leak introduced (no per-request state or retained closures added).
Files Reviewed (18 files)
  • apps/web/src/lib/internal-api-auth.ts (new)
  • apps/web/src/app/api/internal/auto-fix-status/[ticketId]/route.ts
  • apps/web/src/app/api/internal/auto-fix/comment-reply/route.ts
  • apps/web/src/app/api/internal/auto-fix/config/route.ts
  • apps/web/src/app/api/internal/auto-fix/create-pr/route.ts
  • apps/web/src/app/api/internal/code-review-usage/[reviewId]/route.ts
  • apps/web/src/app/api/internal/db/replication-lag/route.ts
  • apps/web/src/app/api/internal/integrations/dolthub/token/route.ts
  • apps/web/src/app/api/internal/kiloclaw/instance-ready/route.ts
  • apps/web/src/app/api/internal/kiloclaw/scheduled-action-side-effects/route.ts
  • apps/web/src/app/api/internal/security-agent/notifications/route.ts
  • apps/web/src/app/api/internal/triage-status/[ticketId]/route.ts
  • apps/web/src/app/api/internal/triage/add-label/route.ts
  • apps/web/src/app/api/internal/triage/check-duplicates/route.ts
  • apps/web/src/app/api/internal/triage/classify-config/route.ts
  • apps/web/src/app/api/internal/triage/post-comment/route.ts
  • apps/web/src/app/api/internal/usage/record/route.ts
  • apps/web/src/app/api/internal/user-data-exports/ready/route.ts

Reviewed by deepseek-v4.1-flash · Input: 52.2K · Output: 9.1K · Cached: 250.2K

Review guidance: REVIEW.md from base branch main

@eshurakov eshurakov left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Executive Summary

Independent review of all 18 changed files at 6cd35a91989a5c415d2a274b2cb01cb43aeec71c against cc585e94ce47193f58cf9f1cc6710fe649061f0a, all 17 auth call sites, repository callers, and existing reviews found no major security or correctness regressions.

Missing, empty, and invalid secrets still fail closed with the same 401 JSON, before body parsing or side effects; the existing timing-safe comparator and downstream response/error paths are preserved. Both header spellings require the same secret. x-internal-api-key takes precedence if both are supplied, including an invalid/empty key shadowing a valid x-internal-secret; no traced producer sends that conflicting combination.

Verification: 2,499 isolated auth cases against GitHub-fetched pinned source passed, with base/head non-auth equivalence checked for every migrated route. CI at this head passed typecheck, lint, formatting, build, and 1,145 web/shared test suites (18,718 tests passed); logs confirm the replication-lag, scheduled-action, and security-notification route suites ran. No dedicated helper test was added; isolated checks stubbed NextResponse and did not rerun the DB-backed suites locally. No workspace edits or installs.

Files Reviewed (18 files)
  • apps/web/src/app/api/internal/auto-fix-status/[ticketId]/route.ts
  • apps/web/src/app/api/internal/auto-fix/comment-reply/route.ts
  • apps/web/src/app/api/internal/auto-fix/config/route.ts
  • apps/web/src/app/api/internal/auto-fix/create-pr/route.ts
  • apps/web/src/app/api/internal/code-review-usage/[reviewId]/route.ts
  • apps/web/src/app/api/internal/db/replication-lag/route.ts
  • apps/web/src/app/api/internal/integrations/dolthub/token/route.ts
  • apps/web/src/app/api/internal/kiloclaw/instance-ready/route.ts
  • apps/web/src/app/api/internal/kiloclaw/scheduled-action-side-effects/route.ts
  • apps/web/src/app/api/internal/security-agent/notifications/route.ts
  • apps/web/src/app/api/internal/triage-status/[ticketId]/route.ts
  • apps/web/src/app/api/internal/triage/add-label/route.ts
  • apps/web/src/app/api/internal/triage/check-duplicates/route.ts
  • apps/web/src/app/api/internal/triage/classify-config/route.ts
  • apps/web/src/app/api/internal/triage/post-comment/route.ts
  • apps/web/src/app/api/internal/usage/record/route.ts
  • apps/web/src/app/api/internal/user-data-exports/ready/route.ts
  • apps/web/src/lib/internal-api-auth.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

janitor Automated dead-code/duplication cleanup

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant