Skip to content

Harden production web endpoint controls - #5341

Open
lawrencecchen wants to merge 3 commits into
mainfrom
feat-prod-endpoint-hardening
Open

lawrencecchen wants to merge 3 commits into
mainfrom
feat-prod-endpoint-hardening

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 4, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • fail closed when the production feedback limiter is unavailable and reject non-multipart feedback bodies
  • add private no-store cache headers to VM API JSON responses, including credential endpoints
  • require VM create-credit env keys in the production env audit and run DB-backed notification/device-token tests in CI
  • keep the push limiter test stable and assert limiter-before-body behavior

Pentest context

Read-only production subagents tested notifications, device tokens, VM APIs, public pages, feedback, third-party routes, CORS preflights, fake tokens, malformed bodies, and method confusion. No auth bypass was found. This PR fixes the hardening gaps they found.

Verification

  • cd web && bun test tests/feedback-route.test.ts
  • cd web && bun test tests/vm-route-auth.test.ts
  • cd web && bun test tests/notifications-push-route.test.ts
  • cd web && bunx tsc --noEmit --pretty false
  • cd web && bun run lint -- app/api/feedback/route.ts app/api/notifications/push/route.ts services/vms/routeHelpers.ts tests/feedback-route.test.ts tests/vm-route-auth.test.ts tests/notifications-push-route.test.ts scripts/cloud-vm/projects.mjs
  • cd web && bun scripts/cloud-vm/audit-vercel-env.mjs production --strict
  • git diff --check

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Medium Risk
Changes authentication-adjacent rate limiting and cache headers on VM credential responses; misconfigured limiters or env audit rules could block legitimate traffic until production env is updated.

Overview
This PR tightens production web endpoint controls after pentest-style review: limiters and payloads fail closed, sensitive VM JSON is not cacheable, and deploy/CI checks catch gaps earlier.

Feedback (/api/feedback): When a configured Vercel Firewall limiter is active, any limiter error (not only not-found) returns 503 rate_limiter_unavailable instead of continuing without protection. Non-multipart/form-data bodies are rejected with 415 before formData() parsing. Test-only configureFeedbackRateLimitForTests wires limiter behavior without VERCEL=1.

Push (/api/notifications/push): Production (and tests with required: true) require a limiter id; missing id or limiter errors return 503 before reading the body or touching the DB. Existing 429 behavior when limited is unchanged; tests assert limiter runs before oversized bodies/DB.

VM APIs: Shared jsonResponse now sets Cache-Control: private, no-store on all VM JSON responses, including SSH credential payloads.

Deploy audit & docs: CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST and CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID are required in the Vercel env audit and must be non-empty (explicit opt-out values like none still satisfy presence). README reflects that production must declare an intentional free-plan credit choice.

CI: The Postgres migration job also runs device-tokens-route and notification-rate-limit tests, plus new feedback-route coverage.

Reviewed by Cursor Bugbot for commit 528ef75. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Hardens production web endpoints: feedback rejects non-multipart bodies with 415 before parsing, VM API JSON responses are no longer cacheable, and the production env audit now requires explicit free-plan create-credit configuration.

  • Bug Fixes

    • Feedback: reject non-multipart bodies with 415 before formData() parsing; the configured firewall check runs before the body is read.
    • VM API: jsonResponse adds Cache-Control: private, no-store to all responses, including credential payloads.
  • Migration

    • Set CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST and CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID in production (must be non-empty; use none/disabled/off/false to explicitly opt out).

Written for commit f173093. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Hardened feedback and push endpoints: centralized rate-limit gating, “fail closed” (503) when limiter unavailable, and feedback rejects non-multipart requests (415).
    • JSON API responses for VM routes are now explicitly non-cacheable.
  • Tests

    • CI now runs expanded DB/migration tests.
    • Added tests covering feedback/push rate-limit failure cases, content-type handling, and VM SSH/cache-control responses.
  • Chores

    • Added two new required runtime env keys (production audit).
  • Documentation

    • Clarified free-plan VM create-credit env requirements.

@vercel

vercel Bot commented Jun 4, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 4, 2026 7:23am
cmux-staging Building Building Preview, Comment Jun 4, 2026 7:23am

@coderabbitai

coderabbitai Bot commented Jun 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Resolve runtime/test rate-limit IDs for feedback and push routes, treat firewall errors as temporary unavailability (503), add multipart/form-data guard (415), make JSON responses non-cacheable, extend CI DB tests, add two VM env keys, and update tests to use per-id firewall handler registries.

Changes

