Skip to main content

Execute JavaScript and WebAssembly in a Deno sandbox from Python

Project description

denobox

PyPI Tests Changelog License

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 deno PyPI 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

  1. DenoBox - Synchronous wrapper using subprocess.Popen with thread-safe locking
  2. AsyncDenoBox - Asynchronous wrapper using asyncio.create_subprocess_exec with a background reader task
  3. WasmModule / AsyncWasmModule - Wrappers for loaded WebAssembly modules
  4. 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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

denobox-0.1a0.tar.gz (12.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

denobox-0.1a0-py3-none-any.whl (12.4 kB view details)

Uploaded Python 3

File details

Details for the file denobox-0.1a0.tar.gz.

File metadata

  • Download URL: denobox-0.1a0.tar.gz
  • Upload date:
  • Size: 12.9 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for denobox-0.1a0.tar.gz
Algorithm Hash digest
SHA256 085203cb5b0e4907aacb1b98c7b065dbd3c6ee80544c2095fb398bba296ccb15
MD5 69dde24b996845ded102d31d99bc48ca
BLAKE2b-256 3c18f22b4557aef32fb5a3c5b6514a70d3bb6ebbea1c76c921cc57b2d88de949

See more details on using hashes here.

Provenance

The following attestation bundles were made for denobox-0.1a0.tar.gz:

Publisher: publish.yml on simonw/denobox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file denobox-0.1a0-py3-none-any.whl.

File metadata

  • Download URL: denobox-0.1a0-py3-none-any.whl
  • Upload date:
  • Size: 12.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for denobox-0.1a0-py3-none-any.whl
Algorithm Hash digest
SHA256 231e150cdcb6b7aa6d1311a9088470c0caec2ee7fa7eb460592b2b6f604431ea
MD5 2743a1bb4db129532ab3e02a16c25645
BLAKE2b-256 3336158770da3b36454e37731d598749a6e95b76f19f713ca3a65d7637d78b4c

See more details on using hashes here.

Provenance

The following attestation bundles were made for denobox-0.1a0-py3-none-any.whl:

Publisher: publish.yml on simonw/denobox

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page