Skip to content

Sandboy: design-readiness — isolation ADR + WIT adapter & wrap-the-child spikes + capability-layer design - #174

Merged
PhysShell merged 8 commits into
mainfrom
claude/sandboy-design-readiness-22e1uw
Jul 4, 2026
Merged

PhysShell merged 8 commits into
mainfrom
claude/sandboy-design-readiness-22e1uw

Conversation

@PhysShell

@PhysShell PhysShell commented Jul 4, 2026

Copy link
Copy Markdown
Owner

Что и зачем

Отвечает на вопрос сессии «хватает ли информации, чтобы проектировать 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-surface adapters.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-файлов.

Тип изменения

  • docs — документация (ADR + design-notes — основной деливери)
  • feat — новая возможность (exploratory-спайки: audit/adapters/, sandboy/)
  • fix — исправление бага
  • refactor / chore / test / ci — без изменения поведения

Как проверено

  • python tests/run_tests.pyN/A: Python-ядро (ownlang/) не затронуто. PR — это доки + новые Rust-спайки под audit/adapters/ и sandboy/; поведение анализатора не менялось.
  • ruff check . и mypy — N/A по той же причине (нет изменений в ownlang).
  • Rust-спайки authored, но не собраны в этой сессии: окружение network-restricted (нет wasm-таргета, static.crates.io заблокирован egress-политикой). Точные команды локальной сборки/прогона — в audit/adapters/BUILD.md и sandboy/README.md; version-sensitive места (bindgen-пути, apply_*-API) там же помечены.
  • Ресёрч под ADR §4–5/§7 верифицирован адверсариально (deep-research: 25/25 claim'ов подтверждены 3–0, 0 опровергнуто); источники и caveats — в ADR Appendix.

Связанные issue

Нет связанных issue — ветка выросла из интерактивной проектной сессии, не из тикета.

Чеклист

  • изменение покрыто тестом/селфтестом (или объяснено, почему нет) — спайки несут демо/тест-входы (sandboy/tests/demo.sh, audit/adapters/tests/), но не собираются в этом окружении (см. «Как проверено»); Python-гейт не применим.
  • README/docs обновлены при необходимости — каждый спайк несёт README/BUILD; ADR §4/§6/§7 обновлены под построенное.
  • коммиты в conventional-commit стиле (docs:, spike: …)

🤖 Generated with Claude Code

https://claude.ai/code/session_01LMA4JwvgPBCZuEbAiG5LJL


Generated by Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new adapter system for converting tool output into standardized SARIF.
    • Introduced a host runner that executes adapters with resource limits and adds provenance details.
    • Added a child-process confinement tool with filesystem, network, and syscall restrictions.
  • Documentation

    • Added guides and design notes for building, running, and understanding the adapter and isolation workflows.
  • Tests

    • Added sample SARIF and adversarial fixtures plus a demo script to validate expected behavior.

claude added 6 commits July 4, 2026 03:06
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
@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@PhysShell, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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 configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: c4a7add2-5c23-4214-9aee-5b27b561d47f

📥 Commits

Reviewing files that changed from the base of the PR and between 7927d95 and 8234fe9.

📒 Files selected for processing (7)
  • audit/adapters/README.md
  • audit/adapters/UNIFIED.md
  • audit/adapters/components/infersharp/src/lib.rs
  • audit/adapters/host/Cargo.toml
  • audit/adapters/host/src/main.rs
  • sandboy/src/main.rs
  • sandboy/src/policy.rs
📝 Walkthrough

Walkthrough

This 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.

Changes

SARIF Adapter Spike

Layer / File(s) Summary
WIT contract and adapter configuration
audit/adapters/wit/world.wit, audit/adapters/adapters.toml
Defines the sarif-adapter WIT world/types and per-tool adapters.toml phases/limits for Roslyn, CodeQL, and Infer#.
Passthrough component
audit/adapters/components/passthrough/*, audit/adapters/tests/passthrough/roslyn.sarif
Implements SARIF validation/normalization for native-SARIF tools with a test fixture.
Infer# component
audit/adapters/components/infersharp/*, audit/adapters/tests/infersharp/*
Transforms Infer# report.json into SARIF, including rule/result mapping and adversarial/happy-path fixtures.
own-adapter-host runner
audit/adapters/host/*
Wasmtime host that loads adapter components under fuel/epoch/memory limits, validates output SARIF, and stamps provenance.
Adapter spike documentation
audit/adapters/README.md, audit/adapters/BUILD.md, audit/adapters/UNIFIED.md
Documents architecture, build/run steps, and orchestrator integration.

Sandboy Process Isolation MVP

Layer / File(s) Summary
Policy model
sandboy/src/policy.rs, sandboy/Cargo.toml, sandboy/policy.example.toml
Defines filesystem/TCP allowlists and seccomp denylist resolution loaded from TOML.
Confinement execution
sandboy/src/main.rs
Applies Landlock, seccomp, no-new-privs, and fd closing before exec-ing the confined child.
README and demo
sandboy/README.md, sandboy/tests/demo.sh
Documents usage and demonstrates allow/deny probes under a generated policy.

Design Notes

Layer / File(s) Summary
Capability layer and isolation ADR
docs/notes/agent-capability-layer.md, docs/notes/sandboy-isolation-adr.md
Adds design notes on an agent capability/policy layer and the ADR justifying OS-level isolation over WASM for agent sandboxing.

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
Loading
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly reflects the main Sandboy design-readiness work: ADRs plus the WIT adapter and wrap-the-child spikes.
Description check ✅ Passed The description follows the template structure and covers what changed, change type, verification, related issues, and checklist.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sandboy-design-readiness-22e1uw

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread sandboy/src/main.rs

// 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();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +151 to +154
artifacts.push(NamedBlob {
name: name.clone(),
bytes: std::fs::read(path).with_context(|| format!("reading {}", path.display()))?,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment thread audit/adapters/BUILD.md Outdated
## 3. Build the host

```bash
cd ../../host

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (7)
sandboy/src/main.rs (1)

35-40: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Fragile 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 of bail!/with_context messages elsewhere in the file, not on the actual error origin. For example, an exec failure 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 win

Unknown syscall names in an explicit override degrade silently.

When seccomp_deny is explicitly set by the policy author, a typo (or a name unsupported on the running arch) is dropped with only an eprintln! 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 explicit seccomp_deny as a hard error (fail the run), while keeping best-effort/skip behavior only for the curated DEFAULT_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 win

Add 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 win

Add 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 world

and 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 value

TOCTOU between size-check and read (minor, low risk here).

read_capped stats 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 a Read::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 win

Use a HashSet/HashMap for 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

📥 Commits

Reviewing files that changed from the base of the PR and between 2ecab43 and 7927d95.

📒 Files selected for processing (22)
  • audit/adapters/BUILD.md
  • audit/adapters/README.md
  • audit/adapters/UNIFIED.md
  • audit/adapters/adapters.toml
  • audit/adapters/components/infersharp/Cargo.toml
  • audit/adapters/components/infersharp/src/lib.rs
  • audit/adapters/components/passthrough/Cargo.toml
  • audit/adapters/components/passthrough/src/lib.rs
  • audit/adapters/host/Cargo.toml
  • audit/adapters/host/src/main.rs
  • audit/adapters/tests/infersharp/adversarial.json
  • audit/adapters/tests/infersharp/report.json
  • audit/adapters/tests/passthrough/roslyn.sarif
  • audit/adapters/wit/world.wit
  • docs/notes/agent-capability-layer.md
  • docs/notes/sandboy-isolation-adr.md
  • sandboy/Cargo.toml
  • sandboy/README.md
  • sandboy/policy.example.toml
  • sandboy/src/main.rs
  • sandboy/src/policy.rs
  • sandboy/tests/demo.sh

Comment thread audit/adapters/host/src/main.rs
Comment thread audit/adapters/wit/world.wit
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
@PhysShell
PhysShell merged commit 174764a into main Jul 4, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants