Skip to main content

procjail/
lib.rs

1//! # procjail
2//!
3//! Process sandbox for running untrusted code in real runtimes.
4//!
5//! When security tools need to execute untrusted code (npm packages, pip
6//! packages, browser extensions, binaries), they need containment that
7//! actually works. This crate provides kernel-level isolation using the
8//! best available mechanism on the host.
9//!
10//! # Containment Strategies (ordered by preference)
11//!
12//! 1. **unshare** — Linux namespaces (PID, network, mount, user). No root needed.
13//! 2. **bubblewrap (bwrap)** — Lightweight container (Flatpak uses this). Rootless.
14//! 3. **firejail** — Feature-rich sandbox. Needs installation.
15//! 4. **rlimits** — Basic resource limits only. Always available. Least secure.
16//!
17//! The sandbox auto-selects the best available strategy, or you can force one.
18//!
19//! # Usage
20//!
21//! ```rust,no_run
22//! use std::path::Path;
23//! use procjail::{SandboxConfig, SandboxedProcess};
24//!
25//! let config = SandboxConfig::builder()
26//!     .runtime("/usr/bin/node")
27//!     .max_memory_mb(256)
28//!     .max_cpu_seconds(30)
29//!     .max_fds(64)
30//!     .allow_localhost(false)
31//!     .env_passthrough(&["HOME", "PATH", "NODE_PATH"])
32//!     .env_strip_secrets(true)
33//!     .build();
34//!
35//! let mut proc = SandboxedProcess::spawn(
36//!     Path::new("/path/to/harness.js"),
37//!     Path::new("/path/to/package"),
38//!     &config,
39//! ).unwrap();
40//!
41//! proc.send(r#"{"method":"eval","args":["1+1"]}"#).unwrap();
42//! if let Some(line) = proc.recv().unwrap() {
43//!     println!("observation: {}", line);
44//! }
45//! ```
46//!
47//! # Architecture
48//!
49//! ```text
50//! Parent (full privileges)
51//!   │
52//!   ├── stdin pipe  → probes flow in
53//!   ├── stdout pipe ← observations flow out
54//!   │
55//!   └── [containment layer]
56//!         ├── PID namespace (process isolation)
57//!         ├── NET namespace (no external network)
58//!         ├── MNT namespace (read-only filesystem)
59//!         ├── USER namespace (unprivileged)
60//!         ├── rlimits (memory, CPU, FDs)
61//!         └── env stripping (no secrets leak)
62//! ```
63
64// Note: unsafe code is used in process.rs for libc calls
65#![warn(missing_docs)]
66
67mod config;
68mod detect;
69mod error;
70mod process;
71mod strategy;
72
73#[cfg(test)]
74mod adversarial_tests;
75
76pub use config::{EnvMode, SandboxConfig, SandboxConfigBuilder};
77pub use detect::{available_strategy, probe_capabilities, ContainmentLevel};
78pub use error::{ProcjailError, Result};
79pub use process::{ResourceUsage, SandboxedProcess};
80pub use strategy::Strategy;
81
82/// Trait for communicating with a sandboxed process.
83pub trait SandboxedIO {
84    /// Send a line to the process.
85    fn send(&mut self, line: &str) -> Result<()>;
86    /// Receive a line from the process. None = EOF.
87    fn recv(&mut self) -> Result<Option<String>>;
88    /// Kill the process.
89    fn kill(&mut self);
90    /// Check if alive.
91    fn is_alive(&mut self) -> bool;
92}
93
94/// Convenience helper that spawns a sandboxed process with a minimal default config.
95///
96/// Example:
97/// ```rust,no_run
98/// use std::path::Path;
99/// use procjail::quick_spawn;
100///
101/// let _child = quick_spawn(
102///     "node",
103///     Path::new("/abs/path/to/harness.js"),
104///     Path::new("/abs/path/to/workdir"),
105/// )?;
106/// # Ok::<(), procjail::ProcjailError>(())
107/// ```
108pub fn quick_spawn(
109    runtime: &str,
110    harness: impl AsRef<std::path::Path>,
111    work_dir: impl AsRef<std::path::Path>,
112) -> Result<process::SandboxedProcess> {
113    let config = config::SandboxConfig::builder()
114        .runtime(runtime)
115        .timeout_seconds(30)
116        .build();
117    process::SandboxedProcess::spawn(harness.as_ref(), work_dir.as_ref(), &config)
118}