T01 · Skill Instruction Hijacking
- Location
stop-hook.sh:25- Finding
Remote Prompt Injection Through the Claude Stop Hook
- Content
View full analysis
/dev/null || true) case "$msgs" in ""|*"inbox empty"*) exit 0 ;; esac # block the stop and hand the message content back to Claude printf '%s' "$msgs" | "$NODE" -e 'let s="";process.stdin.on("data",d=>s+=d).on("end",()=>{process.stdout.write(JSON.stringify({decision:"block",reason:"📨 Incoming Decent-mesh message(s) — reply via `mesh send \"...\"`:\n\n"+s}))})' exit 0 ``` The data returned by `mesh inbox` originates from remote peer messages: ```js peer.onText((msg) => { const key = _djb2(`${msg.pubkey}|${msg.text}`); const t = Date.now(); for (const [k, ts] of _seen) if (t - ts > DEDUP_MS) _seen.delete(k); if (_seen.has(key)) { log(`DEDUP drop ${msg.pubkey}: ${msg.text.slice(0, 48)}`); return; } _seen.set(key, t); _persistSeen(); appendFileSync(P("inbox.jsonl"), JSON.stringify({ ts: now(), from: msg.pubkey, text: msg.text }) + "\n"); log(`RECV ${msg.pubkey}: ${msg.text}`); }); ``` ### Technical Analysis The daemon accepts arbitrary text from a remote peer and stores it in `inbox.jsonl`. The Stop hook subsequently reads that text and concatenates it directly into the `reason` field of a hook response with `decision: "block"`. A hook reason is agent-facing operational context rather than an isolated data-display surface. No trust-boundary marker, content isolation, validation, or confirmation step separates the remote message from the instructions presented to the agent. Consequently, a peer can compose messages that resemble system instructions, tool-use directives, requests to disclose data, or commands to disregard the user's current objective. The hook also prevents the current turn from stopping. This gi ...[truncated 1617 chars]- Remediation
View remediation
