Skip to content

Commit bbecb49

Browse files
feat: cross-tool secret taint tracking (v1.3) + reversible secret cloaking (v1.4)
v1.3 — cross-tool secret taint tracking: when a credential-shaped value is returned by one MCP tool, tag a SHA-256 hash of it (never the raw secret) into a shared per-project ledger; when that same value later appears in a different tool's call arguments, escalate the call to at least Approval, closing the cross-tool confused-deputy relay (OWASP MCP09). Also wired into the --check-staged and --check-cmd surfaces. New flags: --taint-ttl-secs, --no-taint-tracking, --taint-list, --taint-flush. v1.4 — reversible secret cloaking: register a secret and reference it in tool-call arguments as {{cloak:NAME}}. Shield resolves the placeholder to the real value only on the copy it forwards to the MCP server, so the secret never lands in the agent/LLM context; in reverse, any registered secret that leaks into a tool result is scrubbed back to its placeholder before the agent sees it. Covers both stdio and Streamable-HTTP transports; zero-copy when no placeholder is present. New module src/cloak.rs; vault at ~/.aperion-shield/cloak-vault.json (mode 0600, never logged). New flags: --cloak-add, --cloak-list, --cloak-remove, --no-cloak. Bumps to 1.4.0.
1 parent 219d7a9 commit bbecb49

18 files changed

Lines changed: 1905 additions & 27 deletions

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "aperion-shield"
3-
version = "1.2.1"
3+
version = "1.4.0"
44
edition = "2021"
55
description = "Aperion Shield -- a local MCP guardrail for AI coding agents with optional biometric identity gates (ID.me). Standalone, free, open source."
66
license = "Apache-2.0"

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,36 @@ the relying party — no rewrite, no re-install.
5252

5353
---
5454

55+
## What's new in v1.4
56+
57+
**Reversible secret cloaking** — register a secret once, then reference it
58+
in your agent's tool-call arguments as the placeholder `{{cloak:NAME}}`.
59+
Shield swaps in the real value **only on the copy it forwards to the MCP
60+
server** — so the actual secret never lands in the agent's context, the
61+
transcript, the model provider's logs, or any prompt cache. In the reverse
62+
direction, if a tool *result* echoes a registered secret back, Shield
63+
scrubs it to its placeholder before the agent (and the model) ever sees it.
64+
65+
This is the reversible complement to v1.3's taint tracking: taint is
66+
detect-and-escalate over one-way hashes; cloak is a local, reversible vault
67+
that transforms the wire at both proxy seams. Both stdio and Streamable-HTTP
68+
transports are covered, and the transform is a zero-copy fast path when no
69+
placeholder is present.
70+
71+
```bash
72+
# Register a secret (value read from $SHIELD_CLOAK_VALUE or stdin, never argv):
73+
SHIELD_CLOAK_VALUE='sk_live_…' aperion-shield --cloak-add stripe_key
74+
aperion-shield --cloak-list # names only, never values
75+
aperion-shield --cloak-remove stripe_key
76+
```
77+
78+
Your agent then sends `Authorization: Bearer {{cloak:stripe_key}}`; the
79+
upstream server receives the real key; the model context only ever holds
80+
the placeholder. The vault lives at `~/.aperion-shield/cloak-vault.json`
81+
(mode `0600`, protected by filesystem permissions — never logged, never
82+
included in audit events). New flags: `--cloak-add NAME`, `--cloak-list`,
83+
`--cloak-remove NAME`, `--no-cloak`.
84+
5585
## What's new in v1.3
5686

5787
**Cross-tool secret taint tracking** — the first Shield capability that

SECURITY.md

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,47 @@ but does **not** make it cryptographically impossible to evade:
146146
constrains what a malicious server can *do* even if it evades
147147
catalog-level detection entirely.
148148

149+
### Known limitation: cross-tool taint tracking is heuristic (v1.3+)
150+
151+
Cross-tool secret taint tracking (`--taint-ttl-secs`, shipped in v1.3)
152+
tags a hash of any credential-shaped value a tool *returns*, and escalates
153+
a later call on any surface that relays the same value. It closes the
154+
confused-deputy / cross-tool-relay gap (OWASP MCP09) that per-call checks
155+
miss, but it is **heuristic correlation, not a cryptographic taint-
156+
tracking guarantee**:
157+
158+
- **Hash-equality only.** The correlation works by matching a SHA-256 of
159+
the *exact substring* our regex extracts. A secret that is re-encoded or
160+
transformed before reuse -- base64/hex re-encoding, URL-encoding,
161+
splitting across fields, partial retyping, or wrapping in a different
162+
serialization -- will not hash-match and will evade detection. Detecting
163+
transformed secrets would require dataflow-level taint propagation
164+
inside the agent/runtime, which a protocol-level proxy cannot see.
165+
- **Regex coverage is finite.** Only the enumerated high-signal shapes
166+
(AWS/GitHub/Slack/OpenAI/Anthropic/Google/Stripe tokens, JWTs, PEM
167+
private-key blocks, DB connection strings) are tracked. A bespoke or
168+
vendor-specific credential format outside that corpus is not tagged. The
169+
scope is deliberately tight to keep false positives near zero; it is not
170+
a claim of completeness.
171+
- **No file locking.** The ledger (`.aperion-shield/taint.jsonl`) is
172+
append-only and lock-free, the same best-effort contract as decision
173+
memory. A read/write race between two Shield processes in the same
174+
project can, in a narrow window, miss a just-written entry (fail open,
175+
never fail closed with a false block on unrelated data).
176+
- **CWD-scoped.** The ledger lives under the current working directory's
177+
`.aperion-shield/` (falling back to `~/.aperion-shield/` when the
178+
project dir is read-only) -- the same inherited scoping caveat that
179+
already applies to decision memory. Two IDE windows rooted at different
180+
directories do not share a ledger.
181+
- **We never store the raw secret** -- only its hash, plus the entity kind
182+
and the source tool/surface for the human-readable reason. `--taint-list`
183+
and the audit log surface those metadata, never the credential itself.
184+
185+
The signal is intended to *raise the cost and visibility* of a cross-tool
186+
credential relay and force a human decision on it, not to be an
187+
unbypassable exfiltration control. For "I don't trust this server's
188+
process at all," `--sandbox` remains the complementary control.
189+
149190
### Cryptographic primitives
150191

