Skip to content

feat(deploy): Phase 4 — CORS allowlist + prompt-length cap on canonical demo proxy#324

Merged
blove merged 5 commits into
mainfrom
claude/canonical-demo-cors-body-cap
May 15, 2026
Merged

feat(deploy): Phase 4 — CORS allowlist + prompt-length cap on canonical demo proxy#324
blove merged 5 commits into
mainfrom
claude/canonical-demo-cors-body-cap

Conversation

@blove
Copy link
Copy Markdown
Contributor

@blove blove commented May 15, 2026

Summary

Phase 4 of the canonical-demo deployment plan. Two new defensive gates on the demo proxy, both firing BEFORE rate-limit and upstream fetch (cost-free):

  1. CORS allowlist replaces the wildcard `*`. Default: `https://demo.cacheplane.ai\`. Configurable via `ALLOWED_ORIGINS` (comma-separated). Returns 403 for mismatched Origins; passes server-to-server clients (no Origin header) through with no CORS headers.
  2. Request-body byte cap. Default 8192. Configurable via `MAX_PROMPT_BYTES`. Returns 413 fast-fail. Checks `Content-Length` first, falls back to `JSON.stringify(req.body).length`.

Both gates skip entirely when their config field is undefined → `scripts/examples-middleware.ts` (cockpit-examples) is unaffected.

External setup

Already done:

  • Vercel env `ALLOWED_ORIGINS=https://demo.cacheplane.ai\` on cacheplane-demo (production + preview)
  • Vercel env `MAX_PROMPT_BYTES=8192` on cacheplane-demo (production + preview)

Spec & Plan

  • `docs/superpowers/specs/2026-05-15-canonical-demo-cors-body-cap-design.md`
  • `docs/superpowers/plans/2026-05-15-canonical-demo-cors-body-cap.md`

Test plan

Notes on reviewer comments

The code-quality reviewer flagged a missing `--external:@neondatabase/serverless` in `scripts/assemble-demo.ts`. That's intentional. Vercel Build Output API functions get only the bundled `index.js` — no `node_modules` at runtime. `--external` would cause runtime `Cannot find module '@neondatabase/serverless'`. Phase 3 already verified the inlined-driver approach works.

🤖 Generated with Claude Code

blove and others added 5 commits May 15, 2026 09:19
Tightens CORS from wildcard to single-origin allowlist
(demo.cacheplane.ai) and caps request body at 8 KB. Both gates fire
before the rate-limit + upstream fetch so they're cost-free.
Configurable via env vars (ALLOWED_ORIGINS, MAX_PROMPT_BYTES) on the
demo wrapper. Examples wrapper unaffected.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Extends ProxyConfig with optional allowedOrigins and maxBodyBytes
fields (the latter wired in the next commit). When allowedOrigins
is configured, the handler echoes a matching Origin (with vary:
origin) or returns 403 for a mismatch. Requests without an Origin
header (server-to-server) are passed through with no CORS headers
set — CORS only matters in browser context.

When allowedOrigins is undefined (cockpit-examples), the handler
keeps the legacy access-control-allow-origin: * behavior. No change
for examples.

5 new unit tests cover: matching origin echoed, mismatch returns
403, missing Origin allowed without CORS headers, OPTIONS preflight
echoes, and the wildcard-preserved-when-unset legacy path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an optional maxBodyBytes check between the _proxy_debug
endpoint and the rate-limit gate. Checks Content-Length first
(short-circuit); falls back to JSON.stringify(req.body).length.
Returns 413 with { error: 'payload_too_large', maxBytes,
actualBytes } when over the cap.

Skipped entirely when maxBodyBytes is undefined (cockpit-examples).

3 new unit tests cover the JSON.stringify path, Content-Length
short-circuit, and the legacy-unset path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Reads two new env vars at module load and passes them to
createProxyHandler:
  - ALLOWED_ORIGINS (comma-separated; default: https://demo.cacheplane.ai)
  - MAX_PROMPT_BYTES (integer; default: 8192)

The cockpit-examples wrapper does not pass these fields, so its
behavior stays exactly as today (* CORS, no body cap).

Also adds --external:@neondatabase/serverless to the assemble-demo
esbuild command so the Vercel function bundles correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cacheplane Ready Ready Preview, Comment May 15, 2026 6:03pm

Request Review

@blove blove merged commit aad0e87 into main May 15, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant