Skip to content

feat(tools,core): OS sandbox (#2808) + speculative tool dispatch (#2290, #2409)#3068

Merged
bug-ops merged 5 commits intomainfrom
tool-sandbox-speculative-exec
Apr 16, 2026
Merged

feat(tools,core): OS sandbox (#2808) + speculative tool dispatch (#2290, #2409)#3068
bug-ops merged 5 commits intomainfrom
tool-sandbox-speculative-exec

Conversation

@bug-ops
Copy link
Copy Markdown
Owner

@bug-ops bug-ops commented Apr 16, 2026

Summary

  • OS-level subprocess sandbox (#2808): platform-native isolation for all shell commands via ShellExecutor.with_sandbox(). macOS uses sandbox-exec Seatbelt, Linux uses bwrap + Landlock + seccomp BPF, other platforms fall back to NoopSandbox.
  • Speculative tool dispatch (#2290, #2409): SpeculationEngine with two strategies — decoding-level (fire on first complete JSON fields from SSE stream) and pattern-level PASTE (SQLite-backed invocation history predicts next tool).
  • All critical review findings resolved: seccomp denylist populated, dual-arch support, macOS profile overly-permissive paths removed, paths canonicalized, fd and tempfile leaks fixed, bootstrap wiring complete at all three sites.

OS sandbox details

Platform Backend Key mechanism
macOS sandbox-exec Seatbelt TinyScheme .sb profile generated per-call
Linux (sandbox feature) bwrap + Landlock + seccomp Namespace isolation + FS rules + BPF denylist
Other NoopSandbox Logs WARN, no isolation

Security invariants:

  • SandboxPolicy::canonicalized() strips symlinks and .. at construction
  • macOS profile: deny-default; never grants /private/tmp, /var/folders, /private/etc
  • Linux seccomp: 16 privilege-escalation syscalls denied (ptrace, kexec_load, bpf, mount, etc.), compiled once at startup, reused per call
  • seccomp arch via #[cfg(target_arch)] — correct on both x86_64 and aarch64
  • OwnedFd slot in LinuxSandbox keeps BPF fd alive from wrap() to spawn()
  • NamedTempFile pool in MacosSandbox — no per-call mem::forget

Config ([tools.sandbox]):

[tools.sandbox]
enabled = false      # opt-in
strict = true        # abort if backend unavailable
profile = "workspace"
allow_read = []
allow_write = []

Speculative dispatch details

Config ([tools.speculative]):

[tools.speculative]
mode = "off"         # off | decoding | pattern
max_in_flight = 4
ttl_secs = 30

Test plan

  • cargo build --features sandbox — no errors
  • cargo +nightly fmt --check — clean
  • cargo clippy --workspace --features sandbox -- -D warnings — clean
  • cargo nextest run --workspace --lib --bins — 8042 passed
  • Live: cargo run --features full -- --config .local/config/testing.toml with [tools.sandbox] enabled = true on macOS — shell tool commands execute inside sandbox-exec
  • Live: verify WARN OS sandbox unavailable logged when sandbox-exec absent and strict = false

Closes #2808, #2290, #2409

…, #2409)

Adds platform-native subprocess isolation (ShellExecutor sandbox) and two
complementary speculative tool dispatch strategies to reduce tool-call latency.

## OS-level subprocess sandbox (issue #2808)

- macOS: rewrites command as `sandbox-exec -f <profile>.sb -- <cmd>`.
  Profile generated per-call from SandboxPolicy (deny-default, explicit
  allow_read/allow_write paths, optional network). NamedTempFile pool prevents
  per-call leaks; no FFI, no sandbox_init.
- Linux (feature = "sandbox"): rewrites as `bwrap <ns-flags> <bind-mounts>
  --seccomp <fd> -- <cmd>`. Landlock FS rules applied on a throw-away thread.
  seccomp BPF denylist (16 privilege-escalation syscalls) compiled once, dual-arch.
  OwnedFd slot prevents raw fd leak between wrap() and spawn().
- NoopSandbox fallback logs WARN and passes through unchanged.
- SandboxPolicy paths canonicalized at construction (symlink/.. bypass fix).
- macOS profile never grants /private/tmp, /var/folders, or /private/etc.
- Wired at all three bootstrap sites: agent_setup.rs, daemon.rs, acp.rs.

## Speculative tool dispatch (issues #2290, #2409)

- Decoding-level (SpeculationMode::Decoding, #2290): PartialJsonParser streams
  SSE input_json_delta events and fires the tool call as soon as required schema
  fields are present. Operates on raw bytes; serde_json::from_slice for UTF-8-safe
  string decoding (fixes Cyrillic/CJK/emoji corruption from b-as-char Latin-1 cast).
- Pattern-level (SpeculationMode::Pattern, #2409 PASTE): PatternStore (SQLite)
  records invocation sequences and predicts next tool. Temporal decay in Rust via
  f64::powf (avoids SQLite pow() requiring SQLITE_ENABLE_MATH_FUNCTIONS).
- SpeculationEngine: synchronous try_dispatch with pure requires_confirmation check
  (no double side-effects). Sweeper shares the same CacheInner Arc. cancel_for
  cancels by tool_id. Migration 074 adds SQLite + Postgres schemas.
@bug-ops bug-ops force-pushed the tool-sandbox-speculative-exec branch from 5ef1c8f to fb72e34 Compare April 16, 2026 23:15
@bug-ops bug-ops enabled auto-merge (squash) April 16, 2026 23:15
@bug-ops bug-ops merged commit ec1d730 into main Apr 16, 2026
32 checks passed
@bug-ops bug-ops deleted the tool-sandbox-speculative-exec branch April 16, 2026 23:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channels zeph-channels crate (Telegram) core zeph-core crate dependencies Dependency updates documentation Improvements or additions to documentation enhancement New feature or request rust Rust code changes size/XL Extra large PR (500+ lines)

Projects

None yet

1 participant