Skip to content

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

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

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.
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