
Steganographic prompt-injection defense for agentic AI using decode-time provenance binding, HMAC authentication, and MCP trust enforcement.
A reproducible proof-of-concept for a steganographic prompt-injection vector that defeats text content scanners, and a defense that contains it at the decode boundary — decode-time provenance binding.
Educational / defensive security research only. All secrets in this repo are fake. Use only on systems you own or are authorized to test.
An attacker emails a victim an ordinary-looking message. A brand logo in the
email signature hides an instruction via steganography (LSB, DCT, or metadata).
The image is visually identical to the original and contains no suspicious text, so
a content scanner sees nothing and the victim never clicks anything. But the
victim's routine inbox-summarization agent processes the signature, a tool in
its pipeline decodes the image, and the hidden instruction enters the agent's
context — where, in a multi-agent MCP pipeline, it is relayed between agents as
trusted peer output and drives a critical tool call. This is cross-modal trust
laundering. The defense, decode-time provenance binding, mints an
HMAC-signed UNTRUSTED label at the instant a concealment-reversing tool runs and
has the MCP layer maintain the provenance lineage itself, so it blocks any
critical tool call whose lineage is untrusted — even when a compromised agent
presents a valid label for an unrelated trusted value. It contains rather than
detects, so it is independent of payload wording and carrier.
Attack success rate (ASR) — 24 payloads (three carriers × eight evasions: plain, benign-worded, Base64, token-split, Cyrillic homoglyph, zero-width, Unicode-confusable, HTML-comment); lower is better. The in-process taint baseline is an information-flow control that marks decode output untrusted but carries no cross-domain cryptographic binding:
| Defense | Leaks | n | ASR |
|---|---|---|---|
| Undefended (no checks) | 24 | 24 | 100% |
| Content scanner (keyword/DLP) | 15 | 24 | 62.5% |
| Regex intent parser | 15 | 24 | 62.5% |
| Data-flow taint (IFC baseline) | 0 | 24 | 0% |
| Decode-time provenance (ours) | 0 | 24 | 0% |
Taint and provenance tie on single-domain ASR; they diverge under adversarial relaying (below), which is the point of the cryptographic binding.
Valid-label substitution — a compromised agent presents a legitimately signed
TRUSTED binding from a benign tool while omitting the UNTRUSTED decode origin
that actually drives the call. The fix is that the MCP layer maintains the lineage
itself and decides on its own ledger, not on the agent-supplied binding:
| Enforcement | Decision |
|---|---|
| Agent-supplied binding only (naive) | allow (leaks) |
| Layer-maintained, dependency omitted | block |
| Layer-maintained, dependency in arguments | block |
| Layer-maintained, clean context | allow |
Component ablation — four components, each defending a distinct attack. Disable
one at a time; substitution is in-domain, forged relay is a forged TRUSTED
label crossing an untrusted relay to a remote verifier that holds only the presented
binding. Crucially, HMAC alone does not stop substitution (the ledger does), and
the ledger alone does not survive a cross-domain relay (the HMAC does):
| Configuration | Honest | Substitution | Forged relay |
|---|---|---|---|
| Full mechanism | blocked | blocked | blocked |
| − decode marking | leaks | leaks | leaks |
| − cryptographic auth (HMAC) | blocked | blocked | leaks |
| − trusted lineage tracking | blocked | leaks | blocked |
| − sink mediation | leaks | leaks | leaks |
Cross-context propagation — taint survives an inter-agent message boundary, not just a tool-return handle. An untrusted decode in Agent A reaches a critical call in Agent B's separate context, and on a mediated channel (which the threat model already requires for inter-agent comms) the taint propagates and B is blocked. A channel the layer doesn't mediate is outside the model by construction — not a leak within scope:
| Channel A → B | B's critical call |
|---|---|
| Mediated inter-agent message (in scope) | block (taint propagated) |
| Unmediated side channel (outside the model) | not covered |
Overhead scaling — MintBinding is ~O(1) (~10–14 µs regardless of depth);
EnforceCritical is linear in the dependency-closure it traverses (≈10 µs at depth
1 → ≈0.66 ms at depth 100 → ≈6.7 ms at depth 1000) and flat (~6–7 µs) in the number
of concurrent tainted contexts. Realistic closures (depth in the tens) stay under
~0.1 ms.
Legitimate-task utility — an audited trusted-promotion path (re-labelling a specific decoded value, e.g. an OCR'd invoice number, under authorization) eliminates false blocks while the attack stays blocked:
| Regime | Completion | False blocks | Attack blocked |
|---|---|---|---|
| No promotion | 2/3 (67%) | 1 | yes |
| Trusted promotion | 3/3 (100%) | 0 | yes |
Decode-set completeness — the registration assumption, stressed directly:
| Decode set | Stego attack | Legitimate critical call |
|---|---|---|
| Correct | blocked | allowed |
| Missing decoder | leaks | allowed |
| Over-broad | blocked | false block |
Label integrity across handoffs — the binding survives honest relaying and resists a compromised worker:
| Relay scenario | Verifier decision |
|---|---|
| Honest relay, 1 / 3 / 5 / 10 hops | verifies; blocked |
| Compromised: rewrite payload, keep HMAC | block (HMAC mismatch) |
Compromised: forge TRUSTED | block (HMAC mismatch) |
| Compromised: strip binding | block (no valid label) |
Overhead — effectively free at the decision point: minting and verifying a binding are ~8–9 µs each (~17 µs combined), versus a ~11 ms decode tool call — under ~0.2% added. Carrier distortion is low (LSB ≈ 76 dB PSNR, metadata lossless; DCT ≈ 18 dB — a distortion metric, not a perceptual-imperceptibility claim).
stego_provenance_defense_poc.ipynb # the full PoC (run top-to-bottom)
requirements.txt # dependencies (mcp pinned <2)
Running the notebook writes all figures, tables, and CSVs to
MyDrive/stego_provenance_poc/{figures,tables,data} in Colab (or
./stego_provenance_poc/ locally). Every figure and table is produced by a cell,
so the whole thing is reproducible from a clean run.
stego_provenance_defense_poc.ipynb in Colab.MyDrive/stego_provenance_poc/{figures,tables,data}.pip install -r requirements.txt
jupyter notebook stego_provenance_defense_poc.ipynb
Outputs land in ./stego_provenance_poc/. A GPU is optional (the agent model is
small); the security result does not depend on the model.
It combines two guarantees that are usually conflated, and it is their conjunction — not the HMAC alone — that is the contribution: