How it works
Four parts, and a standing refusal to guess.
Python 3, standard library only. Nothing to install — it runs by unzipping. Each part sits somewhere different because each one can only see from where it stands, and the collator makes one picture out of what they each saw.
Every machine
The sensor — agent/watch.py
Watches this machine's real outbound connections and attributes each one to the process that opened it. Linux reads /proc, macOS uses lsof, Windows uses netstat. Reverse DNS can be turned off entirely.
# a 30-second capture of this machine python3 watch.py # then leave it running: a 30s window every 5 minutes, merged as it goes python3 watch.py --daemon
Merging is the point. A destination seen for three weeks keeps its original first-seen date. A fresh capture never makes an old visitor look new — that first-seen date is usually the row that tells you something.
Router, or a small always-on box
The resolver — agent/resolver.py
Covers everything that will never run an agent: televisions, doorbells, phones, cars. Every lookup is logged with the device that asked, resolved upstream, and scored against the same jurisdiction table the sensor uses.
# on the router or a Pi; point the devices, or DHCP, at it for DNS sudo python3 resolver.py --port 53 # to try it out without root python3 resolver.py
--enforce sinkholes denied names. Patch, certificate and time servers are never sunk, and that list is not removable — blocking those makes a device less safe.
What it cannot see, stated in its own output: a device using DNS-over-HTTPS to a hardcoded resolver bypasses this entirely, and so does anything connecting straight to an address with no lookup. Many smart televisions do the first. The console reports what was observed; it never claims nothing else happened.
Wherever the captures land
The collator — agent/collate.py
The sensor and the resolver used to write the same file and erase each other: two true and partial records kept as one file that was neither. The collator reads every capture, merges on (device, destination) and writes one ledger.
# merge everything it can find into one ledger python3 collate.py # widen the window a connection may be attributed to a lookup within python3 collate.py --window 600
| What each source knows | Who wins |
|---|---|
| Which process opened the connection | the sensor, always |
| Which device asked for the name | the resolver, always |
| When it was first seen | the earliest either source ever saw it |
| Two different determinations | the more restrictive — and the disagreement is printed, not averaged away |
Pairing a name to an address is done on the answer and the clock: a connection to an address that follows a lookup which answered that address, from the same device, inside a short window. Three outcomes, and the row says which one it got:
Want to see it before you run anything? Download the example collated capture, open the console, press Load capture and choose it. It is built from the test fixtures — documentation addresses, not anyone's traffic — and it says so in its first line.
Between your apps and the model
The gateway — gateway/proxy.py
Point an application's base URL at the gateway instead of the model provider. On the way out it replaces names, SSNs, record numbers, addresses, emails, phone numbers and card numbers with tokens; on the way back it puts them in again. The answer is readable to the person, and the model never held the identity.
python3 proxy.py # http://127.0.0.1:8787 python3 proxy.py --classification high # nothing leaves this machine
Wherever models are approved
The passport — passport/sign.py
A passport is the paperwork a model produces before it is allowed to run: where it came from, where it runs, who holds the keys, what it may send. Signing it means changing one character breaks the signature — the determination stops being a claim and becomes evidence.
python3 sign.py keygen python3 sign.py sign my-model.json --days 90 python3 sign.py verify my-model.signed.json
Four determinations, each with its reason stated: Sovereign verified · Limited external connectivity · Foreign dependency detected · Unauthorized foreign data transfer.
The gate, in any application
The gateway checked passports, which only ever covered traffic that went through the gateway. An app that loads a model directly skipped it. Three lines closes that:
from require import require_passport
require_passport("claude-opus-5",
passport="passports/claude-opus-5.signed.json")
Missing, expired, altered, signed by an issuer you did not name, or determined worse than you accept — and the application does not start.
The override must name a person: a first and last name and a reason, or it is refused exactly like a missing passport. Every use is appended to overrides.jsonl with the model, the host and the time. SAIS_PASSPORT_OVERRIDE_EXPIRES makes it lapse on its own; without it the override is recorded as open-ended, which is a thing a reader can see and object to.
One signing seam, two possible cores
Everything that signs goes through passport/signer.py. If pyca/cryptography is installed, it uses that — the same Ed25519 core used elsewhere in the portfolio. If it is not, the bundled pure-standard-library implementation answers instead, so the kit still runs by unzipping on a machine with nothing installed.
python3 signer.py
That command runs the RFC 8032 vectors against whichever core is live and, when both are present, signs the same payloads with each and compares the bytes in both directions. Ed25519 is deterministic, so agreement is exact equality, not “both verified”. Two cores that disagree by one byte would be worse than two implementations, and that test is what makes the seam safe.
Before you block anything
rules.py and the resolver's --enforce are the only two places this system can stop traffic. Both are opt-in and both stay conservative.