API Hardening and Resilience

Layer / File(s) Summary
Feedback route rate-limit and multipart validation
web/app/api/feedback/route.ts, web/tests/feedback-route.test.ts
Resolve feedback rate-limit id via runtime or test override; only call checkRateLimit when an id is present; treat any firewall error as temporary unavailability (log and return 503); add isMultipartFormData guard that returns 415 for unsupported content types; export configureFeedbackRateLimitForTests; tests register per-id firewall handlers and mock email sending.
Push notification rate-limit resolution and test harness
web/app/api/notifications/push/route.ts, web/tests/notifications-push-route.test.ts
Add Vercel runtime detection and module-level test override; resolve effective push rate-limit id and require it when configured; unify non-throttling checkRateLimit errors into a 503 path with logging; export configurePushRateLimitForTests; tests use an id-keyed global firewall handler registry.
VM route JSON cache-control header
web/services/vms/routeHelpers.ts, web/tests/vm-route-auth.test.ts
jsonResponse now sets cache-control: private, no-store; tests assert the header on VM provisioning and SSH credential endpoints and validate returned credential fields.
Environment requirements, audit, and CI database tests
web/scripts/cloud-vm/projects.mjs, web/scripts/cloud-vm/audit-vercel-env.mjs, .github/workflows/ci.yml, web/services/vms/README.md
Add CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST and CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID to required runtime env keys and non-blank list; audit now flags listed keys when blank; extend CI web-db-migrations job to run additional Bun tests (device-tokens-route.test.ts, notification-rate-limit.test.ts); update README guidance.

🎯 3 (Moderate) | ⏱️ ~20 minutes

"I nibble logs and guard each gate,
I mock the firewall, test the rate,
JSON tucked safe, no caches near,
Env keys checked, the path is clear,
— CodeRabbit 🐇"

🚥 Pre-merge checks | ✅ 17 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.69% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Harden production web endpoint controls' directly describes the main objective of the PR: security hardening of production endpoints for fail-closed behavior, cache headers, and validation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PR contains no Swift code changes; check for Swift 6 actor isolation is not applicable to TypeScript/JavaScript/YAML changes only.
Cmux Swift Blocking Runtime ✅ Passed PR contains no Swift code changes; the Swift blocking runtime check does not apply. All modifications are TypeScript, JavaScript, YAML, or Markdown files only.
Cmux No Hacky Sleeps ✅ Passed No hacky sleeps, delays, timers, or polling patterns found. All changes are hardening measures (error handling, headers, env checks) and deterministic test mocking without timer-based synchronization.
Cmux Algorithmic Complexity ✅ Passed PR adds rate-limiting and validation; all algorithmic operations involve bounded collections with explicit size limits; no nested scans, rescans, or unbenchmarked algorithms.
Cmux Swift Concurrency ✅ Passed This PR contains no Swift code changes; all modifications are to TypeScript/JavaScript, YAML workflows, and Markdown files. The Swift concurrency check is not applicable.
Cmux Swift @Concurrent ✅ Passed No Swift files are modified in this PR; all changes are TypeScript, JavaScript, YAML, or Markdown. The check does not apply.
Cmux Swift File And Package Boundaries ✅ Passed PR contains no Swift file changes—only TypeScript, JavaScript, YAML, and Markdown modifications in the web/ directory. Custom check for Swift file/package boundaries is not applicable.
Cmux Swift Logging ✅ Passed PR contains no Swift code changes; only TypeScript, JavaScript, YAML, and Markdown modifications. Swift logging rules do not apply.
Cmux User-Facing Error Privacy ✅ Passed All user-facing errors use generic product terms. No vendor names, provider flags, env variables, database details, credentials, or sensitive data exposed per review-bot-rules/user-facing-errors.md.
Cmux Full Internationalization ✅ Passed Error codes like rate_limiter_unavailable follow existing unlocalized API pattern (device-tokens, VM routes); protocol identifiers exempt from i18n per review rules.
Cmux Swiftui State Layout ✅ Passed PR contains no SwiftUI changes; all modifications are backend web code (TypeScript routes, scripts, tests) and documentation. Custom check not applicable.
Cmux Architecture Rethink ✅ Passed PR contains no Swift code. The swift-architectural-rethink rule applies only to Swift changes; this PR modifies only TypeScript, JavaScript, YAML, and Markdown files.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR contains no Swift code changes; only TypeScript/JavaScript web backend files are modified. The Swift window check is not applicable to this PR.
Description check ✅ Passed The pull request description is comprehensive and follows the required template structure with clear Summary, Testing, and Verification sections.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-prod-endpoint-hardening

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 79c9e9af09

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread web/tests/feedback-route.test.ts Outdated
},
}));

