bashkit

Security in Bashkit#

Bashkit is a virtual Bash interpreter designed for safe, sandboxed script execution. Security is a first-class concern, every design decision considers what an untrusted script could do and how to prevent it.

This article gives a high-level overview. For the full threat model with individual threat IDs and mitigation status, see the rustdoc threat model guide.

Core security boundaries#

BoundaryWhat it does
Virtual filesystemScripts run against an in-memory VFS. No real filesystem access by default. Path traversal (../../../etc/passwd) is normalised away. Symlinks are followed inside the VFS only: a target is always a VFS path, so a link can never reach the host.
No process executionexec is excluded entirely. bash -c re-invokes the virtual interpreter instead of spawning a real process. Background jobs (&) parse but run synchronously.
Network allowlistHTTP/HTTPS only, pre-validated against an explicit host allowlist. No DNS resolution, no auto-redirect, no auto-decompression.
Resource limitsConfigurable caps on commands, loop iterations, recursion depth, AST depth, timeouts, and parser operations prevent denial-of-service from malicious scripts.
Filesystem limitsTotal bytes, per-file size, file count, path depth, and filename length are all capped to prevent storage exhaustion (zip bombs, tar bombs, recursive copies).

od -w / od --width accepts at most 65,536 bytes per row. Larger widths fail before rendering, even with empty input. Numeric fields use at most eight bytes of stack padding; output capacity is admitted against ExecutionLimits::max_live_intermediate_bytes before fallible allocation. This memory budget applies to pipelines and redirected output too.

cd searches CDPATH one candidate at a time. Temporary path and normalization storage is reserved against max_live_intermediate_bytes before allocation. Searches also consume the shared work budget and check cancellation and deadlines, including when every candidate is missing.

Command hash entries (hash -p FILE NAME... and automatic PATH lookups) charge each retained name and pathname against the same live-byte budget before copying. Entries that survive between executions count toward the next execution’s budget. Deleting, replacing, evicting or clearing entries releases their storage; subshells share pathname storage while maintaining independent hash state. The table also retains its 512-entry ceiling.

MemoryLimits::max_function_body_bytes also includes retained function-definition filenames and metadata keys. Filename storage is shared; each function is charged conservatively before insertion. An oversized source operand therefore fails with a resource-limit error even when redundant slashes normalize to a short VFS path. Redefinition replaces the charge, unset releases it, and subshell rollback restores filenames together with functions. Executable child scripts discard their own metadata on return. Functions and their admitted metadata persist across executions; shell-state restore discards previous filenames because the serialized state carries function source without definition filenames.

Recursive grep -R skips directory symlinks that resolve to an ancestor and reports warning: recursive directory loop (suppressed by -s). Separate aliases of a non-ancestor directory remain searchable. Traversal uses the shared execution work and deadline budget; file contents are admitted against aggregate input and live intermediate limits and scanned one file at a time. -q stops at the first match, before further traversal.

Threat model#

Bashkit maintains a living threat model in knowledge/security/threat-model.md with stable threat IDs across these categories:

CategoryID prefixExamples
Denial of ServiceTM-DOSResource exhaustion, infinite loops, parser bombs
Sandbox EscapeTM-ESCPath traversal, real FS access, privilege escalation
Information DisclosureTM-INFSecret leakage, host info exposure, data exfiltration
InjectionTM-INJCommand injection, variable namespace pollution
NetworkTM-NETDNS rebinding, allowlist bypass, response flooding
Multi-Tenant IsolationTM-ISOCross-tenant data leaks
Internal ErrorsTM-INTPanics, error message information leaks
GitTM-GITRepo access control, remote URL injection
LoggingTM-LOGSensitive data in logs, log injection
Python SandboxTM-PYMonty resource limits, VFS bridge escapes
UnicodeTM-UNIByte-boundary panics, homoglyph attacks

The full threat model, including mitigation status for each threat, is published in the rustdoc: bashkit::threat_model.

POSIX deviations for security#

Bashkit intentionally deviates from POSIX where compliance would compromise the sandbox. Key exclusions:

  • exec: would break sandbox containment (TM-ESC-005)
  • trap: conflicts with the stateless execution model
  • Real process spawning: all subprocess commands stay within the virtual interpreter (TM-ESC-015)

These decisions are documented in knowledge/operations/limitations.md.

Security testing#

Bashkit uses multiple layers of security testing:

Threat model tests: 232 tests in threat_model_tests.rs that directly validate mitigations against documented threat IDs. Each test maps to a specific TM-* threat.

Fail-point injection: A framework defined in knowledge/security/security-testing.md that injects failures at specific points to verify the interpreter handles them safely. 14+ tests in security_failpoint_tests.rs.

Network security tests: 68 tests covering allowlist enforcement, URL validation, timeout behaviour, and response limits.

Error handling tests: 39 tests verifying that builtins wrapped with catch_unwind never leak panic messages, stack traces, or memory addresses.

Logging security tests: 26 tests confirming that sensitive data (passwords, tokens, API keys, JWTs) is redacted in logs and that log injection is prevented.

Fuzz testing: Parser and lexer fuzzing to catch panics and unexpected behaviour on malformed input.

Differential tests: Compare Bashkit output against real Bash to ensure behaviour parity where expected, and confirm intentional divergences where security requires it.

Panic safety#

All builtin commands are wrapped with catch_unwind. If a builtin panics, the error is caught and converted to a sanitised error message, no stack traces, no memory addresses, no real filesystem paths leak to the caller (TM-INT-001, TM-INT-002).

Reporting security issues#

Do not open a public GitHub issue for security vulnerabilities.

Email: security@everruns.com

Please include a description of the vulnerability, steps to reproduce, and potential impact. We acknowledge reports within 48 hours, provide an initial assessment within 7 days, and target 30-day resolution for critical issues.

See SECURITY.md for the full policy.

We appreciate responsible disclosure and acknowledge researchers who report valid vulnerabilities (with permission).