Sandboy: design-readiness — isolation ADR + WIT adapter & wrap-the-child spikes + capability-layer design - #174
Conversation
Records the outcome of the "is Sandboy design-ready?" audit. Reframes Sandboy as an agent-isolation mechanism (not extensibility): the sandbox slot is 007's run/gate, which executes untrusted bash -lc from a target repo's .007/gate.toml under bypassPermissions. - ADOPT: defense-in-depth (Firecracker microVM outer boundary + Sandlock wrap-the-child per gate step + netns/proxy egress), with an MVP that starts at the wrap-the-child layer alone. - PARK: WASM/WIT rule-plugin surface (no demand; conflicts with the execution-surfaces registry ADR) — kept as a trigger-table row. - REJECT: WASM/WASI as agent isolation (workload is native binaries). Backed by a verified deep-research pass (25/25 claims confirmed 3-0): 2026 microVM escape CVEs, gVisor syscall-surface reduction, the HTTP-Host/TLS-SNI egress gap vs QUIC, and per-second managed pricing. Cross-references 007/docs/security-layers.md; placed as a sibling-project note, not an Own.NET-core P-NNN proposal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
First slice of running raw->SARIF audit adapters as capability-scoped WebAssembly components (ownaudit:adapter world) instead of in-process parsers. The security argument holds at N=1: an adapter parses tool output derived from untrusted target code, so a parser bug is code exec in the orchestrator — the untrusted *input* is the boundary, not a third-party plugin market. - wit/world.wit: ownaudit:adapter@0.1.0 — one export, ZERO imports (zero ambient authority). Pure, deterministic bytes -> SARIF. - host/: own-adapter-host (wasmtime component model) — fuel-primary timeout + epoch backstop + 256MiB memory cap; the returned SARIF is NOT trusted, so it is schema-checked and size/result-capped before ingest; stamps the component sha256 into run.properties for provenance. - components/infersharp/: first ported adapter, Infer# report.json -> SARIF. - adapters.toml registry + happy-path/adversarial test inputs. Authored in a network-restricted sandbox (no wasm target, crate downloads egress-blocked), so NOT compiled here — BUILD.md has exact local build/run commands and flags the two version-sensitive spots (bindgen module paths, ABI evolution). Wires into audit/aggregate via a standalone binary aggregate calls per non-SARIF tool. Realizes the WIT-plugin idea parked in the Sandboy ADR — as untrusted- parser containment, not "rules in any language". Cross-referenced there. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
Extends the adapter spike into a single interface across Roslyn, CodeQL, and Infer#. The unifying contract is SARIF; the adapter layer is how every tool reaches it. - components/passthrough/: ownaudit:adapter component for tools that already emit SARIF (Roslyn, CodeQL). Not identity — it makes native-SARIF output take the SAME sandboxed, validated, provenance-stamped path (parse -> assert 2.1.x + runs[] -> canonicalize version/$schema -> re-emit). Their SARIF is still derived from untrusted target code, so parsing it in a zero-import component is defensible. - adapters.toml: grown from tool->component into the run control surface — per-tool phases (CodeQL honestly two-phase: db-build -> analyze), platform, build_free, output.kind, adapter. Invocation heterogeneity lives in data, not branching code. - UNIFIED.md: the whole path on all three tools + the aggregate loop shape (one own-adapter-host call per tool, one SARIF contract). - The host is unchanged and tool-agnostic — only --component differs. Answers "can I run all of this through one interface?": yes — every tool converges on validated SARIF, native-SARIF ones via passthrough, so there is no "SARIF here, non-SARIF there" split. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
Closes 3 of 4 open questions with sourced findings; #1 and #3 retain a 5-minute empirical residual (run on real Linux), not a design blocker. - gVisor x toolchain: LIKELY OK (277/351 syscalls; runtimes regression- tested). Residual: run gate.toml under runsc, watch for "Bad system call". Known gaps flagged (io_uring off, iptables partial, no KVM-in-sandbox). - Kata 2026: CLOSED — ~100-300ms boot, ~130-200MB/pod, needs KVM, no native egress, guest-kernel patch-currency required. Its edge is containerd/K8s drop-in; with no K8s here it adds overhead without advantage over direct Firecracker (L1) or Sandlock (L2). Fills the deep-research Kata gap. - QUIC/HTTP3 egress: CLOSED default — blanket UDP block suffices; CLI agent tools are TCP-first and fall back cleanly. Don't build MITM-CA/L7 proxy until a hard QUIC dependency appears. Residual: confirm nothing hangs. - TCO self-host vs managed: CLOSED — both cheap in $ at N=1; the fork is purpose/trust/offline, not cost. For a for-the-soul self-use project, build self-host; the L2 MVP is days not weeks. E2B is benchmark, not target. Also fixes the §6 trigger-table (an earlier note blockquote split the table mid-rows). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
The Sandboy MVP from the ADR §4: confine one untrusted command (a gate step, an agent tool call) with unprivileged Linux primitives — no root, no namespaces, no daemon. sandboy run --policy step.toml -- bash -lc '<a .007/gate.toml step>' Applies confinement to itself, then execve's the target; Landlock and seccomp both survive the exec, so the wrapped command and its children inherit the cage. - Landlock: FS read/exec + read/write path allowlists; TCP connect/bind scoped to allowlisted ports (ABI v4). Best-effort compat — warns on old kernels, refuses only if Landlock is entirely absent. - seccomp-bpf: denylist model (default Allow, dangerous syscalls -> EPERM: ptrace, mount, bpf, kexec, ...) to keep broad freedom while shrinking host-kernel attack surface. - Per-step TOML policy (policy.example.toml); tests/demo.sh shows 1 allowed + 3 denied probes (write outside allowlist, ptrace, off-allowlist port). Honest scope (ADR §4/§5): shares the host kernel, so NOT host-escape resistance on its own (that's Layer 1 / Firecracker); Landlock scopes ports not addresses, so CIDR/domain egress is Layer 3. One audited unsafe (prctl NO_NEW_PRIVS) at the syscall seam, justified in README; all other unsafe lives in the landlock/seccompiler crates. Authored in a network-restricted sandbox (crate downloads egress-blocked), so NOT compiled here — README flags the two version-sensitive apply_* spots. Wires into 007 by wrapping each gate step with a per-step policy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
Captures the agent capability-layer design discussed for the branch: WIT as a tool-capability boundary (not an agent sandbox), the load-bearing (A) tools-only vs (B) full-freedom+process-sandbox fork (recommend B), and the canonical owen.policy.toml that turns .agentsignore/.cursorignore/ .codexignore into generated compatibility artifacts. Lands the idea against what already exists: the capability-host is own-adapter-host generalized (zero-import world -> policy-mediated imports); the runner is Sandboy; only the policy engine (Phase 1) is greenfield. Frames ignore-generation as context-hygiene NOT security, flags the [exec]-allowlist-only-bites-without-shell caveat, and records the don't-do list (no agent-in-wasm, no Extism, no wasmCloud, no LLM-auto-applied policy). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
|
Warning Review limit reached
Next review available in: 45 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
📝 WalkthroughWalkthroughThis PR adds a spike for running SARIF adapters as capability-scoped WASM components (WIT contract, passthrough and Infer# adapters, a Wasmtime-based host runner, config, tests, docs), a separate "Sandboy" Landlock+seccomp process-isolation MVP, and two design notes. ChangesSARIF Adapter Spike
Sandboy Process Isolation MVP
Design Notes
Estimated code review effort: 3 (Moderate) | ~30 minutes Sequence Diagram(s)sequenceDiagram
participant Aggregate
participant OwnAdapterHost
participant WasmComponent
participant SARIFOutput
Aggregate->>OwnAdapterHost: invoke with tool args, artifacts, limits
OwnAdapterHost->>WasmComponent: call_to_sarif(RawInput)
WasmComponent-->>OwnAdapterHost: SARIF bytes or error
OwnAdapterHost->>OwnAdapterHost: validate_sarif (version, runs, max_results)
OwnAdapterHost->>OwnAdapterHost: stamp provenance (sha256, host, tool)
OwnAdapterHost-->>SARIFOutput: write validated SARIF to stdout
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b94e47771a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
|
|
||
| // 4. Hand off. execve replaces us; the two confinements persist into it. | ||
| // `exec()` only returns on failure. | ||
| let err = Command::new(prog).args(prog_args).exec(); |
There was a problem hiding this comment.
Close inherited descriptors before exec
When sandboy is launched by a gate/orchestrator that has any non-stdio descriptor without FD_CLOEXEC, this exec() preserves it into the untrusted command. Landlock and seccomp do not revoke already-open file or socket descriptors, so the wrapped process can read/write an inherited file or use an inherited network connection outside the TOML allowlists; close or mark all fd > 2 as close-on-exec before handing off.
Useful? React with 👍 / 👎.
| artifacts.push(NamedBlob { | ||
| name: name.clone(), | ||
| bytes: std::fs::read(path).with_context(|| format!("reading {}", path.display()))?, | ||
| }); |
There was a problem hiding this comment.
Cap artifacts before reading them into memory
For tool outputs supplied as artifacts, the host does an unbounded std::fs::read before any --max-bytes check or WASM memory limit applies. If an audited repo causes a tool to write a multi-GB report, this can OOM the adapter host/orchestrator despite the advertised component memory cap; size-check or stream inputs before allocating the full blob.
Useful? React with 👍 / 👎.
| ## 3. Build the host | ||
|
|
||
| ```bash | ||
| cd ../../host |
There was a problem hiding this comment.
Build the host from the actual host directory
The surrounding commands in this BUILD.md are run from audit/adapters (components/... paths and the later ./host/... invocation depend on that), but cd ../../host resolves to /workspace/Own.NET/host, which this commit does not add. Following the documented build therefore fails before the spike can run; this should be cd host or the cwd assumptions need to be changed consistently.
Useful? React with 👍 / 👎.
…ILD cwd - sandboy (P1): close inherited descriptors before execve. Landlock/seccomp do not revoke already-open fds, so a descriptor leaked by the launcher (open file / live socket without FD_CLOEXEC) would pass into the wrapped command and bypass the FS/port allowlists. Mark every fd > 2 CLOEXEC via one close_range() before handoff; stdio kept. README documents it; this is the second audited unsafe at the syscall seam. - adapter host (P2): cap artifact/stdout/stderr reads. The host did an unbounded fs::read on untrusted tool output before any limit applied, which could OOM the host regardless of the guest memory cap. Add read_capped() (metadata size-check before allocating) + --max-input-bytes (default 64 MiB). - BUILD.md (P2): fix cwd — after the step-2 subshells cwd stays in audit/adapters, so `cd ../../host` resolved to Own.NET/host. Use `cd host` and keep the run step's cwd consistent. All three are correctness/security fixes on the spikes; still authored-not- built here (network-restricted), consistent with the rest of the branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (7)
sandboy/src/main.rs (1)
35-40: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winFragile exit-code classification via error-message substring matching.
Determining exit code 2 vs 1 by checking whether the formatted error
contains("policy")is brittle: it depends on incidental wording ofbail!/with_contextmessages elsewhere in the file, not on the actual error origin. For example, anexecfailure for a command whose name contains "policy" would be misclassified as a policy/config error (exit 2) instead of a runtime failure (exit 1). Callers (gate runners, CI) that branch on exit code could get misleading signals.Prefer a typed error distinction (e.g., a small enum or a marker error type) instead of string matching.
🐛 Sketch using a typed error
-fn run() -> Result<()> { - let (policy_path, argv) = parse_args()?; - let policy = Policy::load(&policy_path) - .with_context(|| format!("loading policy {policy_path}"))?; +#[derive(Debug)] +struct ConfigError(anyhow::Error); + +fn run() -> Result<(), ConfigErrorOrRuntime> { + let (policy_path, argv) = parse_args().map_err(ConfigError)?; + let policy = Policy::load(&policy_path) + .with_context(|| format!("loading policy {policy_path}")) + .map_err(ConfigError)?;🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sandboy/src/main.rs` around lines 35 - 40, The exit-code logic in main is using brittle string matching on run() error text to distinguish policy/config failures from runtime failures. Replace the contains("policy") check with a typed error distinction from run(), such as a small enum or marker error type for policy/config vs other failures, and branch on that concrete type in main when choosing between exit codes 2 and 1.sandboy/src/policy.rs (1)
40-55: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winUnknown syscall names in an explicit override degrade silently.
When
seccomp_denyis explicitly set by the policy author, a typo (or a name unsupported on the running arch) is dropped with only aneprintln!warning — the run proceeds with a weaker filter. In an automated gate/CI context, stderr from a wrapped step is easy to lose, so "loudly" in practice may be silent. Consider treating unresolved names in an explicitseccomp_denyas a hard error (fail the run), while keeping best-effort/skip behavior only for the curatedDEFAULT_DENY(where cross-arch portability is the stated reason).🛡️ Sketch: fail on unresolved explicit overrides
pub fn seccomp_deny_numbers(&self) -> Vec<i64> { - let names: Vec<&str> = match &self.seccomp_deny { - Some(v) => v.iter().map(String::as_str).collect(), - None => DEFAULT_DENY.to_vec(), - }; - names - .iter() - .filter_map(|n| match syscall_number(n) { - Some(nr) => Some(nr), - None => { - eprintln!("sandboy: warning: unknown syscall in denylist: {n} (skipped)"); - None - } - }) - .collect() + match &self.seccomp_deny { + Some(v) => v + .iter() + .map(|n| syscall_number(n).ok_or_else(|| anyhow::anyhow!("unknown syscall in seccomp_deny: {n}"))) + .collect::<anyhow::Result<Vec<_>>>() + .expect("invalid seccomp_deny override"), // or propagate as Result + None => DEFAULT_DENY + .iter() + .filter_map(|n| syscall_number(n)) + .collect(), + } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@sandboy/src/policy.rs` around lines 40 - 55, The `seccomp_deny_numbers` method currently skips unknown syscall names in both explicit policy overrides and `DEFAULT_DENY`, which can silently weaken an author-provided filter. Update `sandboy::policy::seccomp_deny_numbers` so unresolved names from `self.seccomp_deny` cause a hard error instead of being filtered out, while preserving the current best-effort `eprintln!`-and-skip behavior only for the fallback `DEFAULT_DENY`. Use the `seccomp_deny_numbers` and `syscall_number` paths to distinguish explicit overrides from defaults and return/propagate a failure for the explicit case.audit/adapters/UNIFIED.md (1)
29-29: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a language to the fenced diagram block (MD040).
📝 Proposed fix
-``` +```text Roslyn ─analyze──▶ roslyn.sarif ─▶ passthrough.wasm ─┐🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@audit/adapters/UNIFIED.md` at line 29, The fenced diagram block in the unified adapters docs is missing a language tag, which triggers MD040. Update the diagram fence in the relevant markdown section to use a declared language such as text, keeping the diagram content unchanged so the fenced block is explicitly labeled.Source: Linters/SAST tools
audit/adapters/README.md (1)
48-48: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a language to the fenced code blocks (MD040).
Static analysis flags both the layout tree (Line 48) and pipeline diagram (Line 67) fences as missing a language identifier.
📝 Proposed fix
-``` +```text adapters/ wit/world.wit canonical ownaudit:adapter@0.1.0 worldand similarly for the pipeline diagram fence.
Also applies to: 67-67
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@audit/adapters/README.md` at line 48, The fenced code blocks in the README are missing a language identifier, which triggers MD040. Update the Markdown fences around the layout tree and the pipeline diagram to use an explicit language such as text, and make the same change for both affected fenced blocks so the README passes the static check.Source: Linters/SAST tools
audit/adapters/host/src/main.rs (1)
97-116: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low valueTOCTOU between size-check and read (minor, low risk here).
read_cappedstats the file then re-reads it; if the file could grow between calls, the size cap could be bypassed. Given these are tool-output files already fully written before this host invocation, the practical risk is low, but aRead::take(cap + 1)-based bounded read would close the gap without a stat/read race.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@audit/adapters/host/src/main.rs` around lines 97 - 116, The capped file read in read_capped has a small TOCTOU gap because it checks std::fs::metadata before calling std::fs::read, so a file could change between the two operations. Update read_capped in main.rs to use a single bounded read approach (for example, a Read::take(cap + 1)-style read path) that enforces the cap while reading, and then keep the existing size-exceeded error behavior in place. Leave read_opt unchanged unless needed to adapt to the new read_capped signature.audit/adapters/components/infersharp/src/lib.rs (1)
65-81: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winUse a
HashSet/HashMapfor rule dedup instead of O(n²) linear scan.
seen.iter().any(|r| r == &b.bug_type)re-scans the whole vector for every bug, making dedup quadratic in the number of findings. Fine for small reports, but wastes fuel/CPU on large ones under this component's own fuel budget.♻️ Proposed refactor
- let mut rules: Vec<serde_json::Value> = Vec::new(); - let mut seen: Vec<String> = Vec::new(); - for b in &bugs { - if !seen.iter().any(|r| r == &b.bug_type) { - seen.push(b.bug_type.clone()); + let mut rules: Vec<serde_json::Value> = Vec::new(); + let mut seen: std::collections::HashSet<&str> = std::collections::HashSet::new(); + for b in &bugs { + if seen.insert(b.bug_type.as_str()) { let name = if b.bug_type_hum.is_empty() { b.bug_type.clone() } else { b.bug_type_hum.clone() }; rules.push(json!({ "id": b.bug_type, "name": name, })); } }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@audit/adapters/components/infersharp/src/lib.rs` around lines 65 - 81, The dedup logic in infersharp’s rule-building loop is using a Vec-based linear scan via seen.iter().any, which makes it quadratic as bugs grows. Update the rules construction in lib.rs to use a HashSet (or HashMap if you want to keep first-seen order plus lookup) keyed by bug_type, and preserve the existing first-seen behavior while eliminating the repeated scan in the loop over bugs.audit/adapters/host/Cargo.toml (1)
11-19: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win
wasmtime = "28"is far behind current releases and outside the security-patch window.Wasmtime ships a new major monthly and only backports security fixes to LTS releases and the two most recent normal releases; v28 predates all currently-patched trains. Since this crate's entire job is safely sandboxing untrusted adapter output, worth pinning to a currently-supported (ideally LTS) version rather than leaving "28" as a placeholder before this spike graduates.
Please confirm the latest supported Wasmtime LTS/normal release and its component-model API compatibility before building this crate for real.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@audit/adapters/host/Cargo.toml` around lines 11 - 19, Update the wasmtime dependency in the audit/adapters/host Cargo.toml manifest from the hardcoded v28 placeholder to a currently supported release, preferably an LTS or one of the two most recent normal releases, and verify that the component-model feature/API still matches the code in this crate. Use the existing wasmtime dependency entry as the place to adjust the version, and confirm the chosen release before building so the host adapter sandbox stays on a security-patched train.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@audit/adapters/host/src/main.rs`:
- Around line 173-182: The RawInput construction in main needs a compile fix and
consistent input capping: the call to read_opt for options is missing the
max-input cap argument. Update the options field in the RawInput initializer to
use read_opt with the same cap passed for stdout and stderr
(args.max_input_bytes), so the helper signature is satisfied and options are
treated like the other untrusted host inputs.
In `@audit/adapters/wit/world.wit`:
- Around line 29-33: The call to read_opt is missing its required cap argument,
so the host code will not compile. Update the call site in the host entry path
that reads args.options_file to pass the u64 cap expected by read_opt, and make
sure the value is threaded through consistently from the surrounding config/CLI
state; use read_opt and the caller in main to locate the fix.
---
Nitpick comments:
In `@audit/adapters/components/infersharp/src/lib.rs`:
- Around line 65-81: The dedup logic in infersharp’s rule-building loop is using
a Vec-based linear scan via seen.iter().any, which makes it quadratic as bugs
grows. Update the rules construction in lib.rs to use a HashSet (or HashMap if
you want to keep first-seen order plus lookup) keyed by bug_type, and preserve
the existing first-seen behavior while eliminating the repeated scan in the loop
over bugs.
In `@audit/adapters/host/Cargo.toml`:
- Around line 11-19: Update the wasmtime dependency in the audit/adapters/host
Cargo.toml manifest from the hardcoded v28 placeholder to a currently supported
release, preferably an LTS or one of the two most recent normal releases, and
verify that the component-model feature/API still matches the code in this
crate. Use the existing wasmtime dependency entry as the place to adjust the
version, and confirm the chosen release before building so the host adapter
sandbox stays on a security-patched train.
In `@audit/adapters/host/src/main.rs`:
- Around line 97-116: The capped file read in read_capped has a small TOCTOU gap
because it checks std::fs::metadata before calling std::fs::read, so a file
could change between the two operations. Update read_capped in main.rs to use a
single bounded read approach (for example, a Read::take(cap + 1)-style read
path) that enforces the cap while reading, and then keep the existing
size-exceeded error behavior in place. Leave read_opt unchanged unless needed to
adapt to the new read_capped signature.
In `@audit/adapters/README.md`:
- Line 48: The fenced code blocks in the README are missing a language
identifier, which triggers MD040. Update the Markdown fences around the layout
tree and the pipeline diagram to use an explicit language such as text, and make
the same change for both affected fenced blocks so the README passes the static
check.
In `@audit/adapters/UNIFIED.md`:
- Line 29: The fenced diagram block in the unified adapters docs is missing a
language tag, which triggers MD040. Update the diagram fence in the relevant
markdown section to use a declared language such as text, keeping the diagram
content unchanged so the fenced block is explicitly labeled.
In `@sandboy/src/main.rs`:
- Around line 35-40: The exit-code logic in main is using brittle string
matching on run() error text to distinguish policy/config failures from runtime
failures. Replace the contains("policy") check with a typed error distinction
from run(), such as a small enum or marker error type for policy/config vs other
failures, and branch on that concrete type in main when choosing between exit
codes 2 and 1.
In `@sandboy/src/policy.rs`:
- Around line 40-55: The `seccomp_deny_numbers` method currently skips unknown
syscall names in both explicit policy overrides and `DEFAULT_DENY`, which can
silently weaken an author-provided filter. Update
`sandboy::policy::seccomp_deny_numbers` so unresolved names from
`self.seccomp_deny` cause a hard error instead of being filtered out, while
preserving the current best-effort `eprintln!`-and-skip behavior only for the
fallback `DEFAULT_DENY`. Use the `seccomp_deny_numbers` and `syscall_number`
paths to distinguish explicit overrides from defaults and return/propagate a
failure for the explicit case.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 865bd355-db16-4178-b6e3-d6eb968dd04a
📒 Files selected for processing (22)
audit/adapters/BUILD.mdaudit/adapters/README.mdaudit/adapters/UNIFIED.mdaudit/adapters/adapters.tomlaudit/adapters/components/infersharp/Cargo.tomlaudit/adapters/components/infersharp/src/lib.rsaudit/adapters/components/passthrough/Cargo.tomlaudit/adapters/components/passthrough/src/lib.rsaudit/adapters/host/Cargo.tomlaudit/adapters/host/src/main.rsaudit/adapters/tests/infersharp/adversarial.jsonaudit/adapters/tests/infersharp/report.jsonaudit/adapters/tests/passthrough/roslyn.sarifaudit/adapters/wit/world.witdocs/notes/agent-capability-layer.mddocs/notes/sandboy-isolation-adr.mdsandboy/Cargo.tomlsandboy/README.mdsandboy/policy.example.tomlsandboy/src/main.rssandboy/src/policy.rssandboy/tests/demo.sh
Critical:
- adapter host: `options: read_opt(&args.options_file)` was missing the cap
arg added last commit — a compile error I introduced. Now passes
args.max_input_bytes like stdout/stderr, so options is capped too.
Correctness / security:
- adapter host: read_capped now does a single bounded read (take(cap+1))
instead of metadata-then-read, closing the stat/read TOCTOU gap.
- sandboy policy: an unknown syscall name in an EXPLICIT seccomp_deny is now a
hard error (silently dropping it would weaken the author's filter, and a
wrapped step's stderr is easy to lose in CI). DEFAULT_DENY keeps best-effort
skip-with-warn (cross-arch portability is its stated reason).
- sandboy main: replace brittle exit-code classification (err text
contains("policy")) with distinction by origin — config/usage errors exit 2,
runtime/confinement errors exit 1.
Quality:
- infersharp adapter: O(n^2) rule dedup (Vec linear scan) -> HashSet.
- adapter host Cargo.toml: make the wasmtime "28" pin explicitly a placeholder
with a security note (bump to current supported LTS before real build; can't
verify the current train from the offline authoring sandbox).
- MD040: add `text` language to fenced diagram/tree blocks (README, UNIFIED).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
Что и зачем
Отвечает на вопрос сессии «хватает ли информации, чтобы проектировать Sandboy»: да. Ветка доводит направление Sandboy (изоляция агентов) до design-ready и кладёт рядом три рабочих каркаса. Всё — в неймспейсе Own.NET как sibling-проекта; изоляция решает задачу
007(run/gate-слот), поэтому доки перекрёстно ссылаются на007/docs/security-layers.md.Состав:
docs/notes/sandboy-isolation-adr.md— ADR: изоляция как основное направление, defense-in-depth (Firecracker microVM снаружи + Sandlock-style wrap-the-child на gate-шаг + netns/proxy egress), WIT-плагины запаркованы в trigger-table. §7 — четыре открытых вопроса, разрешённые добивающим ресёрчем (Kata-2026 закрыт, gVisor/QUIC — с эмпирическим остатком под прогон на Linux, TCO — self-host для для-души).audit/adapters/— спайк «где WIT реально полезен»: raw→SARIF адаптеры аудита как capability-free WASM-компоненты (ownaudit:adapter), Rust host-runner с fuel/epoch/mem-лимитами + валидацией выхода + provenance, портированный Infer# + passthrough (Roslyn/CodeQL), control-surfaceadapters.toml, единый путь (UNIFIED.md). Граница обоснована недоверенным входом — работает при N=1.sandboy/— Sandboy MVP (Layer 2):sandboy run --policy step.toml -- <cmd>, wrap-the-child через Landlock (FS + TCP-port scope) + seccomp-denylist, unprivileged.docs/notes/agent-capability-layer.md— design-note капабилити-слоя («Owen Gate»): WIT как граница тулов (не клетка агента), развилка (A) tools-only vs (B) full-freedom+sandbox, каноническийowen.policy.tomlвместо зоопарка ignore-файлов.Тип изменения
audit/adapters/,sandboy/)Как проверено
python tests/run_tests.py— N/A: Python-ядро (ownlang/) не затронуто. PR — это доки + новые Rust-спайки подaudit/adapters/иsandboy/; поведение анализатора не менялось.ruff check .иmypy— N/A по той же причине (нет изменений вownlang).static.crates.ioзаблокирован egress-политикой). Точные команды локальной сборки/прогона — вaudit/adapters/BUILD.mdиsandboy/README.md; version-sensitive места (bindgen-пути,apply_*-API) там же помечены.Связанные issue
Нет связанных issue — ветка выросла из интерактивной проектной сессии, не из тикета.
Чеклист
sandboy/tests/demo.sh,audit/adapters/tests/), но не собираются в этом окружении (см. «Как проверено»); Python-гейт не применим.docs:,spike:…)🤖 Generated with Claude Code
https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL
Generated by Claude Code
Summary by CodeRabbit
New Features
Documentation
Tests