Skip to content

fix(api): fail closed on missing upstash env in production - #506

Merged
collinsezedike merged 3 commits into
drydocs:mainfrom
subheeksh5599:fix/rate-limit-fail-closed
Aug 13, 2026
Merged

fix(api): fail closed on missing upstash env in production#506
collinsezedike merged 3 commits into
drydocs:mainfrom
subheeksh5599:fix/rate-limit-fail-closed

Conversation

@subheeksh5599

Copy link
Copy Markdown
Contributor

Summary

api/_lib/middleware.ts falls back to a per-process in-memory Map limiter whenever the Upstash env vars are missing, with no signal. Across concurrent Vercel workers that fallback is effectively no limit at all, so a production deploy missing UPSTASH_REDIS_REST_URL/UPSTASH_REDIS_REST_TOKEN silently serves traffic without distributed rate limiting.

This makes the module fail closed: at load time, if VERCEL_ENV === "production" and the Upstash vars are absent, it throws instead of constructing ratelimit as null. Local dev (VERCEL_ENV unset) and preview deploys (VERCEL_ENV="preview") keep the in-memory fallback unchanged.

Test plan

  • pnpm --filter @meridian/api test — 26 passed (4 new tests cover the production throw, production-with-vars load, and fallback kept for dev + preview)
  • pnpm typecheck:api passes
  • pnpm exec eslint on changed files — clean
  • pnpm exec prettier --check — clean

Closes #483

@vercel

vercel Bot commented Aug 12, 2026

Copy link
Copy Markdown

@subheeksh5599 is attempting to deploy a commit to the Collins' projects Team on Vercel.

A member of the Team first needs to authorize it.

collinsezedike

This comment was marked as outdated.

@collinsezedike
collinsezedike dismissed their stale review August 12, 2026 22:42

Redoing this as inline file comments instead of a single review body.

Comment thread api/__tests__/middleware.test.ts Outdated

// production rate-limit guard -------------------------------------------------

describe("production rate-limit guard", () => {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This new describe block runs inside a test file that imports middleware.js statically at the top of the file (line 7). The fix under review moves the production/missing-env check to module load time, so if this test file ever runs in a shell with VERCEL_ENV=production set but the Upstash credentials absent (vercel env pull, vercel build locally, or a future CI job mirroring the Vercel build environment), that top-level import throws and crashes the entire file, taking down all the pre-existing applyCors/rate-limit tests along with these new production-guard ones, not just failing one assertion.

Guard against the ambient environment instead of relying on it never having VERCEL_ENV=production set during a test run. Either unset VERCEL_ENV explicitly in a top-level beforeAll, or move the production-throw test into its own file that imports middleware.js dynamically inside the test body.

Comment thread api/__tests__/middleware.test.ts Outdated
Comment on lines +66 to +71
process.env.VERCEL_ENV = savedEnv.VERCEL_ENV;
process.env.UPSTASH_REDIS_REST_URL = savedEnv.UPSTASH_REDIS_REST_URL;
process.env.UPSTASH_REDIS_REST_TOKEN = savedEnv.UPSTASH_REDIS_REST_TOKEN;
delete process.env.VERCEL_ENV;
delete process.env.UPSTASH_REDIS_REST_URL;
delete process.env.UPSTASH_REDIS_REST_TOKEN;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

These lines assign each saved env var back and then immediately delete it, before the Object.entries(savedEnv).forEach on lines 72-75 does the real restoration. Nothing else executes in between, so the delete calls unconditionally wipe out whatever the assignments just set, and the forEach overwrites the result again right after. They have no effect on the final state and only obscure that the forEach is the actual restoration logic. Delete them.

Move the production-guard tests into their own file that imports
middleware.js only dynamically, and add a vitest setup file that clears
VERCEL_ENV/Upstash vars before test files load. Without this, any test
file importing middleware.ts crashes at load when the shell has
VERCEL_ENV=production and no Upstash credentials (e.g. after vercel env
pull), taking down the pre-existing applyCors/rate-limit tests.
@subheeksh5599

Copy link
Copy Markdown
Contributor Author

Addressed both review points in 74bccfb:

  1. Ambient VERCEL_ENV=production crash — reproduced it first: it was worse than reported, handlers.test.ts crashed too (it imports v1/tx/deposit.ts → middleware). Fixed with a belt-and-braces pair:
    • New api/vitest.setup.ts (wired via api/vitest.config.ts) deletes VERCEL_ENV/Upstash vars in setupFiles, which run before test files are imported — the only place that can stop a static-import throw. This protects every api test file, not just this one.
    • The 4 production-guard tests moved to api/__tests__/middleware.rate-limit-guard.test.ts, which imports middleware.js only dynamically inside the test bodies.
  2. Dead env-restore lines — removed the assign-then-immediately-delete lines; afterEach now only does the Object.entries(savedEnv) restoration.

Verified: pnpm --filter @meridian/api test passes normally (26 tests) AND with VERCEL_ENV=production exported (previously 2 files crashed); pnpm typecheck:api, eslint, prettier all clean.

@collinsezedike collinsezedike left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thank you for the fix, and for addressing both review comments cleanly, moving the production-guard tests into their own file with dynamic imports is a better solution than either fix I suggested. Approved.

Feel free to pick up another open issue whenever you're ready.

@collinsezedike
collinsezedike merged commit a9dc42c into drydocs:main Aug 13, 2026
8 of 9 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.

[Security] Rate-limit fallback fails open silently in production

2 participants