// out-of-band
OutOfBits
The OAST listener that talks back.
DNS & HTTP callbacks captured. Mutate the response with Python. Sandboxed. Audited. Replayable against any past interaction.
Platform vital signs · last 30 days
46,615
callbacks captured
41,879 dns
queries answered
4,736 http
requests served
What you can do
01
Confirm a blind callback
Generate a one-shot host, plant the URL, watch it land.
# in any vulnerable parameter: http://a8x2.ooast.net/probe # on /interactions (live, ~1s after firing): GET /probe 200 OK DNS A query NOERROR
02
Mutate the response
Sandboxed Python that runs on every callback. Change status, add headers, rewrite the body.
def handle_http(ctx): if ctx.request.path == "/admin": ctx.response.status_code = 401 ctx.response.headers["WWW-Authenticate"] = \ 'Basic realm="x"' return ctx
03
Pipeline & audit
Compose modifiers in order. Per-stage input/output snapshots — see exactly what each step did.
pipeline "tarpit" · http ├─ add-trace-header ok 12 ms ├─ canned-401 ok 3 ms └─ log-source-ip ok 5 ms on_error: stop total: 20 ms
04
Persist state between callbacks
Per-modifier ctx.state and per-user ctx.shared. DNS rebinding in four lines — nothing else in the OAST space does this.
def handle_dns(ctx): n = ctx.state.get("count", 0) + 1 ctx.state["count"] = n ip = "127.0.0.1" if n % 2 else "127.0.0.2" ctx.response.answers = [{ "name": ctx.request.qname, "type": "A", "ttl": 0, "data": ip, }] return ctx
// defense in depth
Five layers of sandbox.
Modifiers are arbitrary user code. So they run in a fresh subprocess, blocked from doing anything they shouldn't.
- AST allowlist — no
import, dunders,open,eval. - Restricted builtins — only a curated subset of
__builtins__. - rlimits — 1s CPU · 100 MiB · NPROC=0 · 32 FDs.
- seccomp — networking, ptrace, mount, kexec, modules — all EPERM.
- Landlock — kernel-enforced filesystem isolation. No paths.
Want the full picture? User guide.
Built by Carl Sampson.