Execute JavaScript and WebAssembly in a Deno sandbox from Python
Project description
denobox
A Python library for executing JavaScript and WebAssembly in a Deno sandbox.
Overview
denobox provides a simple interface to run JavaScript code and WebAssembly modules from Python using Deno as the runtime. It communicates with a Deno subprocess using a newline-delimited JSON (NDJSON) protocol over stdin/stdout.
Features
- Execute JavaScript code in a fully sandboxed Deno environment
- Load and call WebAssembly modules
- Both synchronous and async APIs
- JSON-based data exchange between Python and JavaScript
- Promise resolution handled automatically
- Thread-safe (sync) and concurrent (async) execution support
Security
Deno runs with zero permissions - maximum sandboxing:
- No file system access - WASM files are read by Python and sent as base64
- No network access - cannot make HTTP requests or open sockets
- No subprocess spawning - cannot execute shell commands
- No environment access - cannot read environment variables
The sandbox is enforced by Deno's permission system. Any attempt to access restricted resources will raise an error.
Warning: The sandbox is only as secure as Deno itself. See Deno's documentation on executing untrusted code for important security considerations.
Installation
pip install denobox
Or with uv:
uv add denobox
Requirements
- Python 3.10+
- The
denoPyPI package (automatically installed)
Usage
JavaScript Execution
Synchronous API
from denobox import DenoBox
with DenoBox() as box:
# Simple expressions
result = box.eval("1 + 1")
print(result) # 2
# Strings
result = box.eval("'hello' + ' ' + 'world'")
print(result) # "hello world"
# Arrays and objects
result = box.eval("[1, 2, 3].map(x => x * 2)")
print(result) # [2, 4, 6]
result = box.eval("({name: 'test', value: 42})")
print(result) # {'name': 'test', 'value': 42}
# State persists between evals
box.eval("var x = 10")
box.eval("var y = 20")
result = box.eval("x + y")
print(result) # 30
# Promises are automatically resolved
result = box.eval("Promise.resolve(42)")
print(result) # 42
# Async functions work too
result = box.eval("(async () => { return 'async result'; })()")
print(result) # "async result"
Asynchronous API
import asyncio
from denobox import AsyncDenoBox
async def main():
async with AsyncDenoBox() as box:
result = await box.eval("1 + 1")
print(result) # 2
# Concurrent execution
results = await asyncio.gather(
box.eval("1 + 1"),
box.eval("2 + 2"),
box.eval("3 + 3"),
)
print(results) # [2, 4, 6]
asyncio.run(main())
WebAssembly
Synchronous API
from denobox import DenoBox
with DenoBox() as box:
# Load a WASM module from a file (Python reads and sends to Deno)
module = box.load_wasm("path/to/module.wasm")
# Or load from raw bytes
wasm_bytes = open("path/to/module.wasm", "rb").read()
module = box.load_wasm(wasm_bytes=wasm_bytes)
# Check available exports
print(module.exports) # {'add': 'function', 'multiply': 'function'}
# Call exported functions
result = module.call("add", 3, 4)
print(result) # 7
result = module.call("multiply", 5, 6)
print(result) # 30
# Unload when done (optional, cleaned up on box close)
module.unload()
Asynchronous API
import asyncio
from denobox import AsyncDenoBox
async def main():
async with AsyncDenoBox() as box:
module = await box.load_wasm("path/to/module.wasm")
# Concurrent calls
results = await asyncio.gather(
module.call("add", 1, 2),
module.call("add", 3, 4),
module.call("multiply", 5, 6),
)
print(results) # [3, 7, 30]
await module.unload()
asyncio.run(main())
Error Handling
from denobox import DenoBox, DenoBoxError
with DenoBox() as box:
try:
box.eval("throw new Error('Something went wrong')")
except DenoBoxError as e:
print(f"JavaScript error: {e}")
try:
box.eval("invalid javascript {{{")
except DenoBoxError as e:
print(f"Syntax error: {e}")
Architecture
NDJSON Protocol
Communication between Python and the Deno subprocess uses newline-delimited JSON:
Requests:
{"id": 1, "type": "eval", "code": "1 + 1"}
{"id": 2, "type": "load_wasm", "bytes": "<base64-encoded-wasm>"}
{"id": 3, "type": "call_wasm", "moduleId": "wasm_0", "func": "add", "args": [1, 2]}
{"id": 4, "type": "unload_wasm", "moduleId": "wasm_0"}
{"id": 5, "type": "shutdown"}
Note: WASM modules are sent as base64-encoded bytes. Python reads the file and encodes it, so Deno doesn't need file system access.
Responses:
{"id": 1, "result": 2}
{"id": 2, "result": {"moduleId": "wasm_0", "exports": {"add": "function"}}}
{"id": 3, "result": 3}
{"id": 4, "result": true}
{"id": 5, "result": true, "shutdown": true}
Errors:
{"id": 1, "error": "ReferenceError: x is not defined", "stack": "..."}
Components
- DenoBox - Synchronous wrapper using subprocess.Popen with thread-safe locking
- AsyncDenoBox - Asynchronous wrapper using asyncio.create_subprocess_exec with a background reader task
- WasmModule / AsyncWasmModule - Wrappers for loaded WebAssembly modules
- worker.js - Deno script that handles the NDJSON protocol
Development
# Clone and setup
git clone <repo>
cd denobox
uv sync --all-extras
# Run tests
uv run pytest
# Run tests with verbose output
uv run pytest -v
License
Apache 2.0
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file denobox-0.1a1.tar.gz.
File metadata
- Download URL: denobox-0.1a1.tar.gz
- Upload date:
- Size: 16.1 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
774f8201c867c2d9b1e416bde2de4efb8dc48613481697dc69991a0848a00bcd
|
|
| MD5 |
bfa0e184421529960f940c1ca4ce253f
|
|
| BLAKE2b-256 |
c82bc34ade7370d7c9179d896a0e25597340cc2eda0d889c2edbbd7066c0c3b7
|
Provenance
The following attestation bundles were made for denobox-0.1a1.tar.gz:
Publisher:
publish.yml on simonw/denobox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
denobox-0.1a1.tar.gz -
Subject digest:
774f8201c867c2d9b1e416bde2de4efb8dc48613481697dc69991a0848a00bcd - Sigstore transparency entry: 813260659
- Sigstore integration time:
-
Permalink:
simonw/denobox@3162a144bf9b3c503f7fb8f248a396dad9046d36 -
Branch / Tag:
refs/tags/0.1a1 - Owner: https://github.com/simonw
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3162a144bf9b3c503f7fb8f248a396dad9046d36 -
Trigger Event:
release
-
Statement type:
File details
Details for the file denobox-0.1a1-py3-none-any.whl.
File metadata
- Download URL: denobox-0.1a1-py3-none-any.whl
- Upload date:
- Size: 13.9 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
163aa08e08883fe6c80c572793b6e20cd32114d8ec8d7819c78ea7d63864e501
|
|
| MD5 |
b4a6c5c236fa2b108190f786aba53bef
|
|
| BLAKE2b-256 |
13731ef5d0df40bf1a5a54d76373f5dbee4c7282ef8687ebb525a2bd1c4e146e
|
Provenance
The following attestation bundles were made for denobox-0.1a1-py3-none-any.whl:
Publisher:
publish.yml on simonw/denobox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
denobox-0.1a1-py3-none-any.whl -
Subject digest:
163aa08e08883fe6c80c572793b6e20cd32114d8ec8d7819c78ea7d63864e501 - Sigstore transparency entry: 813260662
- Sigstore integration time:
-
Permalink:
simonw/denobox@3162a144bf9b3c503f7fb8f248a396dad9046d36 -
Branch / Tag:
refs/tags/0.1a1 - Owner: https://github.com/simonw
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3162a144bf9b3c503f7fb8f248a396dad9046d36 -
Trigger Event:
release
-
Statement type: