Masking & Streaming Enforcement
Detected sensitive spans have to be redacted inline, in a live byte stream, without buffering an entire command's output and without corrupting whatever structure the surrounding data has (JSON-RPC frames, terminal escape sequences, partial lines). This chapter covers the mechanics of that.
The streaming problem
A naive approach — buffer everything, scan once, emit — defeats the purpose of a terminal or an MCP transport: output has to appear as it's produced. But a secret can straddle the boundary between two write calls (half of an API key in one chunk, half in the next), so the engine cannot simply scan-and-emit each chunk independently either.
%%{init: {"theme": "base", "themeVariables": {"fontFamily": "-apple-system, Segoe UI, Helvetica, Arial, sans-serif", "primaryColor": "#eef2f7", "primaryTextColor": "#0b0b0b", "primaryBorderColor": "#5b6b7c", "lineColor": "#5b6b7c", "secondaryColor": "#eef4ff", "tertiaryColor": "#f4f3f0", "clusterBkg": "#f9f9f7", "clusterBorder": "#c3c2b7", "edgeLabelBackground": "#fcfcfb", "actorBkg": "#eef2f7", "actorBorder": "#5b6b7c", "actorTextColor": "#0b0b0b", "actorLineColor": "#5b6b7c", "signalColor": "#52514e", "signalTextColor": "#0b0b0b", "labelBoxBkgColor": "#eef4ff", "labelBoxBorderColor": "#5b6b7c", "labelTextColor": "#0b0b0b", "loopTextColor": "#0b0b0b", "noteBkgColor": "#fdf3d9", "noteBorderColor": "#c98500", "noteTextColor": "#3d2f00", "activationBorderColor": "#5b6b7c", "activationBkgColor": "#eef4ff", "sequenceNumberColor": "#ffffff"}}}%%
flowchart LR
In["Incoming bytes\n(command output / JSON-RPC)"] --> Pending["Pending buffer\n(max 64 KB)"]
Pending --> Scan["Scan for findings,\nsorted by offset"]
Scan --> Straddle{"A finding straddles\nthe write boundary?"}
Straddle -- yes --> Retain["Hold back the tail\n(≥ longest matchable span,\ndefault 16 KB)"]
Straddle -- no --> Emit["Emit safely up to\nthe safe boundary"]
Retain --> Idle{"Idle for 20 ms\nwith no new data?"}
Idle -- yes --> ForceEmit["Force-flush\n(don't withhold short values\nlike interactive prompts)"]
Idle -- no --> Pending
Mechanics and constants
- Offset-based substitution, single pass, findings sorted by position; where findings overlap, the longer match wins. An empty placeholder leaves the underlying region unchanged (a no-op substitution).
- Retained tail: bytes held back at the safe-emit boundary, sized to at least the longest matchable span the active detector set can produce (default 16 KB) — this is what prevents a secret split across two writes from slipping through half-masked.
- Max pending buffer: 64 KB. Under memory pressure, the buffer is cut at the end of a straddling finding — never mid-value — so bounding memory usage never costs a disclosure; it costs, at worst, an earlier forced flush.
- Idle flush: 20 ms. Without this, a short value (an interactive prompt, a shell echo) that never reaches the tail-size threshold on its own would be withheld indefinitely. This is a deliberate trade-off in favor of terminal responsiveness.
- Flush drains everything — a final/explicit flush has no retained tail left over.
- Placeholders are substituted inline — a masked PEM block renders as
-----[MASKED:PRIVATE_KEY]-----, preserving the surrounding structure.
Performance
- A synthetic-detector benchmark (
BenchmarkStreamingWriter100MB, a repeatable Go benchmark) measures throughput around 1.5 GB/s for the masking mechanism itself on the hardware it was run on.
Continue to Policy Engine & Trust Chain.