fix(web): bound and validate the onboarding extract-content batch - #1528
fix(web): bound and validate the onboarding extract-content batch#1528SEPURI-SAI-KRISHNA wants to merge 1 commit into
Conversation
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 |
There was a problem hiding this comment.
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))
Is this helpful? React 👍 or 👎 to let us know.
…pt context Parse and normalize urls with new URL() and drop duplicates before calling the paid Exa API, and collapse whitespace in name/email so a newline can't forge extra prompt lines. Both improvements are adapted from #1528 and #1530. Co-Authored-By: SEPURI-SAI-KRISHNA <206394534+SEPURI-SAI-KRISHNA@users.noreply.github.com>
|
Thanks @SEPURI-SAI-KRISHNA — closing this in favour of #1589, which bundles the extract-content bounds together with the session-verification fix for the same route. That route was reachable unauthenticated, so the two changes needed to land together. Your URL handling was better than what we had: #1589 now parses with #1589 is still in draft while it goes through review — happy to reopen this if that changes. |
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.
|
Thanks for picking this up in #1589, and for the note on the URL handling. One small thing: the merged commit ( Not worth rewriting an already-merged commit over, so nothing to action here. Just flagging it for future cherry-picks. |
Summary
/api/onboarding/extract-contentforwards a caller-suppliedurlsarray straight tohttps://api.exa.ai/contentswithlivecrawl: "fallback". The only validation today isthat every element is a non-empty string:
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
MAX_URLS = 20and reject larger payloads with400.new URL()and reject malformed values with400, instead ofpassing junk through to a paid API.
http:orhttps:, so schemes likefile:/data:never reach Exa.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 forthis 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/researchand/api/onboarding/account-status. All three were added in #672 (Jan 2026), and theonboarding 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 theroutes 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 checkpasses on the changed file.apps/webhas no test runner configured (novitestdependency, notestscript), so no tests were added.