fix(security): sprint-2 hardening — M-1, M-2, HD-1, HD-2, I-2#27
Open
stackbilt-admin wants to merge 1 commit intomainfrom
Open
fix(security): sprint-2 hardening — M-1, M-2, HD-1, HD-2, I-2#27stackbilt-admin wants to merge 1 commit intomainfrom
stackbilt-admin wants to merge 1 commit intomainfrom
Conversation
Follow-up to 2026-04-10 audit critical chain. Closes five findings:
- M-1: social OAuth state bound to browser via HttpOnly cookie
(kills session-fixation path)
- M-2: userError() helper routes every tool-error through
sanitizeError + truncate + trace-id; sanitizeError now
strips service binding names (ENGINE, AUTH_SERVICE, etc.)
- HD-1: rate limit /login (5/email/5min + 20/IP/5min) and
/signup (3/IP/10min) via KV sliding window
- HD-2: double-submit CSRF cookie on all OAuth form POSTs
(login, signup, oauth/github, oauth/google)
- I-2: documented redirect URI validation asymmetry in
handlePostAuthorize so the deny-branch lookupClient
doesn't look like dead code to future maintainers
Tests: +18 (cookie, CSRF, rate limit, getClientIp). 120 → 138 passing.
Refs: Stackbilt-dev/codebeast docs/audits/mcp-gateway-2026-04-10.md
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
Follow-up to the 2026-04-10 audit critical chain (fixed in 256ba06). Closes five remaining findings in a single batch on a dedicated branch.
Audit report:
Stackbilt-dev/codebeast→docs/audits/mcp-gateway-2026-04-10.mdFindings closed
oauth_binding).handleSocialOAuthCallbackrejects callbacks whose cookie doesn't match the KV-stored binding — kills the session-fixation path where any party holding the stateKey could complete the callback.userError(prefix, err, traceId)helper routes every tool-error return throughsanitizeError+ truncate (200 chars) + trace-id.sanitizeErrorextended withSERVICE_BINDING_NAMESregex soAUTH_SERVICE,ENGINE,DEPLOYER, etc. are stripped from any user-visible error text. Replaces directerr.messageinterpolation inscaffold_publish,scaffold_deploy,visual_*,scaffold_import, and allproxyToolCallerror paths. Full errors still land in worker logs tied to the trace id./login(5 per email per 5 min + 20 per IP per 5 min) and/signup(3 per IP per 10 min). Runs before the auth RPC so brute-force attempts can't burn quota on the auth service or probe timing. Returns 429 + Retry-After.loginResponse()/signupResponse()wrappers mint a fresh token per page render, inject it as a hiddencsrf_tokenfield into all forms (email login, signup, oauth/github, oauth/google), and stamp a matching HttpOnly cookie. Every POST handler callsverifyCsrf()before touching anything else. SameSite=Lax on the cookie provides belt-and-suspenders protection on top of the double-submit check.handlePostAuthorize's redirect-URI validation so a future maintainer can't mistake the deny-branchlookupClient()for dead code and delete it. Explains whyapprovedoesn't need an explicit lookup (completeAuthorization does it internally).New helpers (all exported + tested)
getCookie(request, name)— parses Cookie header, URL-decodesbuildCookie(name, value, maxAgeSeconds)— HttpOnly/Secure/SameSite=Lax/Path=/clearCookie(name)— Max-Age=0 formmintCsrf()— fresh random tokenverifyCsrf(request, formData)— double-submit checkcheckRateLimit(kv, key, limit, windowSeconds)— returns{allowed, retryAfter?}getClientIp(request)— CF-Connecting-IP preferred, falls back to X-Forwarded-ForTest plan
npx tsc --noEmit— cleannpm test— 138 / 138 passing (was 120; +18 new unit tests)buildCookieattribute contractverifyCsrf(match, form-missing, cookie-missing, mismatch, empty-string)checkRateLimit(under-limit, over-limit, independent keys, denied path doesn't re-increment)getClientIp(CF-Connecting-IP, X-Forwarded-For fallback, unknown).github/workflows/ci.yml) — unchanged, will gate this PRMigration notes
oauthParamsstring to JSON{oauthParams, browserBinding}.handleSocialOAuthCallbackaccepts the legacy format during the first ~5 minutes post-deploy with a console warning, so in-flight flows drain cleanly. After the TTL expires, all state writes use the new format.scaffold_publishstill requiresgithub_token(unchanged since H-2).Out of scope (next sprint)
gateway.tsdecomposition (1321 LOC → 5 files)oauth-handler.ts,scaffold-materializer.ts,tool-registry.ts)npm audit --audit-level=high— CI step iscontinue-on-error: trueso this is advisory; fix vianpm audit fixin the cleanup passAudit log entry to follow
Will update
docs/audits/mcp-gateway-2026-04-10.mdinStackbilt-dev/codebeastwith a "2026-04-10 — Sprint 2 hardening closed" block once this PR merges and deploys.