Skip to content

fix(web): bound metered /api route inputs (OG size cap, Exa url caps, research guards) - #1580

Closed
Sravanjangam wants to merge 1 commit into
supermemoryai:mainfrom
Sravanjangam:fix/metered-api-input-caps
Closed

fix(web): bound metered /api route inputs (OG size cap, Exa url caps, research guards)#1580
Sravanjangam wants to merge 1 commit into
supermemoryai:mainfrom
Sravanjangam:fix/metered-api-input-caps

Conversation

@Sravanjangam

@Sravanjangam Sravanjangam commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Hi supermemory team 👋 Thanks for building such a great product! While running a security & quality audit of the repo we hit this issue and put together a small, tested fix — details below.

Problem

Three metered routes accepted unbounded attacker-controlled input:

  1. app/api/og/route.ts buffered entire fetched HTML into memory via response.text() — size chosen by the fetched origin — then cached it publicly.
  2. app/api/onboarding/extract-content/route.ts relayed an arbitrary-length array of arbitrary URLs into paid Exa quota.
  3. app/api/onboarding/research/route.ts interpolated uncapped name/email strings into the LLM prompt with no timeout on the generateText call.

Anonymous callers (see #1579/H1) could exhaust worker memory or drain third-party credits. Part of #1578 (findings M1–M3).

Solution

Hard caps enforced at the trust boundary with explicit early status codes — reject instead of truncating mid-operation.

Changes

  • og/route.tsreadBoundedText(): streamed read capped at 2 MB + Content-Length precheck, both fetch paths → 413
  • extract-content/route.ts → ≤10 URLs, http(s) only via new URL(), ≤2048 chars each → 400
  • research/route.tsname ≤200, email ≤320 chars + AbortSignal.timeout(60_000)

Verification

Fresh from the committed branch: web unit suites green; tsc --noEmit adds zero new errors vs baseline for all touched files; Biome clean. Merge note: depends on #1579's guards landing first (same files).


Happy to iterate on any of this — feedback and reworks very welcome! 🙏

Environment

  • macOS 26.1 (arm64) · bun 1.4.0 · node v26.7.0
  • vitest 3.2.4 (workspace-pinned) · Biome lint clean
  • Branch fix/metered-api-input-caps — all gates re-run fresh at commit 2fb7a5c91b61

- /api/og: stream-read fetched HTML with a 2MB cap (Content-Length
  pre-check + incremental abort) instead of buffering response.text()
  unbounded; returns 413 when exceeded. Applies to the redirect path too.
- /api/onboarding/extract-content: cap urls at 10 per request, each
  <=2048 chars and http(s)-only, so the endpoint can't relay arbitrary
  unbounded URL lists into paid Exa quota.
- /api/onboarding/research: bound name (<=200) and email (<=320) before
  prompt interpolation and add a 60s abort signal on generateText so a
  hung tool call can't burn xAI credits indefinitely.
graphite-app Bot pushed a commit that referenced this pull request Aug 23, 2026
Cherry-picks #1579 and #1580 from @Sravanjangam (security audit #1578), plus improvements on top.

- `/api/og`, `/api/onboarding/extract-content` and `/api/onboarding/research` now verify the session against the auth backend; the middleware only checked that a cookie was present, so a forged cookie reached handlers that spend metered Exa/xAI quota.
- Bounds those routes: 2MB cap on fetched HTML, max 10 http(s) URLs per request, name/email length limits and a 60s timeout on the LLM call.
- De-duplicates URLs before calling Exa, and collapses whitespace in `name`/`email` so a newline can't forge extra prompt lines. Both adapted from @SEPURI-SAI-KRISHNA's #1528 and #1530.
- Deletes the unused, unauthenticated `account-status` route.

Verified locally: pre-fix `/api/og` returned 200 for a forged cookie, post-fix it returns 401. Five duplicate URLs collapse to two before reaching Exa, and a newline-laden `name` arrives as a single prompt line.

Supersedes #1528 and #1530.
@MaheshtheDev

Copy link
Copy Markdown
Member

Merged as part of #1589, which cherry-picks your commit with your authorship intact.

Changes we made on top: URLs are now parsed with new URL() and de-duplicated before the Exa call, and name/email have their whitespace collapsed so a newline cannot forge extra prompt lines. Both were adapted from #1528 and #1530. We also deleted the unused account-status route rather than guarding it.

Thanks @Sravanjangam.

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.

2 participants