Sovereign AI Shield

Network sovereignty

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 knowsWho wins
Which process opened the connectionthe sensor, always
Which device asked for the namethe resolver, always
When it was first seenthe earliest either source ever saw it
Two different determinationsthe 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:

paired
Exactly one name answered that address inside the window, and it was the same device that asked. The row carries the gap in seconds.
ambiguous
Several names answered it — a shared CDN address is the ordinary cause. The candidates are listed and the row stays an address.
no lookup
Nothing answered that address at all, or the only lookup came from a different device. Not a gap in the report — a finding.

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
01
High classification fails closed. If the local model is unreachable, the request fails. There is no quiet fallback to the cloud one. An outage is recoverable; an accidental disclosure is not.
02
The token map never leaves. scrypt for the key, ChaCha20 for confidentiality, HMAC-SHA256 over the whole file, verified before anything is decrypted. Wrong passphrase or an altered file and it refuses to open.
03
Loopback only. It refuses to bind to anything else. It is not a network service and cannot become one by editing a config line.

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.

01
A capture window is a sample, not an inventory. Run several, at different hours.
02
Update, certificate and time servers appear in the drafts because they are unlisted, not because they are bad. Blocking them makes a machine less safe.
03
Every rule file carries its own undo line at the top.
04
Uncomment one rule, use the machine for a day, then uncomment the next.