Skip to content

fix(web): bound and validate the onboarding extract-content batch - #1528

Open
SEPURI-SAI-KRISHNA wants to merge 1 commit into
supermemoryai:mainfrom
SEPURI-SAI-KRISHNA:fix/web-bound-onboarding-inputs
Open

fix(web): bound and validate the onboarding extract-content batch#1528
SEPURI-SAI-KRISHNA wants to merge 1 commit into
supermemoryai:mainfrom
SEPURI-SAI-KRISHNA:fix/web-bound-onboarding-inputs

Conversation

@SEPURI-SAI-KRISHNA

Copy link
Copy Markdown
Contributor

Summary

/api/onboarding/extract-content forwards a caller-supplied urls array straight to
https://api.exa.ai/contents with livecrawl: "fallback". The only validation today is
that every element is a non-empty string:

if (!urls.every((url) => typeof url === "string" && url.trim())) { ... }

body: JSON.stringify({ urls, text: true, livecrawl: "fallback" })

The array is unbounded, entries are never parsed or de-duplicated, and the scheme is
never checked. Each entry is a potentially live-crawled — and billed — Exa request, so a
single call carrying thousands of URLs (or the same URL thousands of times) turns into a
proportionally large invoice.

Changes

  • Cap the batch at MAX_URLS = 20 and reject larger payloads with 400.
  • Parse each entry with new URL() and reject malformed values with 400, instead of
    passing junk through to a paid API.
  • Require http: or https:, so schemes like file: / data: never reach Exa.
  • De-duplicate on the normalized href, so a repeated URL is billed once.

Behaviour for well-formed input is unchanged, apart from the URLs being sent in
normalized form.

Note for reviewers: this route appears to be unused

While checking the call site to pick a safe MAX_URLS, I could not find any caller for
this route in the monorepo. The only /api/* route the web client fetches is /api/og
(apps/web/components/memories-grid.tsx:157).

The same appears to hold for its two siblings, /api/onboarding/research and
/api/onboarding/account-status. All three were added in #672 (Jan 2026), and the
onboarding flow has since been rebuilt more than once — #904 (remove unused old onboarding flow), #1067, #1178 — which looks like it dropped the callers and left the
routes in place.

I have only grepped this repository, so I cannot rule out a caller outside the monorepo,
which is why this PR hardens rather than removes. If these routes are in fact dead,
deleting all three would be the better fix — it removes a network-reachable surface that
spends money against Exa and xAI — and I am happy to open that PR instead.

Testing

biome check passes on the changed file. apps/web has no test runner configured (no
vitest dependency, no test script), so no tests were added.

  The urls array was forwarded to Exa's billed live-crawl endpoint with no
  length cap, no parsing, and no scheme check, so one request carrying
  thousands of URLs (or the same URL repeated) became a proportionally
  large invoice.

  Cap the batch at 20, parse each entry with new URL(), require http/https,
  and de-duplicate on the normalized href.
)
}

let parsed: URL

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The Variables and constants rule states: use const by default and use let only when reassignment is needed. The variable parsed is declared with let but is only ever assigned once (inside the try block) and never reassigned. It should be declared with const: const parsed: URL = new URL(url.trim()) (and the try/catch restructured accordingly, e.g. by moving the assignment inside the try block as a const).

Spotted by Graphite (based on custom rule: TypeScript style guide (Google))

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

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