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
TranscriptWriter::append (crates/zeph-subagent/src/transcript.rs:120) serializes the full Message (including parts) verbatim to <task_id>.jsonl with no filtering. Found during review of #6239 (P1 ephemeral persistence strip), which enforces invariant C1/M5 (specs/072-multimodal-mcp-passthrough/spec.md §4) only at Agent::persist_message — the single strip point for the three surfaces spec-072 §4/AC-5 explicitly name (SQLite parts_json, Qdrant embeddings, durable JSONL session log). Subagent transcripts are a structurally separate subsystem, never named by spec-072, and subagents never call Agent::persist_message.
extract_parent_messages (crates/zeph-core/src/agent/subagent_commands.rs:745) reads live self.msg.messages (unstripped, can contain MessagePart::Image from the existing user-upload path) and passes it as initial_messages into a spawned subagent's AgentLoopArgs.
Currently not exploitable: independently verified (twice, by both the #6239 implementation-critic and code-reviewer) that crates/zeph-subagent/src/agent_loop.rs::init_loop_state extends initial_messages into the in-memory messages Vec for the subagent's own LLM calls, but never loops over and writes them to the transcript file — only the freshly-built task_msg (empty parts) and subsequently turn-generated messages go through append_transcript. A grep of crates/zeph-subagent/src/ for MessagePart::Image returns zero hits — no current code path constructs a new Image part inside a subagent turn.
This becomes a real, reachable leak once #6240 (P2, MCP image emission inside tool-result processing) lands and a subagent's own turn can produce a new MessagePart::Image that flows through append_transcript with no filter.
Spawn a subagent that calls a tool returning an image.
Inspect the subagent's <task_id>.jsonl transcript file — expect raw base64 image bytes present in a parts array.
Expected Behavior
TranscriptWriter::append (or its call sites in agent_loop.rs) should strip MessagePart::Image before writing, mirroring the strip pattern landed in #6239's Agent::persist_message.
Actual Behavior
No filter exists; append serializes whatever Message it's given.
Run /rust-agents:solve-issue on this issue once #6240's implementation shape is known (or independently, applying the same filter pattern as #6239 at the append_transcript/TranscriptWriter::append call sites).
Description
TranscriptWriter::append(crates/zeph-subagent/src/transcript.rs:120) serializes the fullMessage(includingparts) verbatim to<task_id>.jsonlwith no filtering. Found during review of #6239 (P1 ephemeral persistence strip), which enforces invariant C1/M5 (specs/072-multimodal-mcp-passthrough/spec.md§4) only atAgent::persist_message— the single strip point for the three surfaces spec-072 §4/AC-5 explicitly name (SQLiteparts_json, Qdrant embeddings, durable JSONL session log). Subagent transcripts are a structurally separate subsystem, never named by spec-072, and subagents never callAgent::persist_message.extract_parent_messages(crates/zeph-core/src/agent/subagent_commands.rs:745) reads liveself.msg.messages(unstripped, can containMessagePart::Imagefrom the existing user-upload path) and passes it asinitial_messagesinto a spawned subagent'sAgentLoopArgs.Currently not exploitable: independently verified (twice, by both the #6239 implementation-critic and code-reviewer) that
crates/zeph-subagent/src/agent_loop.rs::init_loop_stateextendsinitial_messagesinto the in-memorymessagesVec for the subagent's own LLM calls, but never loops over and writes them to the transcript file — only the freshly-builttask_msg(emptyparts) and subsequently turn-generated messages go throughappend_transcript. A grep ofcrates/zeph-subagent/src/forMessagePart::Imagereturns zero hits — no current code path constructs a newImagepart inside a subagent turn.This becomes a real, reachable leak once #6240 (P2, MCP image emission inside tool-result processing) lands and a subagent's own turn can produce a new
MessagePart::Imagethat flows throughappend_transcriptwith no filter.Reproduction Steps (future, once #6240 lands)
media_passthrough = true, per Implement: P2 MediaSanitizer decode/validate/attach + vision-tier routing for MCP image passthrough #6240/Implement: P3 config/CLI/TUI/migration surface for MCP image passthrough #6241).<task_id>.jsonltranscript file — expect raw base64 image bytes present in apartsarray.Expected Behavior
TranscriptWriter::append(or its call sites inagent_loop.rs) should stripMessagePart::Imagebefore writing, mirroring the strip pattern landed in #6239'sAgent::persist_message.Actual Behavior
No filter exists;
appendserializes whateverMessageit's given.Environment
Related
specs/072-multimodal-mcp-passthrough/spec.md§4 (invariant C1/M5)Suggested next step
Run
/rust-agents:solve-issueon this issue once #6240's implementation shape is known (or independently, applying the same filter pattern as #6239 at theappend_transcript/TranscriptWriter::appendcall sites).