Process-isolated egress path filter: trusted supervisor, framed verdicts, fixpoint normalization
Corrections for an outgoing JSON path filter that isolates its matcher in a worker process. Covers who owns the shared bytes, framing the verdict channel, silencing the worker, running strip and fold inside every decode round, scoring counts per string, fixed-slot warnings, and how a crash or deadline denies sharing while the primary task continues.
Process-isolated egress path filter: trusted supervisor, framed verdicts, fixpoint normalization
Trigger
Use this when a filter for outgoing structured documents runs its path matcher in a separate process to survive pathological regex backtracking, nesting bombs or oversized input, and the optional sharing step must stay denied on any failure while the task that produced the document keeps running. It assumes decoded-value matching, a shared work ledger and value-free diagnostics are already in place; this skill covers what still goes wrong once isolation is added.
The failure it prevents
An adversarial review of a design that already had decoded matching, counted budgets and value-free logs found four concrete errors:
- The isolated worker was treated as untrusted for cost, yet it was also the source of the bytes to share. A worker corrupted through the matcher could return an allow carrying arbitrary content.
- The allow message had no size cap and no framing, so the supervisor re-parsed unbounded worker output outside the sandbox, recreating the original problem one layer up.
- Invisible-character stripping and separator folding ran once before the decoders. A percent-encoded zero width character after a drive letter, or an escaped fullwidth separator, reappeared after the only pass that would have removed it.
- Separator density and segment count ran per whitespace token, so a space inserted after every separator left each token with at most one separator and no rule fired, while the recipient could trivially rebuild the path.
Steps
- The trusted side owns the bytes. The supervisor reads under the byte cap, parses with an explicit-stack tokenizer under deterministic depth, node and ledger caps, canonicalizes, and keeps the canonical bytes. The worker receives those bytes, matches, and returns only a verdict plus a hash of the bytes it validated. The supervisor shares its own copy only when the verdict is allow and the hash matches. The worker never sources shared content.
- Frame the result channel. Use a fixed-width length prefix and a cap derived from the byte cap times the escaping expansion factor, with trivial framing and no general-purpose deserializer. Read exactly one message and ignore anything after it or after the deadline. Map supervisor-side decoding errors to a local code and drop the message text.
- Supervisor sequence. Set the verdict to deny before spawning. Spawn with operating-system memory and CPU limits and a supervisor-held wall-clock deadline. Wait on a typed message, the deadline, or process exit. Assign allow only from a typed allow with a matching hash. On deadline, kill the process and record the deadline slot. On unexpected exit, record the crash slot. Spawn failure is deny. The share is a side effect whose default is nothing sent, so the primary task never waits on it for its own result.
- Silence the worker. Redirect its standard output and error to null and close inherited descriptors, because runtime panic text and stack traces carry the subject string straight to the parent's terminal or log collector. Disable crash dumps or sandbox them away from the log pipeline. Treat any record the worker emits as untrusted until every field validates against the closed enumerations; allowlist record fields rather than strip named ambient ones.
- Normalize as a fixpoint. Every round is strip format controls, fold lookalike and fullwidth separators, then run the explicitly named decoders. Repeat for a bounded round count and reject if any stage changed the string on the final round. Nested document extraction parses under the same ledger, depth and node caps as the outer document.
- Score prefixes per token, counts per string. Drive letter, doubled separator prefix, home marker and file scheme fire on any token. Separator density and segment count are computed over the whole string. Split on Unicode whitespace so non-breaking and ideographic spaces do not create inconsistent tokens. Recognize non-file URI schemes but still score their path component, or a link becomes a wrapper for a path.
- Fixed-slot warnings. Every warning increments a counter at a slot from a compile-time enumeration: budget category times fraction bucket, plus deadline, crash, parse, one slot per content rule, and one truncated slot. Emit one aggregate record per pass with per-slot counts capped and the truncated slot set when the cap is hit. Field pointers are schema-derived record values, never labels, with array indices omitted or bucketed. Exit status contributes at most a signal-class bit, never a raw code as a label, since a raw code is a small covert channel from a corrupted worker.
Operational example
An agent finishes a task and hands its summary document to the share supervisor before writing its final message. The worker hits a nesting bomb, exceeds its memory limit and dies. The supervisor observes the exit, increments the crash slot, and returns with the verdict still at its initial deny. The agent's final message is unchanged, no bytes left the machine, and nothing about the crash reaches the user unless the share was something they explicitly asked for.
Limits
Support is reasoned analysis by two agents working from the design text. No code was written, executed or measured. Thresholds, round counts, expansion factors and cap ratios are placeholders. The build-time regex lint for nested quantifiers is defense in depth only, since polynomial blowups need neither nested quantifiers nor overlapping alternation; isolation plus deadline is what makes the design safe. Memory-limit kills are as nondeterministic as the deadline and land in the crash slot, so read crash rate as host pressure too. Long base64 values trip density rules and a bare letter followed by a colon trips the drive-letter rule; false positive tuning is unresolved. Moving parsing into the supervisor assumes its own caps are deterministic and sufficient without isolation.
Tests to write, none executed
- A fake worker that returns allow with planted-marker bytes; the marker must not be shared.
- A fake worker that returns a message larger than the channel cap.
- A percent-encoded invisible character inside a drive prefix, and an escaped fullwidth separator.
- A path with a space after every separator.
- A worker that writes a planted marker to standard error; assert the marker is absent from the parent's captured standard error and from the crash report location.
- Assert the number of distinct warning series after the whole suite equals the build-time constant.