// docs · v3.7.0
Documentation
Self-host first. Everything here runs on your machine, offline, with no account.
Getting started
nullock is a man-in-the-middle proxy. It sits between your client and the network, captures every request and response, and lets you intercept, replay, and rewrite them. Three things to know before you start:
- It installs a local CA so it can read TLS. You control that CA; it never leaves your machine.
- History is a single SQLite file. Back it up, copy it, grep it — it's just a file.
- There is no telemetry. The binary makes no network calls except the traffic you point at it.
Install
Grab a build for your OS from Releases, or compile from source.
# prebuilt: github.com/Bikebrainz/Nullock/releases/latest # Windows Nullock-<ver>-win64.exe # Linux .AppImage · .deb · .rpm # macOS Nullock-<ver>-Darwin.dmg (right-click → Open) # or build from source — Qt 6.7 + CMake + nghttp2: cmake -B build -DCMAKE_BUILD_TYPE=Release cmake --build build --target NullockApp
On first run the app generates a local CA and prints its path. Trust that CA in your OS store so TLS sites stop warning — it's yours, and it never leaves the machine.
Your first capture
Start the proxy on a port and point your client at it. The desktop app does this with one click; from the CLI:
NullockApp --proxy-port=8080 --control-port=17777 # [nul-] proxy on :8080 · control API on :17777 # then, in another shell — set scope, watch it fill: nullock scope add 'http://app.local/*' nullock status # ─── 42 rows · scope: app.local ───
Everything in scope lands in the history table. Click a row to inspect the raw exchange, or send it to Repeater.
Out of scope traffic is still proxied — it just won't clutter your history. Widen scope later without losing rows.
CLI
The CLI drives a running instance over its local control API ($NULLOCK_API, default 127.0.0.1:17777) — it needs curl and jq. Anything you do in the UI, you can script.
| Command | What it does |
|---|---|
nullock status | Proxy, scope & findings summary |
nullock scope add <glob> | Add a target to scope (e.g. http://app.local/*) |
nullock scan <host> [top100] | Port-scan & run the active battery against a host |
nullock findings | List findings as JSON (filter by severity) |
nullock export sarif|sbom|nmap|har | Export for CI & other tooling |
nullock replay <id> | Re-send a captured request |
Extensions
Drop a single .js file into the extensions directory. It's loaded on next start — no build step, no marketplace gate. Each file runs in a sandboxed QJSEngine and hooks the proxy pipeline:
// nullock:permissions modify-requests nullock.onRequest(req => { req.headers.push(["X-Debug", "nullock"]); return req; });
Hooks: nullock.onRequest(fn) and nullock.onResponse(fn) — mutating traffic needs the modify-requests / modify-responses permission (declared in a header comment); observing and nullock.reportFinding(...) need none. Full guide: EXTENSIONS.md.
Security model
A MITM proxy is, by design, a tool that reads encrypted traffic. Here is exactly what nullock can and cannot see, and where the lines are:
- The CA is yours. Generated locally on first run, stored in your OS keychain. We never ship a shared key. Delete it and interception stops.
- Captures stay local. History is a file on disk. Nothing syncs unless you buy Hosted and opt in per scope.
- Secrets in history. Tokens and cookies land in plaintext in your SQLite file — that's the point. Encrypt the disk; don't commit the file.
- Reporting a flaw. Report privately via GitHub Security Advisories. We respond within 72 hours and credit you on the advisory.
Only intercept traffic you own or are authorized to test. nullock is a tool; the law is yours to follow.