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.1a1.tar.gz (16.1 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.1a1-py3-none-any.whl (13.9 kB view details)

Uploaded Python 3

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

Hashes for denobox-0.1a1.tar.gz
Algorithm Hash digest
SHA256 774f8201c867c2d9b1e416bde2de4efb8dc48613481697dc69991a0848a00bcd
MD5 bfa0e184421529960f940c1ca4ce253f
BLAKE2b-256 c82bc34ade7370d7c9179d896a0e25597340cc2eda0d889c2edbbd7066c0c3b7

See more details on using hashes here.

Provenance

The following attestation bundles were made for denobox-0.1a1.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.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

Hashes for denobox-0.1a1-py3-none-any.whl
Algorithm Hash digest
SHA256 163aa08e08883fe6c80c572793b6e20cd32114d8ec8d7819c78ea7d63864e501
MD5 b4a6c5c236fa2b108190f786aba53bef
BLAKE2b-256 13731ef5d0df40bf1a5a54d76373f5dbee4c7282ef8687ebb525a2bd1c4e146e

See more details on using hashes here.

Provenance

The following attestation bundles were made for denobox-0.1a1-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