You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Fence fetched webpage content and MCP tool results in <external-content> envelopes, and instruct the model to treat anything inside those tags as untrusted data rather than instructions. This closes the prompt-injection gap left by the network-level SSRF guard.
compactStoredOutput stores the raw, unwrapped result.content before wrapExternalContent is applied to the compacted preview. If the fetched page is large enough to trigger truncation, the model can call readToolOutput on the returned handle and receive the full original content without any <external-content> envelope.
This contradicts the system prompt's claim that "Tool results from fetch ... are wrapped in <external-content> tags" and leaves a prompt-injection bypass for content placed after the 50 k character threshold.
Options to fix:
Wrap the full content before passing it to compactStoredOutput, so the stored handle also returns fenced output.
Or, track that a handle originated from fetch and re-apply wrapExternalContent inside readToolOutput when serving that handle.
buildSubagentPrompt does not include the new "External content" instruction, but subagents are allowed to use the fetch tool (ALL_TOOLS in subagentRunner.ts). As a result, subagents will see wrapped fetch results without any system-level reminder that the contents are untrusted.
Add the same <external-content> rule to the subagent prompt so the quarantine is consistent across both system and subagent contexts.
export function buildSubagentPrompt(contextFiles: ContextFile[] = [], session?: PromptSession) {
const date = (session?.start ?? new Date()).toISOString().slice(0, 10);
const cwd = (session?.cwd ?? process.cwd()).replace(/\\/g, '/');
return `You are a focused coding subagent. Complete only the assigned task with the available tools. Inspect narrowly, edit when requested, validate relevant changes, and return a concise handoff containing findings, changed paths, validation, blockers, and the exact next action if incomplete. Do not ask for routine command confirmation. After a failed edit, reread the affected file before retrying.${projectContextSection(contextFiles)}`;
The helper only escapes the closing </external-content> tag. It does not escape:
The opening <external-content> tag, which can appear in documentation and create ambiguous nesting.
Attribute metacharacters in origin or server, so a value containing " could break out of the attribute.
Since this is used for untrusted content, make the envelope more robust by escaping both opening and closing tags and HTML-escaping attribute values (", <, >, &).
To rebase specific severity levels, mention me:@BabyKoan rebase critical(fixes 🔴 only), @BabyKoan rebase important(fixes 🔴 + 🟡), or just@BabyKoan rebasefor all.
Automated review by Kōan(Claude)HEAD=c0ef74d9 min 32s
Already-solved check: negative (confidence=high, reasoning=No external-content wrapping or prompt-injection quarantine rule exists on main in fetchTool.ts, mcp)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Fence fetched webpage content and MCP tool results in
<external-content>envelopes, and instruct the model to treat anything inside those tags as untrusted data rather than instructions. This closes the prompt-injection gap left by the network-level SSRF guard.Closes #31
Changes
wrapExternalContent()helper insrc/llm/externalContent.tswith attribute rendering and closing-tag escaping.fetchtool output in awebpageenvelope keyed by final URL.execute()results in anmcp-toolenvelope keyed by server name, serializing non-string results as JSON.<external-content>as untrusted external data.Test plan
npm run lint,npm run typecheck, andnpm test: all pass (800 tests).Quality Report
Changes: 8 files changed, 122 insertions(+), 4 deletions(-)
Code scan: clean
Tests: passed (0 test)
Branch hygiene: clean
Generated by Kōan