MCP interceptor
The MCP context interceptor is a middleware layer inside ov mcp serve. It scans the results that OpaqueVault’s MCP tools return and blocks any result containing a detected secret pattern before it reaches the AI model.
This is OpaqueVault’s second line of defense — it catches secrets that leak back through tool output the vault itself can’t control.
Why it exists
Section titled “Why it exists”The vault prevents secrets from being requested by an AI. The interceptor catches the case where a secret ends up in a tool result anyway — most often in untrusted text that a tool embeds in its response, such as an error message relayed from the agent daemon.
It is the second of two layers, and the first one does the heavy lifting for vault_run:
- Structural omission (the primary defense).
vault_runnever returns subprocess output. The bridge deliberately does not read stdout or stderr into the response at all — the payload carriesexit_code,redacted: true, andredacted_fields: ["stdout", "stderr"], and that key set is frozen by test. Even a daemon that still sent output could not get it into the agent-visible payload. Output length is treated as an exfiltration channel too, so no byte or line counts are returned either. You read the output on the daemon terminal or in the agent log. - The interceptor (this page). A pattern-matching backstop over every result the MCP tools do return, so that a credential which slips into a payload some other way is caught before Claude sees it.
What it detects
Section titled “What it detects”The same detection engine as ov scan:
| Pattern | Example |
|---|---|
| AWS access keys | AKIA[0-9A-Z]{16} |
| AWS secret keys | 40-char base64 secret in an AWS assignment context |
| GitHub tokens | ghp_, github_pat_ |
| Stripe keys | sk_live_, sk_test_ |
| Private keys | PEM headers |
| JWT tokens | eyJ... three-part base64url |
| Connection strings | postgres://user:pass@host |
| High-entropy strings | Shannon entropy above a length-banded threshold — 4.5 bits/char at 20–31 chars, rising to 4.8 at 64+ |
How it works
Section titled “How it works” ov mcp serve │ tool result ────────────▶ [interceptor scans] ────clean────▶ Claude │ ↓ match found result dropped, Claude receives: "⚠ OpaqueVault intercepted a potential secret in this output (…). The value was blocked from entering your context. Instead … use: vault_create_secret · vault_run"The interceptor covers two directions, and they do different things:
- Outbound (the detection path). Every result OpaqueVault’s MCP tools return to Claude is scanned by the detection engine above. This is the path that catches secrets in command output.
- Inbound (an exfiltration speed bump).
vault_runcalls only. The requested command is checked against a deny-list of obvious exfiltration patterns —printenv,env,nc,/proc/self/environ,bash -c "declare -p", redirecting an environment variable to a file. This is a deny-list, not a secret scan, and it is explicitly a speed bump rather than a boundary: a renamed binary or an alternate interpreter defeats it.
In block mode (the default) the entire result is scanned, whatever its size. In redact mode, results larger than 512 KB are scanned only in their first 512 KB — a secret buried past that offset in a single response is redacted only if the same value also appears in the scanned prefix.
BLOCK (default)
Section titled “BLOCK (default)”OV_INTERCEPT_MODE=block ov mcp serveThe entire tool result is dropped and a fixed error string is returned to the MCP client in its place. Claude sees the error, never any part of the output. vault_status increments intercepted_count.
REDACT
Section titled “REDACT”OV_INTERCEPT_MODE=redact ov mcp serveEach detected value is replaced with [REDACTED:detector-name] and the surrounding content passes through, so Claude keeps the non-secret context of a command’s output.
Any unrecognized value of OV_INTERCEPT_MODE — including an empty one — falls back to BLOCK. There is no mode that disables the interceptor.
Checking interceptor activity
Section titled “Checking interceptor activity”vault_status()→ { "intercepted_count": 2, "intercept_mode": "block", ... }If intercepted_count is non-zero, a secret pattern reached a tool result and was stopped. ov mcp serve also writes a diagnostic line naming the matched detectors to its own stderr — it never logs the matched value.
A non-zero count is worth investigating at the source: it means a credential reached a response payload, which the structural layer above is designed to make unnecessary.