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.0.tar.gz (98.6 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.0-py3-none-any.whl (112.7 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: blastbox-0.1.0.tar.gz
  • Upload date:
  • Size: 98.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for blastbox-0.1.0.tar.gz
Algorithm Hash digest
SHA256 5d4893ee7bb1564d760998fef0ab0c656f18e56605667ab6d4579c612d31254c
MD5 feb742369cd603746894ff22878e55f0
BLAKE2b-256 05c38dd7b1cba37764c7f396e2091514eb99e1953fa35c0b1a4a55630cf99271

See more details on using hashes here.

File details

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

File metadata

  • Download URL: blastbox-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 112.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.12.3

File hashes

Hashes for blastbox-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 958449020185d172e03fcace697b02e6e8ea6e318ce8a84f5e00c154376f1c11
MD5 1cc7aee5aa941d04fcde9c43077023f6
BLAKE2b-256 22637e9819d0c1e5827dd74dd492ed05b2a16e9f9e8d517815a3ec793ab954e4

See more details on using hashes here.

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