Skip to content

fix(web): bound the free-text context fed into the research prompt - #1530

Closed
SEPURI-SAI-KRISHNA wants to merge 1 commit into
supermemoryai:mainfrom
SEPURI-SAI-KRISHNA:fix/web-bound-research-prompt-context
Closed

fix(web): bound the free-text context fed into the research prompt#1530
SEPURI-SAI-KRISHNA wants to merge 1 commit into
supermemoryai:mainfrom
SEPURI-SAI-KRISHNA:fix/web-bound-research-prompt-context

Conversation

@SEPURI-SAI-KRISHNA

Copy link
Copy Markdown
Contributor

Summary

/api/onboarding/research interpolates the caller-supplied name and email straight
into the prompt sent to grok-4-fast:

const contextParts: string[] = []
if (name) contextParts.push(`Name: ${name}`)
if (email) contextParts.push(`Email: ${email}`)
const userContext =
    contextParts.length > 0
        ? `\n\nAdditional context about the user:\n${contextParts.join("\n")}`
        : ""

ResearchRequest declares both as string, but nothing enforces that at runtime — the
values come from await req.json(). Two consequences:

  • Unbounded length. Neither field is capped, so an arbitrarily large string is billed
    as prompt tokens on a model call that also runs the web_search and x_search tools.
    xUrl is already constrained (the derived handle must match
    /^[A-Za-z0-9_]{1,15}$/); these two fields are the remaining unbounded input.
  • No type guard. A non-string value is template-stringified, so an object arrives in
    the prompt as Name: [object Object].

Changes

Route both fields through a sanitizeContextField helper that:

  • returns "" for anything that is not a string, so only strings reach the prompt;
  • collapses runs of whitespace to a single space;
  • trims and truncates to MAX_CONTEXT_FIELD_LENGTH (200).

The whitespace collapse matters beyond tidiness: each value is meant to occupy one
Name: / Email: line, and an embedded newline would let it forge an additional line of
prompt instead of staying inside its own field.

To be explicit about what this does not do: it bounds cost and keeps each value on
its intended line, but it is not a defence against prompt injection. 200 characters is
ample for an injected instruction, and the fields are free text by design.

Note for reviewers: this route appears to be unused

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
looks true of /api/onboarding/extract-content and /api/onboarding/account-status. All
three were added in #672 (Jan 2026), and onboarding has been rebuilt since — #904
(remove unused old onboarding flow), #1067, #1178 — which appears to have dropped the
callers while leaving the routes.

I have only grepped this repository and cannot rule out a caller outside the monorepo,
which is why this hardens rather than removes. If these routes are dead, deleting all
three would be the better fix, and I am happy to open that PR instead. Same note appears
on #1528, which bounds the sibling extract-content route.

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.

  name and email came straight from req.json() and were interpolated into
  the grok-4-fast prompt with no length cap and no runtime type check, so an
  oversized value was billed as prompt tokens and a non-string arrived as
  [object Object].

  Route both through sanitizeContextField: non-strings become empty, runs of
  whitespace collapse to a single space so a value cannot forge an extra
  prompt line, and the result is trimmed to 200 characters.
MaheshtheDev added a commit that referenced this pull request Aug 23, 2026
…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>
@MaheshtheDev

Copy link
Copy Markdown
Member

Thanks @SEPURI-SAI-KRISHNA — closing this in favour of #1589, which bundles the research-route 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 sanitizeContextField caught something our version missed: we capped length but let newlines through, so a name value could forge extra lines of the prompt. #1589 now collapses whitespace the same way — adapted from this PR, with you credited as co-author on that commit. #1589 also adds a 60s abort signal on generateText.

#1589 is still in draft while it goes through review — happy to reopen this if that changes.

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.
@SEPURI-SAI-KRISHNA

Copy link
Copy Markdown
Contributor Author

Thanks for folding the sanitizeContextField whitespace handling into #1589, and for the 400-on-too-long approach: rejecting explicitly reads better than the silent truncation I had.

Same note as on #1528: the merged commit (3b0fc9c9) doesn't carry a Co-authored-by: trailer, so the credit is prose-only and GitHub doesn't register it. Nothing to action on a merged commit, just flagging it for future cherry-picks.

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