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 genericRecordfloor, 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.jsonmust 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
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 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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b7672f4f5f9df7ed2b3323b518a9e16e286de33bdf9c8de06031000709f82ede
|
|
| MD5 |
8d08fd0570aae6cfa3af3210ecbdbd6e
|
|
| BLAKE2b-256 |
efbd8069067b234cc6e9c1635b3cc75ec36bf98a2ac4ef3f76d7bb5a30a09f84
|
Provenance
The following attestation bundles were made for blastbox-0.1.2.tar.gz:
Publisher:
publish.yml on wmetcalf/blastbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
blastbox-0.1.2.tar.gz -
Subject digest:
b7672f4f5f9df7ed2b3323b518a9e16e286de33bdf9c8de06031000709f82ede - Sigstore transparency entry: 1700063987
- Sigstore integration time:
-
Permalink:
wmetcalf/blastbox@0d80c081c31628794644a9f819be6ee54952296f -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/wmetcalf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0d80c081c31628794644a9f819be6ee54952296f -
Trigger Event:
release
-
Statement type:
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4e33659d240de1f92145c0c370690eae73e4408b78a0aef5d82040b48b0b073d
|
|
| MD5 |
68a968abac27edc9806e0d53ce7351e2
|
|
| BLAKE2b-256 |
f3e5eea8772b54ce36de1b244c615bf71cc52857e6217918ef724f62e15d3fba
|
Provenance
The following attestation bundles were made for blastbox-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on wmetcalf/blastbox
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
blastbox-0.1.2-py3-none-any.whl -
Subject digest:
4e33659d240de1f92145c0c370690eae73e4408b78a0aef5d82040b48b0b073d - Sigstore transparency entry: 1700064242
- Sigstore integration time:
-
Permalink:
wmetcalf/blastbox@0d80c081c31628794644a9f819be6ee54952296f -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/wmetcalf
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@0d80c081c31628794644a9f819be6ee54952296f -
Trigger Event:
release
-
Statement type: