Agent Sandbox Policy-Escape Replay
Validator-backed public refactor of source agent_execution_trace for sandbox/security: metadata-only trace spans, pre-exec policy verdicts, side-effect diffs
The write-up
Every sandbox action is replayed against policy
The validator replays a synthetic agent's risky sandbox actions and recomputes, per action, whether policy should have blocked it, what side effect it left, and whether that effect was rolled back. agent_sandbox_policy_escape_replay is a validator. It reads a synthetic record of an agent attempting risky actions inside a sandbox and recomputes, action by action, whether a safety policy should have blocked the action before it ran, what side effect it left if it ran, and whether that side effect was rolled back and could be re-checked from cold.
Local fixtures record blocked and executed attempts
Inputs are local JSON files with no live agent, secret, or network; blocked attempts are still traced as never-executed, and the output is a metadata-only record plus a public trace. The input is a set of local JSON files. There is no live agent, no real secret, and no network. Each blocked attempt is still recorded as a traced step, marked as never executed with a zero side-effect count. The output is a metadata-only result record plus a public trace of the same steps.
The replay asks three containment questions
It answers one narrow question over synthetic requests: was every policy decision recorded before execution, did blocks leave no side effect, and does each executed effect carry a diff and rollback. The component answers one narrow question: for a fixed set of synthetic requests, was every policy decision recorded before execution, did blocked actions leave no side effect, and does every executed side effect carry a diff and a rollback record. It does not run anything in a real host.
Purpose
Containment claims without checkable records
Containment claims are easy to assert, and a loose validator that reads the verdict row, sees block, and passes is only trusting the label it was handed. Sandbox and containment claims are easy to assert and hard to show. A system can say it blocked an untrusted action without ever producing the record that would make the statement checkable. A loose validator would read the verdict row, see the word block, and pass. That trusts the label it was handed.
Deriving the verdict from request shape
Instead it derives each expected verdict from the request's own shape, fails closed to block on any unrecognised shape, and holds the side-effect check to that derived verdict. This component does not trust the verdict. For each request it derives the expected verdict from the request's own shape: its action kind, requested capability, risk class, and source trust label. A declared allow on a request whose shape derives to block is a finding, not a pass. Any shape the policy table does not recognise fails closed to block. The same rule drives the side-effect check: a request whose shape requires a block must show no execution and a zero diff count regardless of what the verdict row claims.
How it works
Two entry points over one pipeline
run validates the first-wave fixture with eight negative cases and run_sandbox_bundle validates an exported bundle with a manifest; both call _build_result, then write result records. Two entry points share one pipeline. run validates the first-wave fixture with the eight negative-case files included. run_sandbox_bundle validates an exported bundle without the negative files but with a required source-module manifest. Both call _build_result, which is the whole mechanism, then _write_receipts (or a bundle writer) records the outcome.
Ordered pipeline inside _build_result
_build_result runs in order: _load_payloads strict-reads the seven input files, scan_paths blocks on any forbidden-class hit, trace spans are built, then each validator recomputes one layer. _build_result runs these steps in order. _load_payloads reads the seven input files (projection_protocol, sandbox_policy, action_requests, policy_verdicts, side_effect_receipts, rollback_receipts, cold_replay) with strict JSON parsing. scan_paths checks those files and any copied source bodies against core/private_state_forbidden_classes.json and blocks on any hit. build_public_sandbox_policy_trace builds the metadata-only trace spans. Then each validator recomputes one layer.
The fail-closed policy core
_derived_sandbox_policy_verdict looks up risk_class in a fixed table, checks the shape matches, and returns block on any mismatch, then validate_policy_verdicts flags rows that disagree or lack pre_execution. The policy core is _derived_sandbox_policy_verdict. It looks up the request's risk_class in a fixed table (EXPECTED_ACTION_POLICY) and confirms the action kind, capability, and trust label match the expected shape (_policy_shape_mismatches). On a match it returns the table's verdict and rule ref. On any mismatch, or an unknown risk class, it returns FAIL_CLOSED_POLICY_SEMANTICS, which is block. validate_policy_verdicts joins each verdict row to its request, then compares the declared verdict against this derived verdict. A row that does not match, or that lacks pre_execution: true, is a finding.
Side-effect, rollback, replay, and manifest checks
Blocked verdicts must show no execution and zero diff, allowed ones an executed diff, side-effecting actions a verified rollback, replay rows a reproduced state, and manifest bodies a digest match. validate_side_effect_receipts enforces the mechanical consequence. When the derived verdict is block, the result record must show execution_attempted: false and a diff count of zero. When it is allow or review, the result record must show an executed diff of at least one. validate_rollback_receipts requires a verified rollback record for each side-effecting action and needs at least two. validate_cold_replay requires each replay row to reproduce both the verdict and the side-effect state. _source_module_manifest_result checks the seven copied source bodies for digest match, a material class, declared anchors, and metadata-only result record fields.
When the whole run passes
The run passes only when no negative case is missing, the secret scan is clean, every layer validator passes, and any manifest passes; result_card projects a metadata-only card. _build_result passes only when no negative case is missing, the secret scan has zero blocking hits, every layer validator returns pass, and (for a bundle) the manifest passes. result_card projects the result into a compact public card that carries refs, counts, and verdicts, never bodies.
| Function | Role |
|---|---|
run | Fixture entry point, negative cases included |
run_sandbox_bundle | Exported-bundle entry point, manifest required |
_build_result | Runs the ordered pipeline and computes final status |
_load_payloads | Strict JSON read of the seven input files |
_derived_sandbox_policy_verdict | Derives the expected verdict, fails closed to block |
validate_policy_verdicts | Compares declared verdicts against derived ones |
validate_side_effect_receipts | Blocked means no execution and zero diff; allowed means executed diff |
validate_rollback_receipts | Requires verified rollback for side-effecting actions |
validate_cold_replay | Requires replay rows to reproduce verdict and side-effect state |
_source_module_manifest_result | Checks the seven copied source bodies for digest and anchors |
_write_receipts | Writes the result, board, validation, and sign-off records |
What the six-request fixture derives to
Six requests derive to four blocks, one allow, and one review, giving six side-effect result records, two rollback records, six cold replay rows, and six metadata-only trace spans. The fixture has six requests. Four derive to block (a secret read from untrusted tool output, a network exfiltration attempt, a destructive delete, and a shell obfuscation payload), one derives to allow (a low-risk public fixture write), and one derives to review (a mock database update). That gives six side-effect result records, four blocked without execution, two verified rollback records, six cold replay rows, and six public trace spans.
Diagram source & refs
flowchart TD requests["six action requests"] derived["derived verdict per request fail-closed to block"] verdicts["declared verdict rows checked vs derived"] effects["side-effect result records block=no diff, allow=diff"] rollback["two verified rollbacks"] replay["six cold replay rows"] trace["six metadata-only trace spans"] result["result record + status"] requests --> derived derived --> verdicts verdicts --> effects effects --> rollback effects --> replay requests --> trace verdicts --> result effects --> result rollback --> result replay --> result trace --> resultNegative cases
Eight escape patterns rejected by name
validate_negative_cases requires eight synthetic files to each raise their declared error code, from real secrets to a security-benchmark claim, and a missing case blocks the run. validate_negative_cases reads eight synthetic files and requires each to raise its declared error code: real_secret_material, live_network_access, raw_environment_export, policy_after_execution, unlogged_side_effect, tool_output_policy_bypass, executable_escape_payload, and security_benchmark_claim. A missing case blocks the run. These files are the refusal surface: real secrets, live network access, raw environment export, a policy decision recorded after execution, an unlogged side effect, a tool-output authority bypass, an executable escape payload, and a broad security-benchmark claim are each rejected by name.
Prior Art Grounding
Least-privilege lineage and agent sandbox benchmarks
The design borrows Saltzer and Schroeder's least-privilege and complete-mediation principles and sandboxed agent benchmarks like AgentDojo and AgentHarm, taking the pattern and not a new exploit technique. The security-control lineage is Saltzer and Schroeder's least-privilege and complete-mediation principles and capability-oriented confused-deputy thinking: check containment before an action, log side effects, keep a separate rollback record. The agent-evaluation lineage is closer to sandboxed tool-use benchmarks such as AgentDojo and misuse evaluations such as AgentHarm, where an agent's requested actions and policy outcomes are scored under controlled conditions. This component borrows the pattern, not a new exploit technique.
Validation Result record Path
Fixture output stays outside the working tree
The first-wave validator runs against the checked-in fixture and writes its result record to a disposable path outside the working tree. Run the first-wave fixture validator and write its record outside the working tree:
Then run the exported bundle validator:
cd microcosm-substrate && PYTHONPATH=src ../repo-python \
-m microcosm_core.organs.agent_sandbox_policy_escape_replay \
run-sandbox-bundle \
--input examples/agent_sandbox_policy_escape_replay/exported_sandbox_policy_escape_bundle \
--out /tmp/agent_sandbox_policy_escape_bundle_receipt \
--card > /tmp/agent_sandbox_policy_escape_bundle_card.json
The focused regression test and corpus projection check are:
cd microcosm-substrate && ../repo-pytest \
tests/test_agent_sandbox_policy_escape_replay.py
./repo-python scripts/build_doctrine_projection.py --check-paper-module-corpus
What a pass certifies here
A pass means the validators recomputed every verdict, side effect, rollback, and replay row from the source files, the secret scan found nothing, and all eight negative cases fired. A pass means the validators recomputed every verdict, side effect, rollback, and replay row from the source files, the secret scan found nothing, and all eight negative cases fired.
Scope boundary
Scope limit
The strongest supported claim
Over the public fixture and bundle, the component recomputes each verdict, enforces no side effect on blocks, requires diffs and rollbacks on executed actions, replays the state, and refuses eight escape patterns. The strongest claim the evidence supports is narrow. Over the public fixture and exported bundle, this component recomputes the derived policy verdict for each request, checks the declared verdict against it, enforces that blocked actions leave no side effect, requires a diff and a verified rollback for executed actions, reproduces the state on cold replay, and refuses eight named escape patterns. The validation result records listed above are the evidence for that claim and nothing wider.
What stays outside the proof boundary
The proof stops at replay consistency over local symbolic refs and establishes no live escape resistance, secret handling, network isolation, or benchmark performance; a pass is bounded evidence an agent is safe. The proof boundary stops at replay consistency over symbolic refs in local files. It does not establish live sandbox escape resistance, live secret handling, live network isolation, host filesystem mutation authority, executable payload export, provider behaviour, security-benchmark performance, source-file changes, or launch. The scope limit is a metadata-only agent-execution trace refactor over a synthetic sandbox-policy fixture. A pass is validator evidence for one contract, bounded evidence that an agent is safe in the world.
Context & evidence
In short Validator-backed public refactor of the source agent_execution_trace system for sandbox/security claims. Computes metadata-only trace spans from action requests, pre-execution policy verdicts, side-effect diff result records, and rollback against negative cases and scope limits.
Scope limit No live sandbox escape, live secret handling, or live network; source-faithful refactored fixtures and metadata-only result records only; not a complete-security or launch claim.
Covers Sandbox-Policy Replay
Source
Source Source module: src/microcosm_core/organs/agent_sandbox_policy_escape_replay.py · Design note · Source registry