Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/bashkit/docs/compatibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ Default limits (configurable):

### Network Configuration

```rust
```rust,ignore
use bashkit::{Bash, NetworkAllowlist};

// Enable network with URL allowlist
Expand Down
20 changes: 14 additions & 6 deletions crates/bashkit/docs/custom_builtins.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ virtual filesystem.

## Quick Start

```rust,ignore
```rust
use bashkit::{Bash, Builtin, BuiltinContext, ExecResult, async_trait};

struct MyCommand;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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]
Expand All @@ -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
Expand All @@ -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<std::sync::atomic::AtomicU64>,
}
Expand Down
7 changes: 5 additions & 2 deletions crates/bashkit/docs/logging.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -90,6 +91,7 @@ let bash = Bash::builder()
// Limit logged value lengths
.max_value_length(100))
.build();
# }
```

## Security (TM-LOG-*)
Expand Down Expand Up @@ -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
Expand Down
16 changes: 12 additions & 4 deletions crates/bashkit/docs/python.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()
Expand All @@ -133,6 +138,7 @@ let bash = Bash::builder()
.max_recursion(50)
)
.build();
# }
```

| Limit | Default | Purpose |
Expand All @@ -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,
Expand Down
26 changes: 17 additions & 9 deletions crates/bashkit/docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -67,6 +68,7 @@ let bash = Bash::builder()
.limits(limits)
.fs(fs)
.build();
# }
```

### Sandbox Escape (TM-ESC-*)
Expand All @@ -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-*)
Expand All @@ -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")
Expand All @@ -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"
Expand Down Expand Up @@ -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
Expand All @@ -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-*)
Expand Down
6 changes: 1 addition & 5 deletions crates/bashkit/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand All @@ -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:
Expand Down
6 changes: 1 addition & 5 deletions crates/bashkit/src/network/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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
Expand Down
39 changes: 39 additions & 0 deletions specs/008-documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
12 changes: 12 additions & 0 deletions supply-chain/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down
Loading