Skip to content

Latest commit

 

History

History
216 lines (160 loc) · 5.9 KB

File metadata and controls

216 lines (160 loc) · 5.9 KB

Embedded Python (Monty)

Experimental. Monty is an early-stage Python interpreter that may have undiscovered crash or security bugs. Resource limits are enforced by Monty's runtime. The integration should be treated as experimental.

Bashkit embeds the Monty Python interpreter, a pure-Rust implementation of Python 3.12. Python runs entirely in-memory with configurable resource limits and no host access.

See also:

Quick Start

Enable the python feature and register via builder:

use bashkit::Bash;

# #[tokio::main]
# async fn main() -> bashkit::Result<()> {
let mut bash = Bash::builder()
    .python()
    .env("BASHKIT_ALLOW_INPROCESS_PYTHON", "1")
    .build();

let result = bash.exec("python3 -c \"print('hello from Monty')\"").await?;
assert_eq!(result.stdout, "hello from Monty\n");
# Ok(())
# }

Usage Patterns

Inline Code

python3 -c "print(2 ** 10)"
# Output: 1024

Expression Evaluation

When no print() is called, the last expression is displayed (REPL behavior):

python3 -c "2 + 2"
# Output: 4

Script Files (from VFS)

cat > /tmp/script.py << 'EOF'
data = [1, 2, 3, 4, 5]
print(f"sum={sum(data)}, avg={sum(data)/len(data)}")
EOF
python3 /tmp/script.py

Pipelines and Command Substitution

result=$(python3 -c "print(42 * 3)")
echo "Result: $result"

echo "print('piped')" | python3

Virtual Filesystem (VFS) Bridging

Python open() and pathlib.Path operations are bridged to Bashkit's virtual filesystem. Files created by bash are readable from Python and vice versa.

Bash → Python

echo "important data" > /tmp/shared.txt
python3 -c "
content = open('/tmp/shared.txt').read()
print(f'Got: {content.strip()}')
"

Python → Bash

python3 -c "
with open('/tmp/result.txt', 'w') as f:
    _ = f.write('computed by python\n')
"
cat /tmp/result.txt

Supported File Operations

Operation Example
Open/read open('f.txt').read()
Open/write open('f.txt', 'w').write('data')
Open/append open('f.txt', 'a').write('more')
Path open Path('f.txt').open('r')
Read text Path('f.txt').read_text()
Read bytes Path('f.txt').read_bytes()
Write text Path('f.txt').write_text('data')
Write bytes Path('f.txt').write_bytes(b'data')
Exists Path('f.txt').exists()
Is file/dir Path('f.txt').is_file(), .is_dir()
Mkdir Path('d').mkdir(parents=True, exist_ok=True)
Delete Path('f.txt').unlink()
List dir Path('.').iterdir()
Stat Path('f.txt').stat().st_size
Rename Path('old').rename('new')
Env vars os.getenv('KEY'), os.environ

Architecture

Python code → Monty VM → OsCall(Open/ReadText, path) → Bashkit VFS → resume

Monty pauses at filesystem operations, Bashkit bridges them to the VFS, then resumes execution with the result (or a Python exception like FileNotFoundError).

Resource Limits

Default limits prevent runaway Python code. Customize via PythonLimits:

use bashkit::{Bash, PythonLimits};
use std::time::Duration;

# fn main() {
let bash = Bash::builder()
    .python_with_limits(
        PythonLimits::default()
            .max_duration(Duration::from_secs(5))
            .max_memory(16 * 1024 * 1024)   // 16 MB
            .max_allocations(100_000)
            .max_recursion(50)
    )
    .build();
# }
Limit Default Purpose
Allocations 1,000,000 Heap allocation cap
Duration 30 seconds Execution timeout
Memory 64 MB Heap memory cap
Recursion 200 Call stack depth

LLM Tool Integration

When using BashTool for AI agents, call .python() on the tool builder:

use bashkit::{BashTool, Tool};

# fn main() {
let tool = BashTool::builder()
    .python()
    .build();

// help() and system_prompt() automatically document Python limitations
let help = tool.help();  // Includes a Markdown Notes section with Python hints
# }

The builtin's llm_hint() is automatically included in the tool's documentation, so LLMs know file I/O is VFS-scoped and HTTP requests/classes are unavailable.

Limitations

VFS-only file I/O. open() and pathlib.Path read/write Bashkit's virtual filesystem, not the host filesystem:

from pathlib import Path

with open('/tmp/data.txt', 'w') as f:
    f.write('hello')

content = Path('/tmp/data.txt').read_text()

No HTTP/network. No socket, urllib, requests, or http.client modules. Monty has no network primitives and no OsCall variants for network operations.

No classes. Class definitions are not yet supported by Monty (planned upstream).

No third-party imports. Only builtin modules (sys, typing, os, pathlib, math, json, datetime) are available. No pip install, no import numpy.

No re module. Disabled due to catastrophic backtracking DoS risk in untrusted code execution.

No str.format(). Use f-strings instead: f"value={x}" not "value={}".format(x).

Security

All Python execution runs in a virtual environment:

  • No host filesystem access — all paths resolve through the VFS
  • No network access — no sockets, HTTP, or DNS
  • No process spawning — no os.system(), subprocess, or __import__('os')
  • Resource limited — allocation, time, memory, and recursion caps
  • Path traversal safe../.. is resolved by VFS path normalization

See threat IDs TM-PY-001 through TM-PY-029 in the threat model.