// 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:

  1. It installs a local CA so it can read TLS. You control that CA; it never leaves your machine.
  2. History is a single SQLite file. Back it up, copy it, grep it — it's just a file.
  3. 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.

terminal
# 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:

terminal
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.

tip

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.

CommandWhat it does
nullock statusProxy, 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 findingsList findings as JSON (filter by severity)
nullock export sarif|sbom|nmap|harExport 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:

tag-outgoing.js
// 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.
warn

Only intercept traffic you own or are authorized to test. nullock is a tool; the law is yours to follow.