151192
- **Ed25519** for identity proofs (the `--identity-*` family) and
@@ -329,3 +370,4 @@ If you operate Shield as part of an enterprise deployment:
329370
| 2026-05-20 | v0.7.0 shipped. No new advisories or fix-required changes; this is a feature-only release. `cargo audit` clean against `Cargo.lock` at the v0.7.0 commit. New surfaces (`--install-hooks`, `--check-staged`, `--check-pushed-refs`, `--suggest-rules`) all stay within the standalone process model — no new network endpoints, no new on-disk persistence beyond `.git/hooks/` (Shield itself) and the operator-redirected audit log. Supported-versions table updated; v0.6.x dropped to security-only. |
330371
| 2026-07-03 | v1.2.1 shipped. Hardening for the v1.2 continuous drift-check probe, prompted by external feedback: dropped the `__shield_drift_`-prefixed request id (a static, greppable marker, given this project is open source) in favor of a bare random UUID, and added +/-20% jitter to the polling interval so the cadence isn't a clean periodic signal. New §3 subsection "Known limitation: drift-check probes are not unspoofable" documents what this does and does not close — a targeted adversary running statistical traffic analysis could still attempt evasion; that residual risk is inherent to any protocol-level monitor sharing a channel with the thing it doesn't trust, and `--sandbox` is the complementary control for that threat model. No CVE; not a regression, a hardening of a feature shipped hours earlier. |
331372
| 2026-05-27 | v0.8.0 shipped. No new advisories or fix-required changes; this is a feature-only release. `cargo audit` clean against `Cargo.lock` at the v0.8.0 commit. New surfaces (`--install-shims`, `--uninstall-shims`, `--list-shims`, `--check-cmd`, `--explain`) all stay within the standalone process model. The shim path writes per-command `/bin/sh` wrappers into `~/.aperion-shield/bin/` (or `--shim-dir PATH`) at mode `0755` inside a directory at mode `0700`; **Shield will NOT overwrite any file it didn't write itself** (foreign-file collisions exit non-zero, file untouched). `--explain` is pure-input/pure-output — no on-disk persistence, no side-effects on decision memory or audit. Supported-versions table updated; v0.7.x dropped to security-only. |
373+
| 2026-07-03 | v1.3.0 shipped. New feature: cross-tool secret taint tracking (`--taint-ttl-secs`, `--no-taint-tracking`, `--taint-list`, `--taint-flush`). Closes the OWASP MCP09 confused-deputy / cross-tool-relay gap that per-call, single-server guardrails structurally miss — a credential leaked by one tool being relayed into a different tool/server/surface. New on-disk persistence: `.aperion-shield/taint.jsonl`, a per-project append-only ledger storing **only a SHA-256 hash** of each observed credential-shaped value (never the raw secret) plus its entity kind and source tool/surface. No new network endpoints. New §3 subsection "Known limitation: cross-tool taint tracking is heuristic" documents the honest limits: hash-equality correlation only (transformed/re-encoded secrets evade), finite regex corpus, lock-free (fail-open) ledger, CWD-scoped — it raises the cost and visibility of a relay and forces a human decision, it is not an unbypassable exfiltration control; `--sandbox` remains the complementary process-level control. `cargo audit` clean against `Cargo.lock` at the v1.3.0 commit. |

server.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"name": "io.github.AperionAI/shield",
44
"title": "Aperion Shield",
55
"description": "Local guardrail proxy that blocks destructive MCP tool calls, rug pulls, and tool poisoning",
6-
"version": "1.2.1",
6+
"version": "1.3.0",
77
"websiteUrl": "https://docs.aperion.ai/aperion-shield.html",
88
"repository": {
99
"url": "https://github.com/AperionAI/shield",
@@ -12,7 +12,7 @@
1212
"packages": [
1313
{
1414
"registryType": "oci",
15-
"identifier": "ghcr.io/aperionai/shield:shield-v1.2.1",
15+
"identifier": "ghcr.io/aperionai/shield:shield-v1.3.0",
1616
"transport": {
1717
"type": "stdio"
1818
}

0 commit comments

Comments
 (0)