Skip to content

Commit 36fc043

Browse files
committed
feat(python): support vfs-backed open
1 parent 35fd500 commit 36fc043

16 files changed

Lines changed: 356 additions & 76 deletions

File tree

‎Cargo.lock‎

Lines changed: 23 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎README.md‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,17 +341,17 @@ bash.exec("python3 -c \"print(2 ** 10)\"").await?;
341341
// Script files from VFS
342342
bash.exec("python3 /tmp/script.py").await?;
343343

344-
// VFS bridging: pathlib.Path operations work with the virtual filesystem
344+
// VFS bridging: open() and pathlib.Path work with the virtual filesystem
345345
bash.exec(r#"python3 -c "
346-
from pathlib import Path
347-
Path('/tmp/data.txt').write_text('hello from python')
346+
with open('/tmp/data.txt', 'w') as f:
347+
f.write('hello from python')
348348
""#).await?;
349349
bash.exec("cat /tmp/data.txt").await?; // "hello from python"
350350
```
351351

352352
Stdlib modules: `math`, `pathlib`, `os` (getenv/environ), `sys`, `typing`.
353353
Security note: `re` is intentionally disabled due to regex backtracking DoS risk.
354-
Limitations: no `open()` (use `pathlib.Path`), no network, no classes, no third-party imports.
354+
Limitations: file I/O is VFS-scoped, no network, no classes, no third-party imports.
355355
See [crates/bashkit/docs/python.md](crates/bashkit/docs/python.md) for the full guide.
356356

357357
## Experimental: TypeScript Support

‎crates/bashkit/Cargo.toml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ unit-prefix = "0.5"
9898
os_display = "0.1.3"
9999

100100
# Embedded Python interpreter (optional)
101-
monty = { git = "https://github.com/pydantic/monty", rev = "49faa4c", optional = true }
101+
monty = { git = "https://github.com/pydantic/monty", rev = "9b5f478", optional = true }
102102

103103
# Embedded TypeScript interpreter (optional)
104104
zapcode-core = { version = "1.5", optional = true }

‎crates/bashkit/docs/hooks.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -243,7 +243,7 @@ boundary stays in bashkit — hooks cannot bypass the allowlist.
243243

244244
### `before_http` — Filter or Modify Requests
245245

246-
```rust
246+
```rust,ignore
247247
use bashkit::{Bash, NetworkAllowlist, hooks::{HookAction, HttpRequestEvent}};
248248
249249
# fn main() {
@@ -266,7 +266,7 @@ let bash = Bash::builder()
266266

267267
### `after_http` — Observe Responses
268268

269-
```rust
269+
```rust,ignore
270270
use bashkit::{Bash, NetworkAllowlist, hooks::{HookAction, HttpResponseEvent}};
271271
use std::sync::{Arc, Mutex};
272272

‎crates/bashkit/docs/python.md‎

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -73,16 +73,15 @@ echo "print('piped')" | python3
7373

7474
## Virtual Filesystem (VFS) Bridging
7575

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

7979
### Bash → Python
8080

8181
```bash
8282
echo "important data" > /tmp/shared.txt
8383
python3 -c "
84-
from pathlib import Path
85-
content = Path('/tmp/shared.txt').read_text()
84+
content = open('/tmp/shared.txt').read()
8685
print(f'Got: {content.strip()}')
8786
"
8887
```
@@ -91,16 +90,20 @@ print(f'Got: {content.strip()}')
9190

9291
```bash
9392
python3 -c "
94-
from pathlib import Path
95-
_ = Path('/tmp/result.txt').write_text('computed by python\n')
93+
with open('/tmp/result.txt', 'w') as f:
94+
_ = f.write('computed by python\n')
9695
"
9796
cat /tmp/result.txt
9897
```
9998

100-
### Supported Path Operations
99+
### Supported File Operations
101100

102101
| Operation | Example |
103102
|-----------|---------|
103+
| Open/read | `open('f.txt').read()` |
104+
| Open/write | `open('f.txt', 'w').write('data')` |
105+
| Open/append | `open('f.txt', 'a').write('more')` |
106+
| Path open | `Path('f.txt').open('r')` |
104107
| Read text | `Path('f.txt').read_text()` |
105108
| Read bytes | `Path('f.txt').read_bytes()` |
106109
| Write text | `Path('f.txt').write_text('data')` |
@@ -117,7 +120,7 @@ cat /tmp/result.txt
117120
### Architecture
118121

119122
```text
120-
Python code → Monty VM → OsCall(ReadText, path) → Bashkit VFS → resume
123+
Python code → Monty VM → OsCall(Open/ReadText, path) → Bashkit VFS → resume
121124
```
122125

123126
Monty pauses at filesystem operations, Bashkit bridges them to the VFS, then
@@ -169,19 +172,20 @@ let help = tool.help(); // Includes a Markdown Notes section with Python hints
169172
```
170173

171174
The builtin's `llm_hint()` is automatically included in the tool's documentation,
172-
so LLMs know not to generate code using `open()`, HTTP requests, or classes.
175+
so LLMs know file I/O is VFS-scoped and HTTP requests/classes are unavailable.
173176

174177
## Limitations
175178

176-
**No `open()` builtin.** Monty does not implement Python's `open()`. Use `pathlib.Path` instead:
179+
**VFS-only file I/O.** `open()` and `pathlib.Path` read/write Bashkit's virtual
180+
filesystem, not the host filesystem:
177181

178182
```python
179-
# Won't work:
180-
# f = open('data.txt')
181-
182-
# Use instead:
183183
from pathlib import Path
184-
content = Path('data.txt').read_text()
184+
185+
with open('/tmp/data.txt', 'w') as f:
186+
f.write('hello')
187+
188+
content = Path('/tmp/data.txt').read_text()
185189
```
186190

187191
**No HTTP/network.** No `socket`, `urllib`, `requests`, or `http.client` modules.

‎crates/bashkit/docs/threat-model.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -554,15 +554,15 @@ attacks:
554554
### Python / Monty Security (TM-PY-*)
555555

556556
The `python`/`python3` builtins embed the Monty Python interpreter with VFS bridging.
557-
Python `pathlib.Path` operations are bridged to Bashkit's virtual filesystem.
557+
Python `pathlib.Path` and `open()` operations are bridged to Bashkit's virtual filesystem.
558558

559559
| Threat | Attack Example | Mitigation | Status |
560560
|--------|---------------|------------|--------|
561561
| Infinite loop (TM-PY-001) | `while True: pass` | Monty time limit (30s) + allocation cap | MITIGATED |
562562
| Memory exhaustion (TM-PY-002) | Large allocation | Monty max_memory (64MB) + max_allocations (1M) | MITIGATED |
563563
| Stack overflow (TM-PY-003) | Deep recursion | Monty max_recursion (200) | MITIGATED |
564564
| Shell escape (TM-PY-004) | `os.system()` | Monty has no os.system/subprocess | MITIGATED |
565-
| Real FS access (TM-PY-005) | `open()` | Monty has no open() builtin | MITIGATED |
565+
| Real FS access (TM-PY-005) | `open()` | VFS bridge opens only Bashkit VFS files | MITIGATED |
566566
| Error info leak (TM-PY-006) | Errors go to stdout | Errors go to stderr, not stdout | MITIGATED |
567567
| Real FS read (TM-PY-015) | `Path.read_text()` | VFS bridge reads only from Bashkit VFS | MITIGATED |
568568
| Real FS write (TM-PY-016) | `Path.write_text()` | VFS bridge writes only to Bashkit VFS | MITIGATED |

0 commit comments

Comments
 (0)