const feedbackRoute = await import("../app/api/feedback/route");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Isolate the feedback route env module

When this file runs as part of the normal web job's bun test, another test can import ../app/env first while the feedback env vars are still unset. Because env is a module-level singleton, this import then reuses the cached undefined feedback config and both new feedback tests receive 503 { error: "Feedback endpoint is not configured" } instead of exercising the limiter/content-type paths; cd web && bun test tests/notifications-push-route.test.ts tests/vm-route-auth.test.ts tests/feedback-route.test.ts reproduces. Please isolate/reset the env module or mock the config dependency before importing the route.

Useful? React with 👍 / 👎.

@lawrencecchen
lawrencecchen force-pushed the feat-prod-endpoint-hardening branch from 79c9e9a to 097f160 Compare June 4, 2026 05:58
@greptile-apps

greptile-apps Bot commented Jun 4, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR hardens production web endpoints following a pentest: feedback and push routes now fail closed (503) when the Vercel Firewall limiter is unavailable, the feedback route rejects non-multipart bodies with 415 before any parsing, and all VM API JSON responses (including SSH credential payloads) gain Cache-Control: private, no-store. Production env auditing is tightened to require non-blank VM credit keys.

  • Feedback (/api/feedback): Rate-limit check is now unconditional when a limiter ID is configured, and any limiter error returns 503 instead of logging and continuing; a content-type guard rejects non-multipart bodies before formData() parsing.
  • Push (/api/notifications/push): Fail-closed behavior added for both a missing limiter ID and any firewall error; rate-limit check runs before body read and DB access.
  • VM API / ops: jsonResponse adds private, no-store on every JSON response; CI runs device-token and push rate-limit tests in the DB job; production audit now requires CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST and CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID to be present and non-blank.

Confidence Score: 4/5

Safe to merge after verifying CMUX_PUSH_RATE_LIMIT_ID is set in every production Vercel environment, or after adding it to the audit's required key list.

The push route now returns 503 for every authenticated user if CMUX_PUSH_RATE_LIMIT_ID is absent from the Vercel environment, whereas before it simply skipped rate limiting. Because this key is not added to requiredRuntimeEnvKeys in the audit script, the strict deploy audit won't catch a missing or accidentally deleted value. If production already has the key set this is a non-issue, but the audit gap means a future configuration drift could silently break all push notifications without any pre-deploy warning.

web/scripts/cloud-vm/projects.mjs — CMUX_PUSH_RATE_LIMIT_ID should be added to requiredRuntimeEnvKeys alongside the VM credit keys that were added in this PR.

Important Files Changed

Filename Overview
web/app/api/feedback/route.ts Fail-closed rate limiting (503 on any limiter error) and 415 pre-check for non-multipart bodies; test hook exported from production module but gated by VERCEL env in production path
web/app/api/notifications/push/route.ts Fail-closed rate limiting added; CMUX_PUSH_RATE_LIMIT_ID now implicitly required in Vercel production (missing → 503 for all push), but not added to the audit's required key list
web/services/vms/routeHelpers.ts Added cache-control: private, no-store to all jsonResponse calls including SSH credential endpoints; straightforward and correct
web/scripts/cloud-vm/projects.mjs Added nonBlankRequiredRuntimeEnvKeys for VM credit keys; CMUX_PUSH_RATE_LIMIT_ID is missing from requiredRuntimeEnvKeys even though the push route now 503s if it's absent on Vercel
.github/workflows/ci.yml Added device-tokens-route and notification-rate-limit tests to the DB migration CI job; correct placement alongside DB-dependent tests
web/services/vms/README.md Updated docs to reflect that VM credit env keys are now required by the production audit, with explicit opt-out values documented
web/tests/feedback-route.test.ts New test file covers fail-closed limiter path (503 on not-found) and 415 rejection for urlencoded bodies; afterEach/beforeEach cleanup is correct
web/tests/notifications-push-route.test.ts Refactored to use configurePushRateLimitForTests instead of VERCEL=1 env; new tests for fail-closed limiter error and missing limiter ID paths are correct
web/tests/vm-route-auth.test.ts Added assertions for cache-control header on create and SSH credential responses; tests are straightforward and correct
web/scripts/cloud-vm/audit-vercel-env.mjs Non-blank validation logic for nonBlankRequiredRuntimeEnvKeys is correct; hasNonBlankEnvValue trims before checking length

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[POST /api/feedback] --> B{feedbackConfig set?}
    B -- No --> C[503 not configured]
    B -- Yes --> D{rateLimitId resolved?}
    D -- No --> G[skip rate limit]
    D -- Yes --> E[checkRateLimit]
    E --> F{result?}
    F -- rateLimited/blocked --> H[429 Rate limit exceeded]
    F -- error --> I[503 rate_limiter_unavailable]
    F -- ok --> G
    G --> J{multipart/form-data?}
    J -- No --> K[415 Invalid multipart payload]
    J -- Yes --> L[parse formData + send email]

    M[POST /api/notifications/push] --> N{auth ok?}
    N -- No --> O[401 unauthorized]
    N -- Yes --> P{isPushRateLimitRequired AND no ID?}
    P -- Yes --> Q[503 rate_limiter_unavailable]
    P -- No --> R{rateLimitId set?}
    R -- No --> S[skip Vercel rate limit]
    R -- Yes --> T[checkRateLimit]
    T --> U{result?}
    U -- rateLimited/blocked --> V[429 rate_limited]
    U -- error --> W[503 rate_limiter_unavailable]
    U -- ok --> S
    S --> X[read body → DB → APNs]
