diff --git a/PR_BODY_REPOSITORY_SANDBOX_EGRESS_10.md b/PR_BODY_REPOSITORY_SANDBOX_EGRESS_10.md
new file mode 100644
index 00000000..94b1ea44
--- /dev/null
+++ b/PR_BODY_REPOSITORY_SANDBOX_EGRESS_10.md
@@ -0,0 +1,36 @@
+/claim #10
+
+## Summary
+
+Adds `repository-sandbox-egress-guard/`, a focused Project Repository & Version Control guard for computation-aware reproducibility sandboxes.
+
+This is distinct from existing #10 slices for repository ledgers, branch protection, component-owner approval, semantic tags, Git LFS pointer integrity, notebook output diffs, fork provenance, release signatures, external-reference pinning, citation impact, and export bundle manifests. It focuses only on whether a repository reproducibility pipeline can run, merge, or support a DOI/tagged release without unsafe network egress, secret exposure, privileged execution, host mounts, or workspace-escaping outputs.
+
+## What is included
+
+- Dependency-free Node evaluator.
+- Synthetic repository execution plans.
+- Deterministic `RELEASE`, `REVIEW_MERGE`, and `HOLD_MERGE` decisions.
+- Checks for pinned container images, command hashes, lockfiles, network egress, secrets, privileged/root/host namespace settings, unsafe mounts, resource caps, and output paths.
+- Reviewer artifacts:
+ - `reports/summary.json`
+ - `reports/reviewer-packet.md`
+ - `reports/summary.svg`
+ - `reports/demo.mp4`
+
+## Validation
+
+Locally verified:
+
+```text
+npm.cmd test
+npm.cmd run demo
+npm.cmd run check
+npm.cmd run video
+git diff --check
+confirmed reports/demo.mp4 was generated as a non-empty local artifact
+```
+
+The MP4 demo is an FFmpeg-generated synthetic slate only. It is not a screen recording and contains no desktop capture, credentials, private files, or personal information.
+
+AI-assisted with OpenAI Codex; I reviewed and locally verified the implementation before submitting.
diff --git a/repository-sandbox-egress-guard/README.md b/repository-sandbox-egress-guard/README.md
new file mode 100644
index 00000000..a8e1c23f
--- /dev/null
+++ b/repository-sandbox-egress-guard/README.md
@@ -0,0 +1,42 @@
+# Repository Sandbox Egress Guard
+
+This contribution adds a focused guard for computation-aware repository execution in SCIBASE's Project Repository & Version Control layer. It checks whether a repository reproducibility run can be executed, reviewed, merged, or tagged without leaking secrets, reaching unapproved network destinations, or mutating hosted artifacts outside the declared workspace.
+
+The slice is intentionally narrow. It does not replace broad repository versioning, file diffs, semantic tags, Git LFS integrity, branch protection, component-owner approvals, notebook replay, release signatures, citation badges, or export-bundle manifests. It covers the execution sandbox boundary for reproducibility pipelines.
+
+## What It Checks
+
+- Public release, DOI, or protected-branch merge is frozen when sandbox findings are unresolved.
+- Reproducibility runs use pinned container images, locked dependency manifests, and hashed commands.
+- Network egress is denied or limited to an explicit allowlist.
+- Secret environment variables, host secret mounts, and credential-like runtime inputs are blocked.
+- Privileged containers, root execution, host PID/IPC sharing, and writable Docker socket mounts are held.
+- Resource caps for CPU, memory, runtime, and artifact size are present.
+- Output paths stay inside declared `results/` or `artifacts/` locations.
+- Generated reviewer packets explain deterministic `RELEASE`, `REVIEW_MERGE`, or `HOLD_MERGE` decisions.
+
+## Running Locally
+
+```bash
+npm test
+npm run demo
+npm run check
+npm run video
+```
+
+The demo uses only synthetic repository execution plans in `data/sample_sandbox_run_plans.json`.
+
+Generated files:
+
+- `reports/summary.json`
+- `reports/reviewer-packet.md`
+- `reports/summary.svg`
+- `reports/demo.mp4`
+
+The MP4 is generated from an FFmpeg color source and drawtext slate. It is not a screen recording and contains no desktop capture or private information.
+
+## Why This Matters
+
+SCIBASE repository releases are credible only when reproducibility pipelines are repeatable and contained. A notebook or analysis script that can read host secrets, call arbitrary services, or write outside the repository workspace can undermine trust even when the version history looks clean.
+
+This guard gives reviewers a deterministic packet to hold risky execution plans, request sandbox hardening, and keep protected-branch merges or tagged DOI releases frozen until the computation boundary is safe.
diff --git a/repository-sandbox-egress-guard/data/sample_sandbox_run_plans.json b/repository-sandbox-egress-guard/data/sample_sandbox_run_plans.json
new file mode 100644
index 00000000..14bf63f6
--- /dev/null
+++ b/repository-sandbox-egress-guard/data/sample_sandbox_run_plans.json
@@ -0,0 +1,178 @@
+[
+ {
+ "id": "repo-release-001",
+ "title": "Clean climate notebook reproduction",
+ "repository": {
+ "publicReleaseEnabled": true,
+ "protectedBranchMerge": true,
+ "doiTag": "v1.2.0"
+ },
+ "execution": {
+ "pipeline": "notebooks/run_analysis.ipynb",
+ "commandHashPresent": true,
+ "lockfilePresent": true,
+ "runtime": "node18-python311"
+ },
+ "container": {
+ "image": "ghcr.io/scibase/repro-runner",
+ "digest": "sha256:7b8a8f2f9d8a73c7aa11188e9b3188bde11e2f3b21fcf5a8b70c1f949820ed13",
+ "privileged": false,
+ "user": "1000:1000",
+ "hostPid": false,
+ "hostIpc": false,
+ "mounts": [
+ { "source": "workspace", "target": "/workspace", "mode": "ro" },
+ { "source": "results", "target": "/workspace/results", "mode": "rw" }
+ ]
+ },
+ "network": {
+ "mode": "deny",
+ "allowedHosts": []
+ },
+ "secrets": {
+ "env": [],
+ "mounts": []
+ },
+ "resources": {
+ "cpu": "2",
+ "memoryMb": 4096,
+ "timeoutSeconds": 900,
+ "artifactLimitMb": 250
+ },
+ "outputs": {
+ "paths": ["results/figures/summary.png", "results/tables/model_metrics.csv"]
+ }
+ },
+ {
+ "id": "repo-hold-002",
+ "title": "Model benchmark rerun with open egress and host secrets",
+ "repository": {
+ "publicReleaseEnabled": true,
+ "protectedBranchMerge": true,
+ "doiTag": "preprint-v3"
+ },
+ "execution": {
+ "pipeline": "notebooks/benchmark.ipynb",
+ "commandHashPresent": false,
+ "lockfilePresent": false,
+ "runtime": "python311"
+ },
+ "container": {
+ "image": "python:latest",
+ "digest": "",
+ "privileged": true,
+ "user": "root",
+ "hostPid": true,
+ "hostIpc": false,
+ "mounts": [
+ { "source": "/var/run/docker.sock", "target": "/var/run/docker.sock", "mode": "rw" },
+ { "source": "workspace", "target": "/workspace", "mode": "rw" }
+ ]
+ },
+ "network": {
+ "mode": "open",
+ "allowedHosts": ["*"]
+ },
+ "secrets": {
+ "env": ["AWS_SECRET_ACCESS_KEY", "SCIBASE_API_TOKEN"],
+ "mounts": ["/home/researcher/.ssh"]
+ },
+ "resources": {
+ "cpu": "",
+ "memoryMb": 0,
+ "timeoutSeconds": 0,
+ "artifactLimitMb": 0
+ },
+ "outputs": {
+ "paths": ["../published/model.pkl", "/tmp/raw-dump.csv"]
+ }
+ },
+ {
+ "id": "repo-review-003",
+ "title": "Materials simulation with approved package mirror",
+ "repository": {
+ "publicReleaseEnabled": false,
+ "protectedBranchMerge": false,
+ "doiTag": ""
+ },
+ "execution": {
+ "pipeline": "code/run_simulation.py",
+ "commandHashPresent": true,
+ "lockfilePresent": false,
+ "runtime": "python311"
+ },
+ "container": {
+ "image": "ghcr.io/scibase/materials-runner",
+ "digest": "sha256:2d9f0d60c20afaf0a724f911ba894c30df83f061f1da7c8119f79a26b985bf91",
+ "privileged": false,
+ "user": "1000:1000",
+ "hostPid": false,
+ "hostIpc": false,
+ "mounts": [
+ { "source": "workspace", "target": "/workspace", "mode": "ro" },
+ { "source": "results", "target": "/workspace/results", "mode": "rw" }
+ ]
+ },
+ "network": {
+ "mode": "allowlist",
+ "allowedHosts": ["packages.scibase.example"]
+ },
+ "secrets": {
+ "env": [],
+ "mounts": []
+ },
+ "resources": {
+ "cpu": "4",
+ "memoryMb": 8192,
+ "timeoutSeconds": 1800,
+ "artifactLimitMb": 500
+ },
+ "outputs": {
+ "paths": ["results/simulation/report.json"]
+ }
+ },
+ {
+ "id": "repo-hold-004",
+ "title": "Private dataset release with writable home mount",
+ "repository": {
+ "publicReleaseEnabled": false,
+ "protectedBranchMerge": true,
+ "doiTag": ""
+ },
+ "execution": {
+ "pipeline": "code/reproduce.sh",
+ "commandHashPresent": true,
+ "lockfilePresent": true,
+ "runtime": "conda"
+ },
+ "container": {
+ "image": "ghcr.io/scibase/conda-runner",
+ "digest": "sha256:a14d0ea51f321418f1b7b4957677f0baaa18b6eab0e41a75032c5f0ee9913e58",
+ "privileged": false,
+ "user": "root",
+ "hostPid": false,
+ "hostIpc": true,
+ "mounts": [
+ { "source": "/home/researcher", "target": "/host-home", "mode": "rw" },
+ { "source": "workspace", "target": "/workspace", "mode": "rw" }
+ ]
+ },
+ "network": {
+ "mode": "deny",
+ "allowedHosts": []
+ },
+ "secrets": {
+ "env": ["DATABASE_URL"],
+ "mounts": []
+ },
+ "resources": {
+ "cpu": "2",
+ "memoryMb": 4096,
+ "timeoutSeconds": 1200,
+ "artifactLimitMb": 200
+ },
+ "outputs": {
+ "paths": ["results/private-summary.csv"]
+ }
+ }
+]
diff --git a/repository-sandbox-egress-guard/package.json b/repository-sandbox-egress-guard/package.json
new file mode 100644
index 00000000..4ec32d3d
--- /dev/null
+++ b/repository-sandbox-egress-guard/package.json
@@ -0,0 +1,13 @@
+{
+ "name": "repository-sandbox-egress-guard",
+ "version": "1.0.0",
+ "description": "Dependency-free guard for computation-aware repository execution sandboxes.",
+ "main": "src/index.js",
+ "scripts": {
+ "test": "node --test",
+ "demo": "node scripts/demo.js",
+ "video": "node scripts/render-demo-video.js",
+ "check": "node --check src/index.js && node --check scripts/demo.js && node --check scripts/render-demo-video.js"
+ },
+ "license": "MIT"
+}
diff --git a/repository-sandbox-egress-guard/reports/demo.mp4 b/repository-sandbox-egress-guard/reports/demo.mp4
new file mode 100644
index 00000000..d0bfe2af
Binary files /dev/null and b/repository-sandbox-egress-guard/reports/demo.mp4 differ
diff --git a/repository-sandbox-egress-guard/reports/reviewer-packet.md b/repository-sandbox-egress-guard/reports/reviewer-packet.md
new file mode 100644
index 00000000..8908497c
--- /dev/null
+++ b/repository-sandbox-egress-guard/reports/reviewer-packet.md
@@ -0,0 +1,68 @@
+# Repository Sandbox Egress Guard Report
+
+Generated: 2026-08-16T14:57:25.834Z
+Execution plans analyzed: 4
+Decision counts: RELEASE 1, REVIEW_MERGE 1, HOLD_MERGE 2
+
+## Findings
+
+### repo-release-001: Clean climate notebook reproduction
+
+Decision: RELEASE
+Pipeline: notebooks/run_analysis.ipynb
+
+- No sandbox egress or containment risk detected.
+
+Recommended actions:
+- Allow repository execution, merge, and tagged release with normal provenance.
+
+### repo-hold-002: Model benchmark rerun with open egress and host secrets
+
+Decision: HOLD_MERGE
+Pipeline: notebooks/benchmark.ipynb
+
+- MAJOR CONTAINER_IMAGE_NOT_PINNED: The execution container is not pinned by a content digest.
+- MAJOR EXECUTION_PROVENANCE_INCOMPLETE: The command hash or dependency lockfile evidence is missing.
+- CRITICAL SANDBOX_NETWORK_EGRESS_OPEN: The reproducibility run allows open or wildcard network egress.
+- CRITICAL SECRET_ENVIRONMENT_EXPOSED: Secret-like environment variables are available to the repository execution sandbox.
+- CRITICAL SECRET_MOUNT_EXPOSED: Host secret paths are mounted into the repository execution sandbox.
+- CRITICAL PRIVILEGED_SANDBOX_RUNTIME: The sandbox uses privileged, root, host PID, or host IPC execution settings.
+- CRITICAL UNSAFE_HOST_OR_WRITE_MOUNT: The sandbox has unsafe host mounts or writable paths outside declared artifact locations.
+- MAJOR RESOURCE_CAPS_MISSING: The execution plan lacks one or more resource caps.
+- CRITICAL OUTPUT_PATH_ESCAPES_WORKSPACE: One or more output paths escape declared results or artifacts directories.
+- CRITICAL REPOSITORY_RELEASE_NOT_FROZEN_FOR_SANDBOX_RISK: Repository merge or public release remains enabled while sandbox findings are unresolved.
+
+Recommended actions:
+- Freeze protected-branch merge, DOI tagging, and public reproducibility badges.
+- Attach sandbox findings to the repository review packet.
+- Re-run the exact reproducibility pipeline after remediation.
+- Preserve reviewer evidence with the tagged release record.
+
+### repo-review-003: Materials simulation with approved package mirror
+
+Decision: REVIEW_MERGE
+Pipeline: code/run_simulation.py
+
+- MAJOR EXECUTION_PROVENANCE_INCOMPLETE: The command hash or dependency lockfile evidence is missing.
+- MAJOR SANDBOX_NETWORK_ALLOWLIST_REVIEW: The reproducibility run uses limited network egress and needs reviewer approval before merge.
+
+Recommended actions:
+- Attach sandbox findings to the repository review packet.
+- Re-run the exact reproducibility pipeline after remediation.
+- Preserve reviewer evidence with the tagged release record.
+
+### repo-hold-004: Private dataset release with writable home mount
+
+Decision: HOLD_MERGE
+Pipeline: code/reproduce.sh
+
+- CRITICAL SECRET_ENVIRONMENT_EXPOSED: Secret-like environment variables are available to the repository execution sandbox.
+- CRITICAL PRIVILEGED_SANDBOX_RUNTIME: The sandbox uses privileged, root, host PID, or host IPC execution settings.
+- CRITICAL UNSAFE_HOST_OR_WRITE_MOUNT: The sandbox has unsafe host mounts or writable paths outside declared artifact locations.
+- CRITICAL REPOSITORY_RELEASE_NOT_FROZEN_FOR_SANDBOX_RISK: Repository merge or public release remains enabled while sandbox findings are unresolved.
+
+Recommended actions:
+- Freeze protected-branch merge, DOI tagging, and public reproducibility badges.
+- Attach sandbox findings to the repository review packet.
+- Re-run the exact reproducibility pipeline after remediation.
+- Preserve reviewer evidence with the tagged release record.
diff --git a/repository-sandbox-egress-guard/reports/summary.json b/repository-sandbox-egress-guard/reports/summary.json
new file mode 100644
index 00000000..41f8977c
--- /dev/null
+++ b/repository-sandbox-egress-guard/reports/summary.json
@@ -0,0 +1,262 @@
+{
+ "generatedAt": "2026-08-16T14:57:25.834Z",
+ "totalPlans": 4,
+ "counts": {
+ "RELEASE": 1,
+ "REVIEW_MERGE": 1,
+ "HOLD_MERGE": 2
+ },
+ "results": [
+ {
+ "id": "repo-release-001",
+ "title": "Clean climate notebook reproduction",
+ "decision": "RELEASE",
+ "pipeline": "notebooks/run_analysis.ipynb",
+ "findings": [],
+ "recommendedActions": [
+ "Allow repository execution, merge, and tagged release with normal provenance."
+ ]
+ },
+ {
+ "id": "repo-hold-002",
+ "title": "Model benchmark rerun with open egress and host secrets",
+ "decision": "HOLD_MERGE",
+ "pipeline": "notebooks/benchmark.ipynb",
+ "findings": [
+ {
+ "code": "CONTAINER_IMAGE_NOT_PINNED",
+ "severity": "major",
+ "message": "The execution container is not pinned by a content digest.",
+ "evidence": {
+ "image": "python:latest",
+ "digest": null
+ },
+ "remediation": "Pin the runner image with a sha256 digest before publishing or merging the reproducibility run."
+ },
+ {
+ "code": "EXECUTION_PROVENANCE_INCOMPLETE",
+ "severity": "major",
+ "message": "The command hash or dependency lockfile evidence is missing.",
+ "evidence": {
+ "commandHashPresent": false,
+ "lockfilePresent": false
+ },
+ "remediation": "Attach hashed commands and dependency lock evidence for the exact pipeline revision."
+ },
+ {
+ "code": "SANDBOX_NETWORK_EGRESS_OPEN",
+ "severity": "critical",
+ "message": "The reproducibility run allows open or wildcard network egress.",
+ "evidence": {
+ "mode": "open",
+ "allowedHosts": [
+ "*"
+ ]
+ },
+ "remediation": "Deny egress by default or replace it with a narrow host allowlist and cached inputs."
+ },
+ {
+ "code": "SECRET_ENVIRONMENT_EXPOSED",
+ "severity": "critical",
+ "message": "Secret-like environment variables are available to the repository execution sandbox.",
+ "evidence": {
+ "env": [
+ "AWS_SECRET_ACCESS_KEY",
+ "SCIBASE_API_TOKEN"
+ ]
+ },
+ "remediation": "Remove secrets from reproducibility runs and replace live credentials with synthetic or read-only fixtures."
+ },
+ {
+ "code": "SECRET_MOUNT_EXPOSED",
+ "severity": "critical",
+ "message": "Host secret paths are mounted into the repository execution sandbox.",
+ "evidence": {
+ "mounts": [
+ "/home/researcher/.ssh"
+ ]
+ },
+ "remediation": "Remove host secret mounts and use declared, non-sensitive repository fixtures."
+ },
+ {
+ "code": "PRIVILEGED_SANDBOX_RUNTIME",
+ "severity": "critical",
+ "message": "The sandbox uses privileged, root, host PID, or host IPC execution settings.",
+ "evidence": {
+ "privileged": true,
+ "user": "root",
+ "hostPid": true,
+ "hostIpc": false
+ },
+ "remediation": "Run the container as a non-root user without privileged mode or host namespace sharing."
+ },
+ {
+ "code": "UNSAFE_HOST_OR_WRITE_MOUNT",
+ "severity": "critical",
+ "message": "The sandbox has unsafe host mounts or writable paths outside declared artifact locations.",
+ "evidence": {
+ "mounts": [
+ {
+ "source": "/var/run/docker.sock",
+ "target": "/var/run/docker.sock",
+ "mode": "rw"
+ },
+ {
+ "source": "workspace",
+ "target": "/workspace",
+ "mode": "rw"
+ }
+ ]
+ },
+ "remediation": "Restrict mounts to read-only workspace inputs and writable results/artifacts directories."
+ },
+ {
+ "code": "RESOURCE_CAPS_MISSING",
+ "severity": "major",
+ "message": "The execution plan lacks one or more resource caps.",
+ "evidence": {
+ "missingCaps": [
+ "cpu",
+ "memoryMb",
+ "timeoutSeconds",
+ "artifactLimitMb"
+ ]
+ },
+ "remediation": "Set CPU, memory, timeout, and artifact-size caps before queueing the run."
+ },
+ {
+ "code": "OUTPUT_PATH_ESCAPES_WORKSPACE",
+ "severity": "critical",
+ "message": "One or more output paths escape declared results or artifacts directories.",
+ "evidence": {
+ "outputs": [
+ "../published/model.pkl",
+ "/tmp/raw-dump.csv"
+ ]
+ },
+ "remediation": "Write outputs only under repository-scoped results/ or artifacts/ paths."
+ },
+ {
+ "code": "REPOSITORY_RELEASE_NOT_FROZEN_FOR_SANDBOX_RISK",
+ "severity": "critical",
+ "message": "Repository merge or public release remains enabled while sandbox findings are unresolved.",
+ "evidence": {
+ "publicReleaseEnabled": true,
+ "protectedBranchMerge": true,
+ "freezeRelease": false
+ },
+ "remediation": "Freeze protected-branch merge, DOI tagging, and public reproducibility badges until sandbox findings are cleared."
+ }
+ ],
+ "recommendedActions": [
+ "Freeze protected-branch merge, DOI tagging, and public reproducibility badges.",
+ "Attach sandbox findings to the repository review packet.",
+ "Re-run the exact reproducibility pipeline after remediation.",
+ "Preserve reviewer evidence with the tagged release record."
+ ]
+ },
+ {
+ "id": "repo-review-003",
+ "title": "Materials simulation with approved package mirror",
+ "decision": "REVIEW_MERGE",
+ "pipeline": "code/run_simulation.py",
+ "findings": [
+ {
+ "code": "EXECUTION_PROVENANCE_INCOMPLETE",
+ "severity": "major",
+ "message": "The command hash or dependency lockfile evidence is missing.",
+ "evidence": {
+ "commandHashPresent": true,
+ "lockfilePresent": false
+ },
+ "remediation": "Attach hashed commands and dependency lock evidence for the exact pipeline revision."
+ },
+ {
+ "code": "SANDBOX_NETWORK_ALLOWLIST_REVIEW",
+ "severity": "major",
+ "message": "The reproducibility run uses limited network egress and needs reviewer approval before merge.",
+ "evidence": {
+ "mode": "allowlist",
+ "allowedHosts": [
+ "packages.scibase.example"
+ ]
+ },
+ "remediation": "Confirm every allowed host is required for deterministic package retrieval, then mirror or pin where possible."
+ }
+ ],
+ "recommendedActions": [
+ "Attach sandbox findings to the repository review packet.",
+ "Re-run the exact reproducibility pipeline after remediation.",
+ "Preserve reviewer evidence with the tagged release record."
+ ]
+ },
+ {
+ "id": "repo-hold-004",
+ "title": "Private dataset release with writable home mount",
+ "decision": "HOLD_MERGE",
+ "pipeline": "code/reproduce.sh",
+ "findings": [
+ {
+ "code": "SECRET_ENVIRONMENT_EXPOSED",
+ "severity": "critical",
+ "message": "Secret-like environment variables are available to the repository execution sandbox.",
+ "evidence": {
+ "env": [
+ "DATABASE_URL"
+ ]
+ },
+ "remediation": "Remove secrets from reproducibility runs and replace live credentials with synthetic or read-only fixtures."
+ },
+ {
+ "code": "PRIVILEGED_SANDBOX_RUNTIME",
+ "severity": "critical",
+ "message": "The sandbox uses privileged, root, host PID, or host IPC execution settings.",
+ "evidence": {
+ "privileged": false,
+ "user": "root",
+ "hostPid": false,
+ "hostIpc": true
+ },
+ "remediation": "Run the container as a non-root user without privileged mode or host namespace sharing."
+ },
+ {
+ "code": "UNSAFE_HOST_OR_WRITE_MOUNT",
+ "severity": "critical",
+ "message": "The sandbox has unsafe host mounts or writable paths outside declared artifact locations.",
+ "evidence": {
+ "mounts": [
+ {
+ "source": "/home/researcher",
+ "target": "/host-home",
+ "mode": "rw"
+ },
+ {
+ "source": "workspace",
+ "target": "/workspace",
+ "mode": "rw"
+ }
+ ]
+ },
+ "remediation": "Restrict mounts to read-only workspace inputs and writable results/artifacts directories."
+ },
+ {
+ "code": "REPOSITORY_RELEASE_NOT_FROZEN_FOR_SANDBOX_RISK",
+ "severity": "critical",
+ "message": "Repository merge or public release remains enabled while sandbox findings are unresolved.",
+ "evidence": {
+ "publicReleaseEnabled": false,
+ "protectedBranchMerge": true,
+ "freezeRelease": false
+ },
+ "remediation": "Freeze protected-branch merge, DOI tagging, and public reproducibility badges until sandbox findings are cleared."
+ }
+ ],
+ "recommendedActions": [
+ "Freeze protected-branch merge, DOI tagging, and public reproducibility badges.",
+ "Attach sandbox findings to the repository review packet.",
+ "Re-run the exact reproducibility pipeline after remediation.",
+ "Preserve reviewer evidence with the tagged release record."
+ ]
+ }
+ ]
+}
diff --git a/repository-sandbox-egress-guard/reports/summary.svg b/repository-sandbox-egress-guard/reports/summary.svg
new file mode 100644
index 00000000..a2554151
--- /dev/null
+++ b/repository-sandbox-egress-guard/reports/summary.svg
@@ -0,0 +1,17 @@
+
diff --git a/repository-sandbox-egress-guard/scripts/demo.js b/repository-sandbox-egress-guard/scripts/demo.js
new file mode 100644
index 00000000..1f3f3924
--- /dev/null
+++ b/repository-sandbox-egress-guard/scripts/demo.js
@@ -0,0 +1,77 @@
+"use strict";
+
+const fs = require("node:fs");
+const path = require("node:path");
+const { analyzeSandboxRunPlans } = require("../src");
+
+const root = path.resolve(__dirname, "..");
+const dataPath = path.join(root, "data", "sample_sandbox_run_plans.json");
+const outDir = path.join(root, "reports");
+const plans = JSON.parse(fs.readFileSync(dataPath, "utf8"));
+const report = analyzeSandboxRunPlans(plans);
+
+fs.mkdirSync(outDir, { recursive: true });
+fs.writeFileSync(path.join(outDir, "summary.json"), `${JSON.stringify(report, null, 2)}\n`);
+
+const markdown = [
+ "# Repository Sandbox Egress Guard Report",
+ "",
+ `Generated: ${report.generatedAt}`,
+ `Execution plans analyzed: ${report.totalPlans}`,
+ `Decision counts: RELEASE ${report.counts.RELEASE}, REVIEW_MERGE ${report.counts.REVIEW_MERGE}, HOLD_MERGE ${report.counts.HOLD_MERGE}`,
+ "",
+ "## Findings",
+ "",
+ ...report.results.flatMap((item) => [
+ `### ${item.id}: ${item.title}`,
+ "",
+ `Decision: ${item.decision}`,
+ `Pipeline: ${item.pipeline || "not declared"}`,
+ "",
+ item.findings.length === 0
+ ? "- No sandbox egress or containment risk detected."
+ : item.findings.map((finding) => `- ${finding.severity.toUpperCase()} ${finding.code}: ${finding.message}`).join("\n"),
+ "",
+ "Recommended actions:",
+ ...item.recommendedActions.map((action) => `- ${action}`),
+ ""
+ ])
+].join("\n");
+
+fs.writeFileSync(path.join(outDir, "reviewer-packet.md"), markdown);
+
+const total = Math.max(1, report.totalPlans);
+const maxBar = 620;
+const holdWidth = Math.round((report.counts.HOLD_MERGE / total) * maxBar);
+const reviewWidth = Math.round((report.counts.REVIEW_MERGE / total) * maxBar);
+const releaseWidth = Math.round((report.counts.RELEASE / total) * maxBar);
+const svg = `
+`;
+
+fs.writeFileSync(path.join(outDir, "summary.svg"), svg);
+
+console.log(JSON.stringify({
+ plans: report.totalPlans,
+ counts: report.counts,
+ outputs: [
+ path.join(outDir, "summary.json"),
+ path.join(outDir, "reviewer-packet.md"),
+ path.join(outDir, "summary.svg")
+ ]
+}, null, 2));
diff --git a/repository-sandbox-egress-guard/scripts/render-demo-video.js b/repository-sandbox-egress-guard/scripts/render-demo-video.js
new file mode 100644
index 00000000..993e8621
--- /dev/null
+++ b/repository-sandbox-egress-guard/scripts/render-demo-video.js
@@ -0,0 +1,60 @@
+"use strict";
+
+const fs = require("node:fs");
+const path = require("node:path");
+const { spawnSync } = require("node:child_process");
+
+const root = path.resolve(__dirname, "..");
+const ffmpeg = path.resolve(
+ root,
+ "..",
+ "..",
+ "tool_downloads",
+ "video_tools",
+ "node_modules",
+ "ffmpeg-static",
+ "ffmpeg.exe"
+);
+const outDir = path.join(root, "reports");
+const out = path.join(outDir, "demo.mp4");
+const font = "C\\:/Windows/Fonts/arial.ttf";
+
+fs.mkdirSync(outDir, { recursive: true });
+
+const draw = [
+ `drawtext=fontfile=${font}:text='Repository Sandbox Egress Guard':x=64:y=56:fontsize=42:fontcolor=black`,
+ `drawtext=fontfile=${font}:text='Reproducibility run containment before merge or DOI release':x=64:y=122:fontsize=25:fontcolor=0x34405a`,
+ `drawtext=fontfile=${font}:text='Synthetic execution plans analyzed 4':x=64:y=214:fontsize=34:fontcolor=black`,
+ `drawtext=fontfile=${font}:text='HOLD 2 REVIEW 1 RELEASE 1':x=64:y=274:fontsize=40:fontcolor=0x225c9c`,
+ `drawtext=fontfile=${font}:text='Detects open egress secrets privileged mode unsafe mounts':x=64:y=366:fontsize=26:fontcolor=0x34405a`,
+ `drawtext=fontfile=${font}:text='Freezes protected merge and public release until sandbox risks clear':x=64:y=428:fontsize=26:fontcolor=0x34405a`,
+ `drawtext=fontfile=${font}:text='Synthetic generated slate only no desktop capture or private data':x=64:y=492:fontsize=26:fontcolor=0x34405a`
+].join(",");
+
+if (!fs.existsSync(ffmpeg)) {
+ throw new Error(`ffmpeg binary not found at ${ffmpeg}`);
+}
+
+const result = spawnSync(ffmpeg, [
+ "-y",
+ "-f",
+ "lavfi",
+ "-i",
+ "color=c=0xf7fbff:s=1280x720:d=8:r=30",
+ "-vf",
+ draw,
+ "-c:v",
+ "libx264",
+ "-pix_fmt",
+ "yuv420p",
+ "-movflags",
+ "+faststart",
+ out
+], { encoding: "utf8" });
+
+if (result.status !== 0) {
+ process.stderr.write(result.stderr || result.stdout);
+ process.exit(result.status || 1);
+}
+
+console.log(JSON.stringify({ out, bytes: fs.statSync(out).size }, null, 2));
diff --git a/repository-sandbox-egress-guard/src/index.js b/repository-sandbox-egress-guard/src/index.js
new file mode 100644
index 00000000..c725d69a
--- /dev/null
+++ b/repository-sandbox-egress-guard/src/index.js
@@ -0,0 +1,273 @@
+"use strict";
+
+function list(value) {
+ return Array.isArray(value) ? value : [];
+}
+
+function normalizeText(value) {
+ return String(value || "").trim().toLowerCase();
+}
+
+function finding(code, severity, message, evidence, remediation) {
+ return { code, severity, message, evidence, remediation };
+}
+
+function normalizePlan(plan) {
+ return {
+ id: plan.id,
+ title: plan.title || "Untitled repository sandbox plan",
+ repository: plan.repository || {},
+ execution: plan.execution || {},
+ container: plan.container || {},
+ network: plan.network || {},
+ secrets: plan.secrets || {},
+ resources: plan.resources || {},
+ outputs: plan.outputs || {}
+ };
+}
+
+function hasPinnedImage(plan) {
+ return /^sha256:[a-f0-9]{64}$/i.test(String(plan.container.digest || ""));
+}
+
+function hasLockedExecution(plan) {
+ return plan.execution.commandHashPresent === true && plan.execution.lockfilePresent === true;
+}
+
+function networkFinding(plan) {
+ const mode = normalizeText(plan.network.mode);
+ const allowedHosts = list(plan.network.allowedHosts).map(normalizeText).filter(Boolean);
+
+ if (mode === "deny") return null;
+ if (mode === "allowlist" && allowedHosts.length > 0 && !allowedHosts.includes("*")) {
+ return finding(
+ "SANDBOX_NETWORK_ALLOWLIST_REVIEW",
+ "major",
+ "The reproducibility run uses limited network egress and needs reviewer approval before merge.",
+ { mode, allowedHosts },
+ "Confirm every allowed host is required for deterministic package retrieval, then mirror or pin where possible."
+ );
+ }
+
+ return finding(
+ "SANDBOX_NETWORK_EGRESS_OPEN",
+ "critical",
+ "The reproducibility run allows open or wildcard network egress.",
+ { mode: mode || "unspecified", allowedHosts },
+ "Deny egress by default or replace it with a narrow host allowlist and cached inputs."
+ );
+}
+
+function secretFindings(plan) {
+ const env = list(plan.secrets.env).filter(Boolean);
+ const mounts = list(plan.secrets.mounts).filter(Boolean);
+ const findings = [];
+
+ if (env.length > 0) {
+ findings.push(finding(
+ "SECRET_ENVIRONMENT_EXPOSED",
+ "critical",
+ "Secret-like environment variables are available to the repository execution sandbox.",
+ { env },
+ "Remove secrets from reproducibility runs and replace live credentials with synthetic or read-only fixtures."
+ ));
+ }
+
+ if (mounts.length > 0) {
+ findings.push(finding(
+ "SECRET_MOUNT_EXPOSED",
+ "critical",
+ "Host secret paths are mounted into the repository execution sandbox.",
+ { mounts },
+ "Remove host secret mounts and use declared, non-sensitive repository fixtures."
+ ));
+ }
+
+ return findings;
+}
+
+function unsafeMounts(plan) {
+ return list(plan.container.mounts).filter((mount) => {
+ const source = normalizeText(mount.source);
+ const target = normalizeText(mount.target);
+ return source.includes("docker.sock") ||
+ target.includes("docker.sock") ||
+ source.startsWith("/") && !source.startsWith("/workspace") ||
+ normalizeText(mount.mode) === "rw" && target !== "/workspace/results" && target !== "/workspace/artifacts";
+ });
+}
+
+function missingResourceCaps(plan) {
+ const resources = plan.resources;
+ return ["cpu", "memoryMb", "timeoutSeconds", "artifactLimitMb"].filter((key) => {
+ const value = resources[key];
+ if (typeof value === "string") return value.trim() === "";
+ return !Number.isFinite(Number(value)) || Number(value) <= 0;
+ });
+}
+
+function unsafeOutputPaths(plan) {
+ return list(plan.outputs.paths).filter((rawPath) => {
+ const value = String(rawPath || "").replace(/\\/g, "/");
+ return value.startsWith("/") ||
+ value.includes("../") ||
+ !(value.startsWith("results/") || value.startsWith("artifacts/"));
+ });
+}
+
+function analyzePlan(input) {
+ const plan = normalizePlan(input);
+ const findings = [];
+ const network = networkFinding(plan);
+ const mounts = unsafeMounts(plan);
+ const missingCaps = missingResourceCaps(plan);
+ const outputs = unsafeOutputPaths(plan);
+
+ if (!hasPinnedImage(plan)) {
+ findings.push(finding(
+ "CONTAINER_IMAGE_NOT_PINNED",
+ "major",
+ "The execution container is not pinned by a content digest.",
+ { image: plan.container.image || null, digest: plan.container.digest || null },
+ "Pin the runner image with a sha256 digest before publishing or merging the reproducibility run."
+ ));
+ }
+
+ if (!hasLockedExecution(plan)) {
+ findings.push(finding(
+ "EXECUTION_PROVENANCE_INCOMPLETE",
+ "major",
+ "The command hash or dependency lockfile evidence is missing.",
+ {
+ commandHashPresent: plan.execution.commandHashPresent || false,
+ lockfilePresent: plan.execution.lockfilePresent || false
+ },
+ "Attach hashed commands and dependency lock evidence for the exact pipeline revision."
+ ));
+ }
+
+ if (network) findings.push(network);
+ findings.push(...secretFindings(plan));
+
+ if (plan.container.privileged === true || normalizeText(plan.container.user) === "root" || plan.container.hostPid === true || plan.container.hostIpc === true) {
+ findings.push(finding(
+ "PRIVILEGED_SANDBOX_RUNTIME",
+ "critical",
+ "The sandbox uses privileged, root, host PID, or host IPC execution settings.",
+ {
+ privileged: plan.container.privileged || false,
+ user: plan.container.user || null,
+ hostPid: plan.container.hostPid || false,
+ hostIpc: plan.container.hostIpc || false
+ },
+ "Run the container as a non-root user without privileged mode or host namespace sharing."
+ ));
+ }
+
+ if (mounts.length > 0) {
+ findings.push(finding(
+ "UNSAFE_HOST_OR_WRITE_MOUNT",
+ "critical",
+ "The sandbox has unsafe host mounts or writable paths outside declared artifact locations.",
+ { mounts },
+ "Restrict mounts to read-only workspace inputs and writable results/artifacts directories."
+ ));
+ }
+
+ if (missingCaps.length > 0) {
+ findings.push(finding(
+ "RESOURCE_CAPS_MISSING",
+ "major",
+ "The execution plan lacks one or more resource caps.",
+ { missingCaps },
+ "Set CPU, memory, timeout, and artifact-size caps before queueing the run."
+ ));
+ }
+
+ if (outputs.length > 0) {
+ findings.push(finding(
+ "OUTPUT_PATH_ESCAPES_WORKSPACE",
+ "critical",
+ "One or more output paths escape declared results or artifacts directories.",
+ { outputs },
+ "Write outputs only under repository-scoped results/ or artifacts/ paths."
+ ));
+ }
+
+ if (
+ findings.length > 0 &&
+ (plan.repository.publicReleaseEnabled === true || plan.repository.protectedBranchMerge === true) &&
+ plan.repository.freezeRelease !== true
+ ) {
+ findings.push(finding(
+ "REPOSITORY_RELEASE_NOT_FROZEN_FOR_SANDBOX_RISK",
+ "critical",
+ "Repository merge or public release remains enabled while sandbox findings are unresolved.",
+ {
+ publicReleaseEnabled: plan.repository.publicReleaseEnabled || false,
+ protectedBranchMerge: plan.repository.protectedBranchMerge || false,
+ freezeRelease: plan.repository.freezeRelease || false
+ },
+ "Freeze protected-branch merge, DOI tagging, and public reproducibility badges until sandbox findings are cleared."
+ ));
+ }
+
+ const decision = decisionFor(findings);
+ return {
+ id: plan.id,
+ title: plan.title,
+ decision,
+ pipeline: plan.execution.pipeline || null,
+ findings,
+ recommendedActions: buildRecommendedActions(decision, findings)
+ };
+}
+
+function decisionFor(findings) {
+ if (findings.some((item) => item.severity === "critical")) return "HOLD_MERGE";
+ if (findings.some((item) => item.severity === "major")) return "REVIEW_MERGE";
+ return "RELEASE";
+}
+
+function buildRecommendedActions(decision, findings) {
+ if (decision === "RELEASE") {
+ return ["Allow repository execution, merge, and tagged release with normal provenance."];
+ }
+
+ const actions = [
+ "Attach sandbox findings to the repository review packet.",
+ "Re-run the exact reproducibility pipeline after remediation.",
+ "Preserve reviewer evidence with the tagged release record."
+ ];
+
+ if (findings.some((item) => item.code === "REPOSITORY_RELEASE_NOT_FROZEN_FOR_SANDBOX_RISK")) {
+ actions.unshift("Freeze protected-branch merge, DOI tagging, and public reproducibility badges.");
+ }
+
+ return actions;
+}
+
+function analyzeSandboxRunPlans(plans) {
+ const results = list(plans).map(analyzePlan);
+ const counts = results.reduce((acc, item) => {
+ acc[item.decision] = (acc[item.decision] || 0) + 1;
+ return acc;
+ }, { RELEASE: 0, REVIEW_MERGE: 0, HOLD_MERGE: 0 });
+
+ return {
+ generatedAt: new Date().toISOString(),
+ totalPlans: results.length,
+ counts,
+ results
+ };
+}
+
+module.exports = {
+ analyzePlan,
+ analyzeSandboxRunPlans,
+ hasPinnedImage,
+ hasLockedExecution,
+ unsafeMounts,
+ unsafeOutputPaths,
+ missingResourceCaps
+};
diff --git a/repository-sandbox-egress-guard/test/repository-sandbox-egress-guard.test.js b/repository-sandbox-egress-guard/test/repository-sandbox-egress-guard.test.js
new file mode 100644
index 00000000..df26c400
--- /dev/null
+++ b/repository-sandbox-egress-guard/test/repository-sandbox-egress-guard.test.js
@@ -0,0 +1,65 @@
+"use strict";
+
+const assert = require("node:assert/strict");
+const test = require("node:test");
+const {
+ analyzePlan,
+ analyzeSandboxRunPlans,
+ hasPinnedImage,
+ hasLockedExecution,
+ unsafeMounts,
+ unsafeOutputPaths,
+ missingResourceCaps
+} = require("../src");
+const samples = require("../data/sample_sandbox_run_plans.json");
+
+test("accepts a pinned, locked, egress-denied repository run", () => {
+ const plan = samples.find((item) => item.id === "repo-release-001");
+ const result = analyzePlan(plan);
+ assert.equal(hasPinnedImage(plan), true);
+ assert.equal(hasLockedExecution(plan), true);
+ assert.equal(result.decision, "RELEASE");
+ assert.equal(result.findings.length, 0);
+});
+
+test("holds open egress and host-secret exposure", () => {
+ const result = analyzePlan(samples.find((item) => item.id === "repo-hold-002"));
+ assert.equal(result.decision, "HOLD_MERGE");
+ assert.ok(result.findings.some((item) => item.code === "SANDBOX_NETWORK_EGRESS_OPEN"));
+ assert.ok(result.findings.some((item) => item.code === "SECRET_ENVIRONMENT_EXPOSED"));
+ assert.ok(result.findings.some((item) => item.code === "SECRET_MOUNT_EXPOSED"));
+});
+
+test("detects unsafe mounts and workspace-escaping outputs", () => {
+ const plan = samples.find((item) => item.id === "repo-hold-002");
+ assert.ok(unsafeMounts(plan).length >= 2);
+ assert.deepEqual(unsafeOutputPaths(plan), ["../published/model.pkl", "/tmp/raw-dump.csv"]);
+});
+
+test("routes allowlisted egress and missing lockfiles for review", () => {
+ const result = analyzePlan(samples.find((item) => item.id === "repo-review-003"));
+ assert.equal(result.decision, "REVIEW_MERGE");
+ assert.ok(result.findings.some((item) => item.code === "SANDBOX_NETWORK_ALLOWLIST_REVIEW"));
+ assert.ok(result.findings.some((item) => item.code === "EXECUTION_PROVENANCE_INCOMPLETE"));
+});
+
+test("detects privileged runtime settings and missing resource caps", () => {
+ const plan = samples.find((item) => item.id === "repo-hold-002");
+ const result = analyzePlan(plan);
+ assert.deepEqual(missingResourceCaps(plan), ["cpu", "memoryMb", "timeoutSeconds", "artifactLimitMb"]);
+ assert.ok(result.findings.some((item) => item.code === "PRIVILEGED_SANDBOX_RUNTIME"));
+ assert.ok(result.findings.some((item) => item.code === "RESOURCE_CAPS_MISSING"));
+});
+
+test("holds root or host namespace execution even when egress is denied", () => {
+ const result = analyzePlan(samples.find((item) => item.id === "repo-hold-004"));
+ assert.equal(result.decision, "HOLD_MERGE");
+ assert.ok(result.findings.some((item) => item.code === "PRIVILEGED_SANDBOX_RUNTIME"));
+ assert.ok(result.findings.some((item) => item.code === "UNSAFE_HOST_OR_WRITE_MOUNT"));
+});
+
+test("aggregates sandbox run decisions", () => {
+ const report = analyzeSandboxRunPlans(samples);
+ assert.equal(report.totalPlans, 4);
+ assert.deepEqual(report.counts, { RELEASE: 1, REVIEW_MERGE: 1, HOLD_MERGE: 2 });
+});