Skip to main content

Reusable detonation framework: run untrusted documents through disposable, hardened workers

Project description

blastbox

A reusable framework for running untrusted/malicious documents through disposable, hardened workers — and turning their output into a typed, host-validated result you can trust.

Extracted from the common substrate of two production services (a LibreOffice document→image rasterizer and a Tika recursive-extraction service) that had each grown a parallel, drifting copy of the same machinery. blastbox is the single, audited-once core; each service becomes a thin engine.

  • Write one function. An engine implements detonate(input, outdir, limits) -> DetonationResult. The framework gives it ingress, a JobStore, a disposable hardened worker per job, output-trust validation, artifact serving, optional warm pooling, metrics, and a CLI — for free.
  • Output is never trusted. A worker processes a malicious document; the host re-seals its output from disk (recomputing hashes/sizes, confining paths) before believing a byte of it.
  • One untrusted doc per disposable slot — always. Warm pooling pre-pays startup in the background; it never reuses a worker across documents.
  • Typed, engine-shaped output. A shared node library (Page, EmbeddedResource, ExtractedText, a generic Record floor, recursive) lets engines be as specific or generic as they need, while the framework validates a fixed security envelope identically for everyone.

Proven end-to-end against two real, language-diverse engines: a Python + LibreOffice rasterizer and a JVM + Tika recursive extractor.

Architecture

blastbox/
├── contract/   typed node tree + security envelope + seal/validate (registry-aware at any depth)
├── host/       LAYER 1 — host orchestrator (engine-agnostic) — needs blastbox[host]
│   ├── ingress     FastAPI API + CLI: upload, status, artifact serving, /metrics
│   ├── jobs/       JobStore protocol + memory / sql / redis backends + retention
│   ├── dispatch    claim → launch disposable worker → validate output → serve   (+ warm path)
│   ├── runtime/    runc/runsc selection (fail-closed) + hardened worker `docker run` argv
│   ├── pool        warm slot pool (one-doc-per-slot, never-reuse)
│   ├── trust       output-trust validator — re-seals worker output from disk
│   └── observability
└── worker/     LAYER 2 — worker SDK (runs inside the disposable worker) — lean core
    ├── engine      the seam: detect / warmup / detonate
    ├── harness     read input → detonate → seal → write metadata.json
    ├── sandbox/    in-process hardening self-check + env-stripped subprocess execution
    └── warm        service lifecycle: boot → warmup → one job → exit

The host never imports an engine; it depends only on the contract. An engine never handles hashes/paths defensively; the worker SDK and host do that in audited code.

Writing an engine

from pathlib import Path
from blastbox import Engine, DetonationResult, run_detonation
from blastbox.contract import Page, DeclaredArtifact, Detection, ArtifactRef, Dimensions
from blastbox.limits import Limits

class MyEngine:
    name = "myengine"
    formats = frozenset({"pdf"})

    def detonate(self, input: Path, outdir: Path, limits: Limits) -> DetonationResult:
        # ... render/extract; write artifact files into outdir ...
        (outdir / "page-001.png").write_bytes(png_bytes)
        return DetonationResult(
            payload=Page(index=0, dims=Dimensions(width=210, height=297, unit="mm"),
                         image=ArtifactRef(id="p0")),
            artifacts=[DeclaredArtifact(id="p0", path="page-001.png", kind="image")],
            detected=Detection(label="pdf", mime="application/pdf", confidence=1.0, source="myengine"),
        )

# worker entrypoint:
if __name__ == "__main__":
    import sys
    from blastbox.worker.harness import main
    sys.exit(main(MyEngine()))

The harness seals your declared artifacts (recomputing sha256/size from disk, confining paths, resolving references) and writes metadata.json. The host's validate_worker_output re-validates it. Complex engines build recursive EmbeddedResource trees (e.g. Tika's recursive metadata); simple ones use Page/Record. Engine-specific node subtypes register via contract.register_node_type.

Install

pip install blastbox           # lean core — everything an ENGINE needs (pydantic only)
pip install blastbox[host]     # + the host orchestrator (FastAPI, jobstores, observability)

Run (host)

blastbox serve     --host 127.0.0.1 --port 8000     # the ingress API
blastbox dispatch                                    # the worker dispatcher loop

POST /v1/jobs (multipart file + engine) enqueues a job; the dispatcher launches a hardened disposable worker for it; GET /v1/jobs/{id}/artifacts/{artifact_id} serves validated output.

Security model

  • Disposable worker per job: --network=none --cap-drop=ALL --no-new-privileges --read-only, runsc preferred (fail-closed when a secure runtime is required); the input is deleted after conversion.
  • The host re-seals worker output from disk — worker-reported hashes/sizes are never trusted; artifact paths are confined; the input-SHA round-trip is checked; metadata.json must be a regular file.
  • Ingress rejects oversized bodies before spooling, sanitizes filenames, serves artifacts by id under a confined path, and (optionally) sits behind a bearer token / auth proxy.
  • The contract bounds payload size/depth and validates every node; engine subtypes are validated against their registered schema.

Status

Core framework complete and adversarially tested: contract + full host orchestrator + worker SDK (harness, container sandbox, warm protocol) + warm pool + warm dispatch. Proven end-to-end on two real engines.

Roadmap: host-native bwrap/nsjail sandbox backends; Firecracker microVM + snapshot runtime; the warm-pool burst/health loops.

License

MIT.

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

blastbox-0.1.2.tar.gz (98.5 kB view details)

Uploaded Source

Built Distribution

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

blastbox-0.1.2-py3-none-any.whl (112.8 kB view details)

Uploaded Python 3

File details

Details for the file blastbox-0.1.2.tar.gz.

File metadata

  • Download URL: blastbox-0.1.2.tar.gz
  • Upload date:
  • Size: 98.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for blastbox-0.1.2.tar.gz
Algorithm Hash digest
SHA256 b7672f4f5f9df7ed2b3323b518a9e16e286de33bdf9c8de06031000709f82ede
MD5 8d08fd0570aae6cfa3af3210ecbdbd6e
BLAKE2b-256 efbd8069067b234cc6e9c1635b3cc75ec36bf98a2ac4ef3f76d7bb5a30a09f84

See more details on using hashes here.

Provenance

The following attestation bundles were made for blastbox-0.1.2.tar.gz:

Publisher: publish.yml on wmetcalf/blastbox

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

File details

Details for the file blastbox-0.1.2-py3-none-any.whl.

File metadata

  • Download URL: blastbox-0.1.2-py3-none-any.whl
  • Upload date:
  • Size: 112.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for blastbox-0.1.2-py3-none-any.whl
Algorithm Hash digest
SHA256 4e33659d240de1f92145c0c370690eae73e4408b78a0aef5d82040b48b0b073d
MD5 68a968abac27edc9806e0d53ce7351e2
BLAKE2b-256 f3e5eea8772b54ce36de1b244c615bf71cc52857e6217918ef724f62e15d3fba

See more details on using hashes here.

Provenance

The following attestation bundles were made for blastbox-0.1.2-py3-none-any.whl:

Publisher: publish.yml on wmetcalf/blastbox

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