Loading

Reviews (4): Last reviewed commit: "Harden production web endpoint controls" | Re-trigger Greptile

Comment thread web/app/api/notifications/push/route.ts Outdated
Comment on lines 70 to 72
if (error === "not-found") {
console.error("notifications.push.rate_limit_not_found", env.CMUX_PUSH_RATE_LIMIT_ID);
console.error("notifications.push.rate_limit_not_found", rateLimitId);
}

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.

P2 Push limiter still fails open on "not-found"

The feedback route in this same PR was hardened to return 503 on any error from the Vercel firewall (including "not-found"). The push route still logs and continues when the configured limiter ID can't be found, meaning push requests proceed without rate limiting if the firewall rule is deleted or misconfigured in production. Given the pentest context of this PR, parity between the two routes is worth considering — an unreachable limiter silently disables the only Vercel-layer rate check for this endpoint.

Comment thread web/app/api/feedback/route.ts Outdated
Comment on lines +216 to +218
export function configureFeedbackRateLimitForTests(rateLimitId: string | null) {
testFeedbackRateLimitId = rateLimitId;
}

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.

P2 Test-only escape hatch exported from production module

configureFeedbackRateLimitForTests (and configurePushRateLimitForTests in the push route) are exported symbols in the production bundle that mutate module-level state. In practice this is safe because resolveFeedbackRateLimitId gates on process.env.VERCEL === "1" and ignores testFeedbackRateLimitId in production, but exporting test hooks from production route files is a pattern that can cause confusion and makes the surface slightly harder to audit. A process.env.NODE_ENV === "test" guard or a separate test-utilities file would keep the escape hatch clearly out of production symbol exports.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/app/api/feedback/route.ts`:
- Around line 76-83: Replace the two hardcoded English response strings in the
feedback route with locale lookups: instead of returning jsonError("Feedback is
temporarily unavailable", 503) and jsonError("Unsupported feedback content
type", 415), call the i18n/message getter used in this API (via next-intl or the
repo’s locale helper) inside the same handler so jsonError receives localized
text; update the corresponding message keys in every locale bundle referenced by
web/i18n/routing.ts under web/messages/ (add entries for the new keys) so all
supported locales return translated copies; ensure the lookup is performed
before calling jsonError and that you use the same key names consistently (e.g.,
feedback.rate_limit_unavailable and feedback.unsupported_content_type) to match
the new message entries.

In `@web/scripts/cloud-vm/projects.mjs`:
- Around line 33-34: The audit currently treats a key as present based only on
Object.keys(env), so update web/scripts/cloud-vm/audit-vercel-env.mjs to
validate that required Vercel env vars are non-empty strings instead of just
existing; specifically ensure the new keys
"CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST" and
"CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID" are checked for truthy/non-blank
values (e.g., trim() !== "" or length > 0) and fail the --strict audit when
either is empty or whitespace-only.

In `@web/tests/feedback-route.test.ts`:
- Around line 3-24: The test mutates process.env.SKIP_ENV_VALIDATION and
process.env.VERCEL but only restores keys from envKeys; update the
setup/teardown to capture the original values for SKIP_ENV_VALIDATION and VERCEL
(alongside originalEnv) and ensure the afterAll restores them as well; modify
the afterAll cleanup that currently iterates envKeys (referencing envKeys and
originalEnv) to also reset process.env.SKIP_ENV_VALIDATION and
process.env.VERCEL back to their original values (or delete them if originally
undefined) so those flags do not leak into other suites.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dc3ee307-32f6-488d-b4eb-fc78a1e06f09

📥 Commits

Reviewing files that changed from the base of the PR and between 9d926e2 and 79c9e9a.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • web/app/api/feedback/route.ts
  • web/app/api/notifications/push/route.ts
  • web/scripts/cloud-vm/projects.mjs
  • web/services/vms/routeHelpers.ts
  • web/tests/feedback-route.test.ts
  • web/tests/notifications-push-route.test.ts
  • web/tests/vm-route-auth.test.ts

Comment thread web/app/api/feedback/route.ts Outdated
Comment on lines +76 to +83
if (error) {
console.error("feedback.route.rate_limit_unavailable", error);
return jsonError("Feedback is temporarily unavailable", 503);
}
}

if (!isMultipartFormData(request)) {
return jsonError("Unsupported feedback content type", 415);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Localize these new feedback error bodies.

Lines 78 and 83 add new production API response copy as hardcoded English strings. Because this route lives under web/app/api, these messages need to come from the repo’s locale source and be added across every supported locale instead of being embedded in the handler.

As per coding guidelines, web/**/*.{ts,tsx,js,md} production API response changes must read from next-intl or another locale-specific source and update every locale listed in web/i18n/routing.ts with matching web/messages/ entries.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/api/feedback/route.ts` around lines 76 - 83, Replace the two
hardcoded English response strings in the feedback route with locale lookups:
instead of returning jsonError("Feedback is temporarily unavailable", 503) and
jsonError("Unsupported feedback content type", 415), call the i18n/message
getter used in this API (via next-intl or the repo’s locale helper) inside the
same handler so jsonError receives localized text; update the corresponding
message keys in every locale bundle referenced by web/i18n/routing.ts under
web/messages/ (add entries for the new keys) so all supported locales return
translated copies; ensure the lookup is performed before calling jsonError and
that you use the same key names consistently (e.g.,
feedback.rate_limit_unavailable and feedback.unsupported_content_type) to match
the new message entries.

