Skip to content

fix(proxy): read every place a tool result carries text - #131

Open
Cedric921 wants to merge 1 commit into
Memnox:mainfrom
Cedric921:fix/read-every-place-a-result-carries-text
Open

Cedric921 wants to merge 1 commit into
Memnox:mainfrom
Cedric921:fix/read-every-place-a-result-carries-text

Conversation

@Cedric921

Copy link
Copy Markdown
Contributor

Closes #9.

What this changes

textOfResult read a content block's own text and nothing else, and frameResult and resultRecordOf are both built on it. An MCP result can carry text in two other places, and an injection in either was invisible:

{ "content": [{ "type": "resource", "resource": { "uri": "file:///notes.md", "text": "..." } }] }
{ "structuredContent": { "summary": "..." } }

So a prompt injection sitting there was never quoted, never reached onInstruction to put the session under suspicion, and was recorded as fewer bytes than the result actually carried. All three are read now. structuredContent is stringified, because the shape is the server's and a value anywhere inside it is what an agent reads.

The framing itself is untouched: it still wraps content in the quotation blocks, and the content still survives whole rather than being stripped.

How it was verified

A new packages/proxy/test/result-guard.test.ts — the issue is right that nothing imported frameResult or resultRecordOf directly before. Twelve cases; the five that matter fail on main:

  • an injection in resource.text, in structuredContent, and in a resource sitting beside an innocent block, each reported as containsInstruction
  • bytes for a result with a resource is greater than for the first block alone
  • a result whose only instruction is inside a resource comes back framed, with the injection still present inside the quotation

And seven that pin what must not change: a resource with no text, a resource_link, ordinary output, structuredContent holding only a number, an empty result and a message with no result at all are all left alone and returned unchanged by frameResult.

Test Files  287 passed (287)
     Tests  8464 passed (8464)

prettier, tsc, vitest and knip all clean, on Node 24.

One limit worth stating

A resource_link carries a URI and no text, so there is nothing to read in it — it is covered by a test as a no-op rather than by logic. If the intent is to treat the URI itself as readable text, say so and I will add it; I did not want to invent that rule here.

Checklist

  • pnpm format && pnpm typecheck && pnpm test && pnpm deadcode all pass
  • Behaviour change ships with a test
  • No any, no magic values, no console.* outside cli-output.ts
  • If this touches the decision path: still deterministic — hasInstructionShape over more of the same text, no model, network or randomness
  • If this changes a verb table: n/a
  • If this changes a command, flag or file it writes: n/a; ledger rows gain a truer byte count and may newly report an instruction, which the changeset names

@Cedric921
Cedric921 requested a review from moise10r as a code owner October 6, 2026 14:54

This branch has not been deployed

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

proxy: instruction-shaped text in embedded resources and structuredContent is never framed

1 participant