security: remove API key from browser bundle + full audit hardening (v0.2.0)#2
Merged
Merged
Conversation
….2.0) P1-1 (critical): Remove API key from browser bundle - Add Next.js server-side proxy route (apps/web/src/app/api/[...proxy]/route.ts) - Browser calls /api/* — proxy injects OPERATORBOARD_API_KEY server-side - Remove NEXT_PUBLIC_OPERATORBOARD_API_KEY from page.tsx, Dockerfile, docker-compose - Update CLI docker-compose template to use runtime env vars (not build args) - Update .env.example with correct server-side var names P1-2: Extend SSRF guard to agent endpoint field - Apply isSafeWebhookUrl() to endpoint at POST /agents registration - Add defense-in-depth check in executeTaskRun before dispatching - Add defense-in-depth check in POST /agents/:id/test before health fetch P1-3: Strip stack traces from HTTP error responses - setErrorHandler returns generic message; stack logged server-side only P2-1: CORS blocks all origins in production if OPERATORBOARD_CORS_ORIGINS unset - Was: fall back to wildcard allow-all - Now: return false (block all) with a startup warning log P2-2: POST /heartbeats returns 404 for unknown agent IDs P2-3: SQL table-name allowlist (assertAllowedTable in db.ts) P3-2: Security headers in next.config.ts - X-Frame-Options, X-Content-Type-Options, Referrer-Policy, Permissions-Policy, Strict-Transport-Security, Content-Security-Policy Update CHANGELOG.md and SECURITY.md with full findings documentation Co-Authored-By: Claude Sonnet 4.6 <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
This PR addresses all findings from the post-launch security review, with the most significant being an architectural fix to ensure the API key never reaches the browser.
endpointfield (was only onwebhookUrl)POST /heartbeatsreturns 404 for unknown agent IDsdb.tsX-Frame-Options, CSP, etc.)NEXT_PUBLIC_OPERATORBOARD_API_KEYKey architectural change (P1-1)
Before:
NEXT_PUBLIC_OPERATORBOARD_API_KEYwas baked into the Next.js client bundle at build time, visible in DevTools, network requests, and bundle source to any user.After: A catch-all proxy route (
apps/web/src/app/api/[...proxy]/route.ts) runs server-side. The browser calls/api/*, the proxy readsOPERATORBOARD_API_KEYfrom the Node.js environment and injects it into upstream requests to Fastify. The key never reaches the client.Migration: replace
NEXT_PUBLIC_OPERATORBOARD_API_KEYwithOPERATORBOARD_API_KEY(no prefix) on the web service. ReplaceNEXT_PUBLIC_API_URLwithOPERATORBOARD_API_URL. Seeapps/web/.env.examplefor the updated reference.Files changed
apps/web/src/app/api/[...proxy]/route.tsapps/web/src/app/page.tsxAPI_KEYconstant; fetch via/api/*proxyapps/web/next.config.tsapps/web/Dockerfileapps/web/.env.exampleapps/api/src/index.tsapps/api/src/db.tsdocker-compose.ymlpackages/cli/src/cli.tsCHANGELOG.mdSECURITY.mdTest plan
docker compose up— dashboard loads, all API calls succeed through proxyx-operatorboard-keyheader in browser requestsOPERATORBOARD_API_KEYvalue not in bundlehttp://192.168.1.1/task) → expect 400/heartbeatswith unknownagentId→ expect 404pnpm -r typecheckpasses🤖 Generated with Claude Code