Comment thread web/tests/feedback-route.test.ts Outdated
coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/tests/notifications-push-route.test.ts (1)

58-77: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reset the push-route test override in teardown.

beforeEach installs a process-wide limiter override and handler, but afterAll only restores env vars. That leaves configurePushRateLimitForTests("cmux-push-test") and the "cmux-push-test" registry entry live for later suites in the same Bun worker, so unrelated push-route tests can silently inherit this mocked limiter.

Suggested fix
 afterAll(() => {
+  pushRoute.configurePushRateLimitForTests(null);
+  firewallState.__cmuxFirewallCheckRateLimits?.delete("cmux-push-test");
   for (const key of envKeys) {
     const value = originalEnv[key];
     if (typeof value === "undefined") {
       delete process.env[key];
     } else {
       process.env[key] = value;
     }
   }
 });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/tests/notifications-push-route.test.ts` around lines 58 - 77, The test
installs a process-wide limiter override via
pushRoute.configurePushRateLimitForTests("cmux-push-test") and registers a
handler in firewallState.__cmuxFirewallCheckRateLimits but the afterAll teardown
only restores env vars; fix by undoing those test-only changes in afterAll: call
pushRoute.configurePushRateLimitForTests(undefined) (or the no-arg/reset
variant) to clear the override and delete the "cmux-push-test" entry from
firewallState.__cmuxFirewallCheckRateLimits (e.g.,
firewallState.__cmuxFirewallCheckRateLimits?.delete("cmux-push-test")), ensuring
the test mutation is fully reverted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/tests/feedback-route.test.ts`:
- Around line 54-75: Add teardown to remove the test-only limiter and the
firewall handler after each test: call
feedbackRoute.configureFeedbackRateLimitForTests(undefined) (or null) to clear
the module-global override set by
configureFeedbackRateLimitForTests("feedback-limiter"), then remove the handler
from firewallState.__cmuxFirewallCheckRateLimits by calling its
delete("feedback-limiter") and, if the map becomes empty, clear or unset
__cmuxFirewallCheckRateLimits; also clear mocks (checkRateLimit.mockClear(),
send.mockClear()) to ensure no order-dependence between suites.

---

