Process-isolated egress filter worker: verdict protocol, pipe draining, exit codes, bounded warnings
Checklist for running an egress path filter in an isolated worker process: per job deny first verdict, concurrent pipe draining with length prefixed completeness, one abnormal exit code, wildcarded array indices so warning cardinality stays bounded, and segment rules that do not silently deny everything. Reasoned analysis and adversarial review, not executed tests.
Process-isolated egress filter worker: verdict protocol, pipe draining, exit codes, bounded warnings
Trigger
Use this when an egress privacy filter for outgoing structured documents runs as, or is being moved into, a separate worker process so that hostile depth, size or matcher input cannot take down the primary task. It assumes decoded-value matching, a shared work ledger and value-free diagnostics are already in place; adjacent guidance covers those. Apply it as a checklist while writing the parent to worker protocol and its telemetry.
The failure it prevents
Isolation looks correct in review and still fails in three ways.
- A parent that waits for the worker to exit before reading its output pipe deadlocks the worker on any result larger than the pipe buffer. Every large legitimate payload then becomes a false deny at the deadline.
- A warning cardinality bound stated as code count times schema key count is false once array indices enter the field pointer. A single wide array floods the metric store until a flood cap fires.
- A verdict held in a shared variable rather than per job lets a request rejected as busy overwrite the in-flight job's result.
A fourth failure is quieter. A detection rule that fires on any two segments joined by separators denies nearly all real content, which silently disables sharing rather than protecting it, and nobody notices because deny is the safe-looking outcome.
Protocol
- Commit the primary output before enqueueing the sharing job. The sharing job never feeds back and the primary task never reads the verdict. This is what makes the filter fail open for the user and closed for sharing.
- Create a per-job verdict initialized to deny before spawning. Never share the verdict across jobs.
- Spawn the worker with its own memory limit and stack size. Pass the document over a pipe or a descriptor, never on the command line.
- Drain the worker's output concurrently with waiting for exit. Define a complete result as a length prefix, exactly that many bytes, then a trailer. Do not define completeness as parsed successfully.
- Flip to allow only when all of these hold: exit status zero, trailer present, byte count equal to the prefix. Anything else leaves deny.
- On deadline expiry, kill the worker, reap it, keep deny. Also kill on parent shutdown so no orphan keeps running.
- Record one abnormal-exit code plus the raw signal number or exit status. Do not distinguish out-of-memory from crash by heuristics. Memory limits surface as aborts under one mechanism and as kills under another, so a three-way split misattributes.
- Allow one in-flight worker per host. A second request in that window records a busy code and stays deny. Do not queue without a bound.
- Walk nested encoded documents inside string values with the same explicit worklist as the outer walk. Any real call recursion in the inner parse reintroduces the stack overflow path that isolation was meant to contain, and a depth test on the outer document will not exercise it.
Detection rules that over-deny
A rule shaped as two or more segments joined by separators matches URLs, dates, media types, scoped package names and version control refs. Require a root signal such as a drive letter, a share marker, a rooted separator or a home marker, or require three or more segments with no URL scheme present. Track the deny rate as a health metric and alert on sustained near-total denial, because that state is indistinguishable from a working filter on the dashboard.
Bounded warnings: first detailed, then counted
For each pair of code and field pointer, emit one detailed record per fixed window, then increment a counter that is flushed when the window closes. Codes come from a compile-time enumeration. Pointers come only from schema-known keys, and every array index is replaced by one wildcard token; without that the pointer set is unbounded and the stated cardinality bound is false. Unknown keys report under an unknown-key code on the parent pointer. A per-window flood cap emits one flood record and drops the rest, so the worst case per window is code count times pointer count plus one. Lengths go in power-of-two buckets. Parser messages are never included because parsers quote their input.
Keyed digest caveat
A keyed hash of the rejected value is a confirmation oracle by design. Anyone holding the key can confirm a low-entropy guess such as an account name in a few attempts, and a deterministic digest links equal values across records. If confirming false positives is worth that, rotate the key, include the rule id and pointer in the hash input, and keep the key outside the log store. If it is not worth it, drop the digest. Do not describe the digest as non-invertible.
Tests to write
None of these were executed.
- Deep document: assert the specific depth budget code, not merely that the parent survived. Survival cannot distinguish a proper abort from a worker crash.
- Large valid document whose canonical output exceeds the pipe buffer: assert allow. This fails when draining is sequential.
- Backtracking worst cases: long separator runs, percent signs followed by non-hex, and backslash-u prefixes. A megabyte of one repeated character is not the worst case.
- Wide array of benign strings: assert the number of distinct metric series stays at the closed bound.
- Benign controls containing URLs, dates and media types must be allowed.
- Keep wall-clock assertions out of continuous integration. Assert on consumed ledger units instead.
Limits
Support is reasoned analysis and one independent adversarial review of the design text. Nothing was executed, measured or inspected in a running system. Pipe buffer sizes, window lengths and segment thresholds are placeholders. A path split across sibling values still passes per-value detection; a per-field grammar or an array join heuristic is the mitigation and is covered by adjacent guidance rather than here.