diff --git a/crates/bashkit/docs/compatibility.md b/crates/bashkit/docs/compatibility.md index fb807b72..311d1e2b 100644 --- a/crates/bashkit/docs/compatibility.md +++ b/crates/bashkit/docs/compatibility.md @@ -380,7 +380,7 @@ Default limits (configurable): ### Network Configuration -```rust +```rust,ignore use bashkit::{Bash, NetworkAllowlist}; // Enable network with URL allowlist diff --git a/crates/bashkit/docs/custom_builtins.md b/crates/bashkit/docs/custom_builtins.md index f6fb0335..e4dc969b 100644 --- a/crates/bashkit/docs/custom_builtins.md +++ b/crates/bashkit/docs/custom_builtins.md @@ -12,7 +12,7 @@ virtual filesystem. ## Quick Start -```rust,ignore +```rust use bashkit::{Bash, Builtin, BuiltinContext, ExecResult, async_trait}; struct MyCommand; @@ -144,12 +144,13 @@ pub struct ExecResult { Helper constructors: -```rust,ignore +```rust +# use bashkit::ExecResult; // Success with output -ExecResult::ok("output\n".to_string()) +ExecResult::ok("output\n".to_string()); // Error with message and exit code -ExecResult::err("error message\n".to_string(), 1) +ExecResult::err("error message\n".to_string(), 1); ``` ## Examples @@ -221,7 +222,9 @@ impl Builtin for HttpGet { Custom builtins can override default builtins by using the same name: -```rust,ignore +```rust,no_run +use bashkit::{Bash, Builtin, BuiltinContext, ExecResult, async_trait}; + struct SecureEcho; #[async_trait] @@ -235,9 +238,11 @@ impl Builtin for SecureEcho { } } +# fn main() { let bash = Bash::builder() .builtin("echo", Box::new(SecureEcho)) // Overrides default echo .build(); +# } ``` ## Best Practices @@ -253,7 +258,10 @@ let bash = Bash::builder() The `Builtin` trait requires `Send + Sync`. For builtins with mutable state, use appropriate synchronization: -```rust,ignore +```rust +use bashkit::{Builtin, BuiltinContext, ExecResult, async_trait}; +use std::sync::Arc; + struct Counter { count: Arc, } diff --git a/crates/bashkit/docs/logging.md b/crates/bashkit/docs/logging.md index 3b42b7af..38205e24 100644 --- a/crates/bashkit/docs/logging.md +++ b/crates/bashkit/docs/logging.md @@ -79,9 +79,10 @@ RUST_LOG=bashkit::parser=debug cargo run ### Custom Configuration -```rust,ignore +```rust use bashkit::{Bash, LogConfig}; +# fn main() { let bash = Bash::builder() .log_config(LogConfig::new() // Add custom sensitive variable patterns @@ -90,6 +91,7 @@ let bash = Bash::builder() // Limit logged value lengths .max_value_length(100)) .build(); +# } ``` ## Security (TM-LOG-*) @@ -130,7 +132,8 @@ Control characters are filtered, and newlines are escaped. For debugging in **non-production** environments only: -```rust,ignore +```rust +# use bashkit::LogConfig; // WARNING: May expose sensitive data let config = LogConfig::new() .unsafe_disable_redaction() // Disable ALL redaction diff --git a/crates/bashkit/docs/python.md b/crates/bashkit/docs/python.md index 866efb93..924992bb 100644 --- a/crates/bashkit/docs/python.md +++ b/crates/bashkit/docs/python.md @@ -18,13 +18,17 @@ configurable resource limits and no host access. Enable the `python` feature and register via builder: -```rust,ignore +```rust use bashkit::Bash; +# #[tokio::main] +# async fn main() -> bashkit::Result<()> { let mut bash = Bash::builder().python().build(); let result = bash.exec("python3 -c \"print('hello from Monty')\"").await?; assert_eq!(result.stdout, "hello from Monty\n"); +# Ok(()) +# } ``` ## Usage Patterns @@ -120,10 +124,11 @@ resumes execution with the result (or a Python exception like `FileNotFoundError Default limits prevent runaway Python code. Customize via `PythonLimits`: -```rust,ignore +```rust,no_run use bashkit::{Bash, PythonLimits}; use std::time::Duration; +# fn main() { let bash = Bash::builder() .python_with_limits( PythonLimits::default() @@ -133,6 +138,7 @@ let bash = Bash::builder() .max_recursion(50) ) .build(); +# } ``` | Limit | Default | Purpose | @@ -146,15 +152,17 @@ let bash = Bash::builder() When using `BashTool` for AI agents, call `.python()` on the tool builder: -```rust,ignore -use bashkit::BashTool; +```rust,no_run +use bashkit::{BashTool, Tool}; +# fn main() { let tool = BashTool::builder() .python() .build(); // help() and system_prompt() automatically document Python limitations let help = tool.help(); // Includes NOTES section with Python hints +# } ``` The builtin's `llm_hint()` is automatically included in the tool's documentation, diff --git a/crates/bashkit/docs/threat-model.md b/crates/bashkit/docs/threat-model.md index 4029785d..e4868e6a 100644 --- a/crates/bashkit/docs/threat-model.md +++ b/crates/bashkit/docs/threat-model.md @@ -45,11 +45,12 @@ through configurable limits. | ExtGlob blowup (TM-DOS-031) | `+(a\|aa)` exponential | Add depth limit | **OPEN** | **Configuration:** -```rust,ignore +```rust use bashkit::{Bash, ExecutionLimits, FsLimits, InMemoryFs}; use std::sync::Arc; use std::time::Duration; +# fn main() { let limits = ExecutionLimits::new() .max_commands(10_000) .max_loop_iterations(10_000) @@ -67,6 +68,7 @@ let bash = Bash::builder() .limits(limits) .fs(fs) .build(); +# } ``` ### Sandbox Escape (TM-ESC-*) @@ -88,17 +90,19 @@ Scripts may attempt to break out of the sandbox to access the host system. Bashkit uses an in-memory virtual filesystem by default. Scripts cannot access the real filesystem unless explicitly mounted via [`MountableFs`]. -```rust,ignore -use bashkit::{Bash, InMemoryFs}; +```rust +use bashkit::{Bash, InMemoryFs, MountableFs}; use std::sync::Arc; +# fn main() { // Default: fully isolated in-memory filesystem let bash = Bash::new(); // Custom filesystem with explicit mounts (advanced) -use bashkit::MountableFs; -let fs = Arc::new(MountableFs::new()); -// fs.mount_readonly("/data", "/real/path/to/data"); // Optional real FS access +let root = Arc::new(InMemoryFs::new()); +let fs = Arc::new(MountableFs::new(root)); +// fs.mount("/data", Arc::new(InMemoryFs::new())); // Mount additional filesystems +# } ``` ### Information Disclosure (TM-INF-*) @@ -118,7 +122,8 @@ Scripts may attempt to leak sensitive information. Do NOT pass sensitive environment variables to untrusted scripts: -```rust,ignore +```rust +# use bashkit::Bash; // UNSAFE - secrets may be leaked let bash = Bash::builder() .env("DATABASE_URL", "postgres://user:pass@host/db") @@ -136,7 +141,8 @@ let bash = Bash::builder() System builtins return configurable virtual values, never real host information: -```rust,ignore +```rust +# use bashkit::Bash; let bash = Bash::builder() .username("sandbox") // whoami returns "sandbox" .hostname("bashkit-sandbox") // hostname returns "bashkit-sandbox" @@ -227,10 +233,11 @@ echo $user_input Each [`Bash`] instance is fully isolated. For multi-tenant environments, create separate instances per tenant: -```rust,ignore +```rust use bashkit::{Bash, InMemoryFs}; use std::sync::Arc; +# fn main() { // Each tenant gets completely isolated instance let tenant_a = Bash::builder() .fs(Arc::new(InMemoryFs::new())) // Separate filesystem @@ -241,6 +248,7 @@ let tenant_b = Bash::builder() .build(); // tenant_a cannot access tenant_b's files or state +# } ``` ### Internal Error Handling (TM-INT-*) diff --git a/crates/bashkit/src/lib.rs b/crates/bashkit/src/lib.rs index 7a9d6fbd..4edb7eb4 100644 --- a/crates/bashkit/src/lib.rs +++ b/crates/bashkit/src/lib.rs @@ -243,11 +243,9 @@ //! //! Enable the `http_client` feature and configure an allowlist for network access: //! -//! ```rust,no_run +//! ```rust,ignore //! use bashkit::{Bash, NetworkAllowlist}; //! -//! # #[tokio::main] -//! # async fn main() -> bashkit::Result<()> { //! let mut bash = Bash::builder() //! .network(NetworkAllowlist::new() //! .allow("https://httpbin.org")) @@ -256,8 +254,6 @@ //! // curl and wget now work for allowed URLs //! let result = bash.exec("curl -s https://httpbin.org/get").await?; //! assert!(result.stdout.contains("httpbin.org")); -//! # Ok(()) -//! # } //! ``` //! //! Security features: diff --git a/crates/bashkit/src/network/mod.rs b/crates/bashkit/src/network/mod.rs index 38e353ad..c758c941 100644 --- a/crates/bashkit/src/network/mod.rs +++ b/crates/bashkit/src/network/mod.rs @@ -18,11 +18,9 @@ //! //! Configure network access using [`NetworkAllowlist`] with [`crate::Bash::builder`]: //! -//! ```rust,no_run +//! ```rust,ignore //! use bashkit::{Bash, NetworkAllowlist}; //! -//! # #[tokio::main] -//! # async fn main() -> bashkit::Result<()> { //! let mut bash = Bash::builder() //! .network(NetworkAllowlist::new() //! .allow("https://api.example.com") @@ -31,8 +29,6 @@ //! //! // Now curl/wget can access allowed URLs //! let result = bash.exec("curl -s https://api.example.com/data").await?; -//! # Ok(()) -//! # } //! ``` //! //! # Allowlist Patterns diff --git a/specs/008-documentation.md b/specs/008-documentation.md index d872debb..2d4ae02d 100644 --- a/specs/008-documentation.md +++ b/specs/008-documentation.md @@ -84,8 +84,47 @@ Add "Guides" section and link to scorecard in main crate documentation: 4. Add link in crate docs `# Guides` section 5. Run `cargo doc --open` to verify +## Code Examples + +Rust code examples in guides are compiled and tested by `cargo test --doc`. + +### Fencing rules + +| Fence | When to use | +|-------|-------------| +| `` ```rust `` | Complete examples using only bashkit types — tested | +| `` ```rust,no_run `` | Complete examples that compile but shouldn't execute | +| `` ```rust,ignore `` | Uses external crates (sqlx, reqwest, tracing-subscriber) or feature-gated APIs in non-gated modules | + +### Making examples testable + +Use `# ` (hash-space) prefix to hide boilerplate lines from rendered docs while +keeping them in the compiled test: + +````markdown +```rust +# use bashkit::Bash; +# #[tokio::main] +# async fn main() -> bashkit::Result<()> { +let mut bash = Bash::new(); +let result = bash.exec("echo hello").await?; +assert_eq!(result.stdout, "hello\n"); +# Ok(()) +# } +``` +```` + +### Feature-gated modules + +Doc modules behind `#[cfg(feature = "...")]` (e.g., `python_guide`, `logging_guide`) +can use feature-gated APIs freely — their tests only run when the feature is enabled. + +Non-gated modules (e.g., `threat_model`, `compatibility_scorecard`) must NOT use +feature-gated APIs in tested examples. Use `rust,ignore` for those. + ## Verification - `cargo doc` builds without errors +- `cargo test --doc --all-features` passes - Links resolve correctly in generated docs - Markdown renders properly in rustdoc diff --git a/supply-chain/config.toml b/supply-chain/config.toml index 2161bd3d..69900972 100644 --- a/supply-chain/config.toml +++ b/supply-chain/config.toml @@ -442,6 +442,10 @@ criteria = "safe-to-deploy" version = "0.4.1" criteria = "safe-to-run" +[[exemptions.getrandom]] +version = "0.4.2" +criteria = "safe-to-run" + [[exemptions.globset]] version = "0.4.18" criteria = "safe-to-deploy" @@ -910,6 +914,10 @@ criteria = "safe-to-deploy" version = "5.3.0" criteria = "safe-to-deploy" +[[exemptions.r-efi]] +version = "6.0.0" +criteria = "safe-to-deploy" + [[exemptions.rand]] version = "0.8.5" criteria = "safe-to-deploy" @@ -1270,6 +1278,10 @@ criteria = "safe-to-deploy" version = "1.49.0" criteria = "safe-to-deploy" +[[exemptions.tokio]] +version = "1.50.0" +criteria = "safe-to-deploy" + [[exemptions.tokio-macros]] version = "2.6.0" criteria = "safe-to-deploy"