Outside diff comments:
In `@web/tests/notifications-push-route.test.ts`:
- Around line 58-77: The test installs a process-wide limiter override via
pushRoute.configurePushRateLimitForTests("cmux-push-test") and registers a
handler in firewallState.__cmuxFirewallCheckRateLimits but the afterAll teardown
only restores env vars; fix by undoing those test-only changes in afterAll: call
pushRoute.configurePushRateLimitForTests(undefined) (or the no-arg/reset
variant) to clear the override and delete the "cmux-push-test" entry from
firewallState.__cmuxFirewallCheckRateLimits (e.g.,
firewallState.__cmuxFirewallCheckRateLimits?.delete("cmux-push-test")), ensuring
the test mutation is fully reverted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: aa6d217d-e461-4981-9487-d961b2f4b0cc

📥 Commits

Reviewing files that changed from the base of the PR and between 79c9e9a and 097f160.

📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • web/app/api/feedback/route.ts
  • web/app/api/notifications/push/route.ts
  • web/scripts/cloud-vm/projects.mjs
  • web/services/vms/routeHelpers.ts
  • web/tests/feedback-route.test.ts
  • web/tests/notifications-push-route.test.ts
  • web/tests/vm-route-auth.test.ts

Comment thread web/tests/feedback-route.test.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 8 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="web/app/api/feedback/route.ts">

