Skip to content

Commit a87f5a4

Browse files
committed
sandboy: S0 GitHub-hosted-runner feasibility gate (env allowlist, probe, hostile fixtures)
Scope: Sandboy S0 capability probe for the QODEC Scope N2 sourcing addendum. Determines whether the existing Sandboy commit (Landlock + seccomp, no VM — Layer 2 of the isolation ADR) actually confines a hostile process tree on a standard ubuntu-24.04 GitHub-hosted runner, which supplies the disposable-VM boundary this gate assumes rather than builds. No third-party repository is executed, no RepoLogMiner, no microVM layer. - sandboy: env_clear() + env_allow allowlist before exec — closes a real gap found while designing the gate's credential-canary test (sandboy previously passed its full launcher environment straight through to the wrapped command). Default (no env_allow) is deny-all. - sandboy: new `probe` subcommand — reuses the same landlock_status/ apply_seccomp code `run` uses against a synthetic default policy, reports host capability (Landlock ABI/status via both the crate and a raw landlock_create_ruleset(..., VERSION) syscall, seccomp, no_new_privs, close_range, kernel/uid/container signals) as JSON. Always exits 0; a caller decides pass/fail. Distinct from the still-unimplemented `run --report` (per-step outcome) noted in the README. - tests/feasibility/: argv-only (no shell strings) synthetic hostile fixtures — filesystem escape (incl. symlink, unix-socket-bind), seccomp syscall denial with fork/child/grandchild/compiler-tree-shaped process trees and an unsandboxed baseline to isolate seccomp's actual contribution, credential-canary leak scanning, and TCP port-scope network tests — plus run_gate.py, which also runs the existing tests/demo.sh smoke and writes sandboy-host-capabilities.json, sandboy-feasibility-report.json, sandbox-enforcement-matrix.json, and sandboy-feasibility-summary.md. Documents (without live-testing against a real address) the two structural network gaps: no UDP coverage, and TCP scoped by port only, never destination address — so a metadata-endpoint port collision is not something Landlock can distinguish. - .github/workflows/sandboy-feasibility-gate.yml: read-only, pinned actions, no secrets, ubuntu-24.04, injects fake canary env vars, always uploads the four reports as CI artifacts even when the gate fails. - policy.rs unit tests for env_allow parsing/default and seccomp_deny resolution/hard-error-on-unknown-name. Locally (this dev container) the gate fails closed end-to-end: Landlock is not implemented in this kernel, so every sandboxed invocation is refused before it ever execs — which is exactly the scenario this gate exists to tell apart from a real ubuntu-24.04 runner. Real verification happens in CI. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0146FG6sjNiVaQEbCUZkzSy6
1 parent 53bf05a commit a87f5a4

12 files changed

Lines changed: 1723 additions & 42 deletions

File tree

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
name: Sandboy S0 — GitHub-Hosted Runner Feasibility Gate
2+
3+
# Capability probe only (Sandboy S0, part of the QODEC Scope N2 sourcing
4+
# addendum). Determines whether the EXISTING Sandboy commit (Landlock +
5+
# seccomp, no VM — sandboy/docs is Layer 2 of
6+
# ../docs/notes/sandboy-isolation-adr.md) actually confines a hostile process
7+
# tree on a standard `ubuntu-24.04` GitHub-hosted runner, which supplies the
8+
# disposable-VM boundary this gate assumes rather than builds.
9+
#
10+
# This workflow explicitly does NOT: execute any third-party repository,
11+
# build RepoLogMiner, start the N2 18-case corpus, touch qodec/ or any frozen
12+
# Benchmark v2 file, or implement a microVM/new isolation mechanism. Only
13+
# synthetic hostile fixtures committed under sandboy/tests/feasibility/ run
14+
# here.
15+
#
16+
# Least privilege: read-only checkout, no secrets, no OIDC, no package
17+
# publishing, no cache write. Every third-party `uses:` is pinned to a commit
18+
# SHA (matching this repo's existing ci.yml convention).
19+
permissions:
20+
contents: read
21+
22+
on:
23+
pull_request:
24+
paths:
25+
- "sandboy/**"
26+
- ".github/workflows/sandboy-feasibility-gate.yml"
27+
workflow_dispatch:
28+
29+
jobs:
30+
feasibility-gate:
31+
name: Sandboy S0 feasibility gate
32+
runs-on: ubuntu-24.04
33+
steps:
34+
- uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
35+
with:
36+
persist-credentials: false
37+
38+
- uses: dtolnay/rust-toolchain@fa04a1451ff1842e2626ccb99004d0195b455a88 # master, 2026-07-10
39+
with:
40+
toolchain: stable
41+
42+
- name: cargo test (policy unit tests — env_allow, seccomp_deny)
43+
working-directory: sandboy
44+
run: cargo test --release --locked
45+
46+
- name: Build sandboy (release, locked)
47+
working-directory: sandboy
48+
run: cargo build --release --locked
49+
50+
- name: Run Sandboy S0 feasibility gate
51+
id: gate
52+
working-directory: sandboy
53+
# Fake canary credentials injected into the OUTER job env, per the N2
54+
# Sandboy-dogfooding addendum §8. Values are unmistakably fake; no
55+
# real secret, token, or credential is used or present anywhere in
56+
# this job. The gate asserts these never reach the sandboxed process
57+
# tree (tests/feasibility/run_gate.py env_tests + a universal
58+
# canary-leak scan over every sandboxed invocation's stdout/stderr).
59+
env:
60+
GITHUB_TOKEN: fake-canary-github-token
61+
AZURE_DEVOPS_PAT: fake-canary-azure-token
62+
NUGET_AUTH_TOKEN: fake-canary-nuget-token
63+
AWS_SECRET_ACCESS_KEY: fake-canary-aws-key
64+
SSH_AUTH_SOCK: /tmp/fake-ssh-agent
65+
run: |
66+
set +e
67+
mkdir -p "$RUNNER_TEMP/sandboy-s0-reports"
68+
python3 tests/feasibility/run_gate.py \
69+
--sandboy-bin "$(pwd)/target/release/sandboy" \
70+
--out-dir "$RUNNER_TEMP/sandboy-s0-reports"
71+
code=$?
72+
echo "exit_code=$code" >> "$GITHUB_OUTPUT"
73+
exit 0 # never fail this step itself — artifacts must still upload
74+
75+
- name: Upload S0 feasibility reports
76+
if: always()
77+
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
78+
with:
79+
name: sandboy-s0-feasibility-reports
80+
path: |
81+
${{ runner.temp }}/sandboy-s0-reports/sandboy-host-capabilities.json
82+
${{ runner.temp }}/sandboy-s0-reports/sandboy-feasibility-report.json
83+
${{ runner.temp }}/sandboy-s0-reports/sandbox-enforcement-matrix.json
84+
${{ runner.temp }}/sandboy-s0-reports/sandboy-feasibility-summary.md
85+
if-no-files-found: error
86+
retention-days: 90
87+
88+
- name: Enforce gate result
89+
if: always()
90+
run: |
91+
if [ "${{ steps.gate.outputs.exit_code }}" != "0" ]; then
92+
echo "::error::Sandboy S0 feasibility gate FAILED (or Landlock/seccomp unavailable on this runner) — see the sandboy-s0-feasibility-reports artifact, especially sandboy-feasibility-summary.md"
93+
exit 1
94+
fi
95+
echo "Sandboy S0 feasibility gate PASSED on this ubuntu-24.04 runner."

‎sandboy/.gitignore‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
target/

‎sandboy/Cargo.lock‎

Lines changed: 239 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)