<violation number="1" location="web/app/api/feedback/route.ts:80">
P3: New user-facing API error messages are hardcoded English strings instead of using the project’s localization path.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread web/tests/feedback-route.test.ts Outdated
Comment thread web/app/api/feedback/route.ts Outdated
console.error("feedback.route.rate_limit_error", error);
if (error) {
console.error("feedback.route.rate_limit_unavailable", error);
return jsonError("Feedback is temporarily unavailable", 503);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P3: New user-facing API error messages are hardcoded English strings instead of using the project’s localization path.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At web/app/api/feedback/route.ts, line 80:

<comment>New user-facing API error messages are hardcoded English strings instead of using the project’s localization path.</comment>

<file context>
@@ -73,16 +75,16 @@ export async function POST(request: Request) {
-          console.error("feedback.route.rate_limit_error", error);
+        if (error) {
+          console.error("feedback.route.rate_limit_unavailable", error);
+          return jsonError("Feedback is temporarily unavailable", 503);
         }
       }
</file context>

coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/app/api/feedback/route.ts`:
- Around line 78-80: The current branch logs limiter failures but returns
"Feedback endpoint is not configured" to callers; change the user-facing
response in the error branch inside the feedback route handler (where
resolveFeedbackConfig() has already succeeded) to return a neutral
temporary-unavailable error (e.g., machine code "rate_limiter_unavailable" or a
generic "Service temporarily unavailable") via jsonError with a 503 status
instead of exposing configuration state; keep the internal
console.error("feedback.route.rate_limit_unavailable", error) for diagnostics
but ensure jsonError does not leak config details.

In `@web/scripts/cloud-vm/projects.mjs`:
- Around line 33-34: The two env keys CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID
and CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST are documented as optional but are
currently listed in requiredRuntimeEnvKeys causing production audit failures;
either remove these keys from requiredRuntimeEnvKeys (to make them truly
optional) or keep them required but update the runtime validation in the same
module to accept explicit opt-out sentinel values ("none", "disabled", "off",
"false") and treat CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST defaulting to 1 when
opted out; also update the README wording for
CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID and CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST
to clearly state the production contract (required with valid value or required
with explicit opt-out value vs optional in dev/preview).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 713fbc33-8ff5-48c6-b8b0-76d2f2282f77

📥 Commits

Reviewing files that changed from the base of the PR and between 097f160 and 29556b9.

📒 Files selected for processing (9)
  • .github/workflows/ci.yml
  • web/app/api/feedback/route.ts
  • web/app/api/notifications/push/route.ts
  • web/scripts/cloud-vm/audit-vercel-env.mjs
  • web/scripts/cloud-vm/projects.mjs
  • web/services/vms/routeHelpers.ts
  • web/tests/feedback-route.test.ts
  • web/tests/notifications-push-route.test.ts
  • web/tests/vm-route-auth.test.ts

Comment thread web/app/api/feedback/route.ts Outdated
Comment thread web/scripts/cloud-vm/projects.mjs
coderabbitai[bot]
coderabbitai Bot previously requested changes Jun 4, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@web/app/api/notifications/push/route.ts`:
- Around line 58-60: The code currently skips rate limiting when
resolvePushRateLimitId() returns no id; update the handler(s) that call
resolvePushRateLimitId() (both usages around the first call and the later call
at the other block) to treat a missing rateLimitId as a fail-closed condition:
detect when rateLimitId is falsy and immediately respond with a
temporary-unavailable/503 error (and log the missing CMUX_PUSH_RATE_LIMIT_ID)
instead of calling checkRateLimit or proceeding; ensure the same change is
applied to both places where resolvePushRateLimitId() is used so the production
path cannot bypass throttling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1e55fcbd-4bc0-46f7-98ec-75a93d429921

📥 Commits

Reviewing files that changed from the base of the PR and between 29556b9 and b67e6f1.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • web/app/api/feedback/route.ts
  • web/app/api/notifications/push/route.ts
  • web/scripts/cloud-vm/audit-vercel-env.mjs
  • web/scripts/cloud-vm/projects.mjs
  • web/services/vms/README.md
  • web/services/vms/routeHelpers.ts
  • web/tests/feedback-route.test.ts
  • web/tests/notifications-push-route.test.ts
  • web/tests/vm-route-auth.test.ts

Comment thread web/app/api/notifications/push/route.ts Outdated
Comment thread web/scripts/cloud-vm/projects.mjs Outdated
Comment on lines +48 to +51
export const nonBlankRequiredRuntimeEnvKeys = [
"CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST",
"CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID",
];

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.

P1 CMUX_PUSH_RATE_LIMIT_ID not audited but now required in production

This PR changes push-route behavior: on Vercel, a missing CMUX_PUSH_RATE_LIMIT_ID now makes isPushRateLimitRequired() && !rateLimitId true, returning 503 for every authenticated push request. Before this PR the missing-ID path simply skipped rate limiting and let the request proceed. The audit script doesn't include CMUX_PUSH_RATE_LIMIT_ID in requiredRuntimeEnvKeys, so a deployment without this env var passes the strict audit while silently breaking all push notifications for every user.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 528ef75b16

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +22 to +24
const missingRequired = requiredRuntimeEnvKeys.filter((key) => {
if (!present.has(key)) return true;
return nonBlankRequired.has(key) && !hasNonBlankEnvValue(env, key);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Validate create-credit cost values in the audit

When CMUX_VM_PLAN_FREE_CREATE_CREDIT_ITEM_ID is configured to a real Stack item but CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST is set to a non-empty invalid value such as 0, none, or abc, the new strict audit still reports ok because it only checks for a non-blank string here. The VM create path later calls positiveInteger for CMUX_VM_PLAN_FREE_CREATE_CREDIT_COST in billingGateway.ts, so the first production create for that plan will fail at runtime even though cloud-vm:env:audit -- --strict passed; the audit should validate a positive integer unless the item id is explicitly disabled.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
web/app/api/feedback/route.ts (1)

188-195: 🧹 Nitpick | 🔵 Trivial | 💤 Low value

Consider aligning cache-control with the VM route contract.

The feedback route uses Cache-Control: no-store while the shared jsonResponse helper in web/services/vms/routeHelpers.ts (lines 93-101) sets cache-control: private, no-store. The private directive provides an additional guarantee that intermediate caches (CDNs, proxies) won't store the response even if they ignore no-store.

For consistency and defense-in-depth on sensitive endpoints, consider using private, no-store here as well.

♻️ Suggested alignment
       return NextResponse.json(
         { ok: true },
         {
           headers: {
-            "Cache-Control": "no-store",
+            "Cache-Control": "private, no-store",
           },
         },
       );
 function jsonError(message: string, status: number) {
   return NextResponse.json(
     { error: message },
     {
       status,
       headers: {
-        "Cache-Control": "no-store",
+        "Cache-Control": "private, no-store",
       },
     },
   );
 }

Also applies to: 409-418

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@web/app/api/feedback/route.ts` around lines 188 - 195, The feedback route is
setting Cache-Control to "no-store" while the shared jsonResponse helper uses
"private, no-store"; update the NextResponse.json responses in the feedback
handler (the returns that currently set headers with "Cache-Control":
"no-store") to use "Cache-Control": "private, no-store" instead so they align
with the jsonResponse behavior (and apply the same change to the other similar
return blocks in this file).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@web/app/api/feedback/route.ts`:
- Around line 188-195: The feedback route is setting Cache-Control to "no-store"
while the shared jsonResponse helper uses "private, no-store"; update the
NextResponse.json responses in the feedback handler (the returns that currently
set headers with "Cache-Control": "no-store") to use "Cache-Control": "private,
no-store" instead so they align with the jsonResponse behavior (and apply the
same change to the other similar return blocks in this file).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0eaea710-14d9-4711-8938-e9981129a234

📥 Commits

Reviewing files that changed from the base of the PR and between b67e6f1 and 528ef75.

📒 Files selected for processing (10)
  • .github/workflows/ci.yml
  • web/app/api/feedback/route.ts
  • web/app/api/notifications/push/route.ts
  • web/scripts/cloud-vm/audit-vercel-env.mjs
  • web/scripts/cloud-vm/projects.mjs
  • web/services/vms/README.md
  • web/services/vms/routeHelpers.ts
  • web/tests/feedback-route.test.ts
  • web/tests/notifications-push-route.test.ts
  • web/tests/vm-route-auth.test.ts

@lawrencecchen
lawrencecchen dismissed stale reviews from coderabbitai[bot], coderabbitai[bot], coderabbitai[bot], and coderabbitai[bot] June 4, 2026 07:51

Stale CodeRabbit review on superseded commit; addressed in latest commit and latest CodeRabbit review is non-blocking.

Preserve current firewall opt-out and database push limiting policies while retaining multipart validation, private VM JSON responses, and explicit free-credit audit configuration. Keep current provider, authentication, delivery, and CI behavior.
@cursor

cursor Bot commented Sep 17, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@github-actions

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@lawrencecchen

Copy link
Copy Markdown
Contributor Author

Fleet instruction update for head ad12bffc8f79dbfcbaf76afd0468abf01626af56: this PR is classified other. No macOS build tag is claimed. The current controller app recipe does not establish iOS/test readiness; that requires the appropriate validated recipe. Use cmux-ci for supported jobs, retain the returned ID and receipt, and wait on the same ID after any timeout. Do not use retired maclease allocation or post credentials. Exact-head tags will be posted only after the applicable build succeeds.

@teamleaderleo teamleaderleo added area: build-and-ci Build system, CI workflows, test infrastructure S2: major A crash, hang, lost state, broken connection, or a regression on a path people use ready-to-land Reviewed and ready to land when CI is green labels Sep 30, 2026
@github-actions

github-actions Bot commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor

CI failure attribution

CI passes on f17309306d (run 36746025104 attempt 1).

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's.

@teamleaderleo

Copy link
Copy Markdown
Collaborator

This is red for a reason that has nothing to do with the change. Both this and #5341 fail only guards / workflow-guard-tests / preflight, on the same ten files, none of which either pull request touches:

Python test execution registry validation failed:
  - tests/test_cli_current.py: newly added tests may not enter the legacy migration lane
  - tests/test_cli_glaeda_execution.py: ...
  ... eight more
warning: could not read the base registry at 9bf6cb8c9421:
  Command '['git','show','9bf6cb8c9421:tests/test-execution.toml']' returned non-zero exit status 128

Those ten files and tests/test-execution.toml all arrived together on main in 224327b55a07 on 2026-09-24. They are main's, in main's legacy lane.

The cause is .github/workflows/ci-guards.yml:262, which passes github.event.pull_request.base.sha as the baseline. That field is not main's current tip, it is the base tip recorded when the branch was last synchronized, and here it is thirteen days stale:

value date
base.sha reported for this PR 9bf6cb8c9421 2026-09-17
origin/main 6d7ad149121a 2026-09-30

This PR was updated today, so that is not staleness from sitting idle. And because the branch has merged main in since, 9bf6cb8c9421 is an ancestor of its head, so git merge-base base_sha HEAD collapses to base.sha itself and the guard ends up diffing 2026-09-17 to HEAD. Everything main added in that window, including 224327b55a07, is attributed to this pull request.

Verified against the real module in a scratch repo, driving it from both inputs:

newly_added_tests(stale base.sha) -> ['tests/test_mine.py', 'tests/test_theirs.py']   wrong
newly_added_tests(main tip)       -> ['tests/test_mine.py']                            right

So the validator is correct and is being handed a bad baseline. A fix is in flight on fix/registry-newly-added-after-catch-up: resolve base.ref at run time and pass what it resolves to, plus a workflow-level test so nobody puts base.sha back. Once it lands, this PR should go green without you touching anything. Nothing for you to do here, and worth not chasing it as a problem with your change.

Note ci-status and guards / Guard status are just the cascade from that one step. web-validation on #5341 is a separate and much older failure.

— Raindrop g2 🫧 / Run: run_worker_20260930_3fc64ba6

This branch was successfully deployed

1 active (outdated) deployment
Preview – cmux — 528ef75b Deployed Jun 4, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: build-and-ci Build system, CI workflows, test infrastructure ready-to-land Reviewed and ready to land when CI is green S2: major A crash, hang, lost state, broken connection, or a regression on a path people use

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants