Releases: doug445/Panoptes
Release list
v2.5.0 — MAC randomization
Wi-Fi MAC randomization is now part of a full install
Panoptes shipped three tools that read or undo MAC randomization — netcheck reports it, netmaster flags it as a captive-portal blocker, wifi-recover.sh disables it — but nothing that turned it on. The enabler was never a script, just a NetworkManager drop-in, and it had been left behind in the predecessor deploy kit.
share/nm/mac-randomization.conf is now installed to /etc/NetworkManager/conf.d/ by sudo ./install.sh, setting wifi.scan-rand-mac-address=yes and wifi.cloned-mac-address=random so an access point cannot recognize your machine across visits. Skip it with --no-mac-random. This is the only file Panoptes places outside a bin directory.
NetworkManager is not restarted. A restart tears down every connection mid-install, and on a machine whose default route belongs to a VPN's adopted tunnel that can strand the route table. Randomization takes effect on your next reboot, or immediately with sudo systemctl restart NetworkManager.
The installer declines on drivers that cannot cope
wl, b43, b43legacy and brcmsmac — Intel Macs and older Broadcom laptops — take the MAC from the chip. NetworkManager clones it, association fails, and the machine is left with no Wi-Fi at all: the exact hole wifi-recover.sh exists to dig out of. On a match the installer names the interface and driver, explains why, writes nothing, and prints the manual override.
The test is the driver rather than the vendor, so brcmfmac Broadcom parts — Apple Silicon BCM4377/4387, and the SDIO parts on single-board machines — are not skipped. They clone correctly.
Uninstall
uninstall.sh removes the drop-in only when it is still byte-for-byte the file Panoptes shipped; an edited or pre-existing file is left alone and reported. A config displaced at install time is saved as mac-randomization.conf.pre-panoptes — deliberately not a .conf suffix, since NetworkManager reads only *.conf from conf.d — and is put back on uninstall.
New README section: captive portals
Hotel, airport and café Wi-Fi is where a hardened stack most convincingly looks broken, and WARP is the layer that does it: the tunnel carries your traffic past the portal, so the portal never gets a request to redirect and the login page simply never appears. You get a dead connection, not a redirect.
The new section tabulates each layer — WARP, DNS-over-TLS, MAC randomization, ipv4.ignore-auto-dns, NM connectivity checking — against the reason the portal fails, then gives the order that works:
sudo netmaster portal # WARP down, plain DNS, stable MAC, accept their DNS
# log in at the gateway it prints, and confirm you are actually online
sudo netmaster restoreReconnect WARP only after the portal has authorised you. Bringing the tunnel up first puts you straight back where you started.
Also in this release
SECURITY.mdcovers the drop-in. In scope: the driver guard failing to decline, the file being written despite--no-mac-randomor by a partial install, a displaced config saved somewhereconf.dstill parses, andnetcheckreporting MAC privacy the adapter is not applying. Out of scope: what a randomized MAC does not hide, and captive portals breaking, which is documented behaviour.- Fixed:
uninstall.shnever removedwarp-setup.sh. It was added toinstall.sh's system tools without being added to the uninstaller's, so it survived asudo ./uninstall.shin/usr/local/bin.
Full changelog: https://github.com/doug445/Panoptes/blob/main/CHANGELOG.md
v2.4.0 Updated DNS tray color and notifications
dns-tray's off state was a red AG shield. Red says something is wrong, and nothing was: the toggle never turns DNS off, it moves systemd-resolved from AdGuard to Cloudflare 1.1.1.2 security — just as encrypted, just as DNSSEC-validated, only filtering malware instead of ads.
It is now a yellow C shield. Red is reserved for warp-tray, which is a real on/off switch and where down really does mean down. The icon names the resolver you are on rather than colour-coding it as a fault.
The notifications were saying the same wrong thing. The off-state one claimed "Disabled — using default DNS", which was never true of either state. Both now name the resolver and the address:
- AdGuard 94.140.14.14 — ad and tracker filtering
- Cloudflare 1.1.1.2 — malware filtering, no ad blocking
Tooltips likewise carry the resolver and address instead of "on"/"off".
share/icons/adguard-red.svg is removed and share/icons/cloudflare-shield.svg added. install.sh globs the icon directory, so upgrading needs no extra step; a stale adguard-red.svg from an earlier install is inert and can be deleted by hand.
v2.3.1 — dependabot.yml filled in
.github/dependabot.yml was still the GitHub starter template with an empty package-ecosystem, which Dependabot rejects — so it was watching nothing.
Filled in with github-actions, the only ecosystem that applies here: the workflow pins actions/checkout and actions/setup-python, and the repository has no package manifests. Bumps are grouped into a single pull request rather than one per action.
The file also records why pip is deliberately absent — ruff is installed inline in a run: step, and Dependabot does not parse those; it needs a manifest. The block to paste in, should ruff ever be pinned in one, is in the comment.
v2.3.0 — warp-setup.sh: install and activate WARP
The suite shipped warp-tray, warp-killswitch and netmaster warp — and no way to actually get WARP. On a fresh machine those installed fine and then failed with no explanation, because warp-cli wasn't there and nothing said so.
Added
warp-setup.sh — installs and activates Cloudflare WARP
No distribution packages WARP, so this adds Cloudflare's own repository (an RPM repo file with gpgcheck=1, or an APT list with the key dearmoured into /usr/share/keyrings), installs the client, registers the device and connects.
Fedora/RHEL and Debian/Ubuntu — what Cloudflare publishes for. Arch is told to build cloudflare-warp-bin from the AUR and re-run with --register, rather than handed a command that won't work.
sudo warp-setup.sh # repo, package, registration, connect
warp-setup.sh --check # report state, change nothing
warp-setup.sh --mode warp # switch operating mode
sudo warp-setup.sh --uninstall # remove the client and the repositoryIt sets tunnel_only mode by default, and that's the interesting decision. WARP's other modes proxy DNS themselves, which fights every other tool here — harden-dns.sh pins DNSSEC and DoT in systemd-resolved, dns-toggle switches the resolver underneath it, and checkdns audits the result. tunnel_only carries traffic and leaves resolution to resolved, so the two stop arguing. --mode overrides it.
Registration asks before running, because it creates a device record on Cloudflare's side rather than only changing local state. It flushes the resolver after connecting, for the same reason warp-tray and netmaster do.
panoptes-deps.sh now knows about warp-cli
New warp group. It's the one requirement no distribution ships, so instead of the generic "install it yourself" it reports "no distro packages WARP — run: warp-setup.sh".
Clarified
AdGuard needs no installation, and was never missing. It's a public resolver (94.140.14.14 / 94.140.15.15 over DoT), not software — the DNS tools simply point systemd-resolved at it. WARP was the only component running a local daemon, which is why it was the only gap.
Eighteen tools.
v2.2.1 — ECH IP caveat now accounts for VPN tunnels
Documentation accuracy fix.
Changed
The ECH caveat about the destination IP said flatly that "your IP address is still visible". That's true of ECH standing alone, and misleading for anyone running a tunnel — which is a large share of the people this suite is aimed at, given it ships a WARP tray and a WARP killswitch.
Corrected to the real shape: behind a VPN the destination IP travels inside the tunnel and is out of your ISP's view — but the visibility moves rather than disappears, because the tunnel operator now sees both ends. With WARP that operator is Cloudflare, who is already the far end of most ECH-enabled connections, so the pairing buys less than it appears to.
It also flags split-tunnel excludes, which is the case people forget. Anything on the exclude list travels outside the tunnel, so its destination IP is back on the wire and ECH becomes the only thing still hiding the hostname. Check yours with warp-cli settings.
v2.2.0 — resolver flush on the remaining WARP paths
2.1.0 fixed the resolver flush in warp-tray and stopped there. Auditing every path in the suite that changes WARP state turned up three more that needed it.
Fixed
netmaster warp off, warp on and warp reset now flush the resolver.
off and on each have two branches: one delegating to warp-killswitch, and a fallback for when the killswitch isn't installed. The killswitch branches were already covered — warp-killswitch restarts systemd-resolved, and a restart drops the cache and the learned per-server feature grades together. The fallback branches, and the whole of reset, brought the tunnel up or down and left the cache untouched — which is exactly the failure 2.1.0 set out to fix.
Verified in dry-run that all three emit the flush, and that every warp-cli connect in the file is now followed by either flush_resolver or a systemd-resolved restart.
Audited and already correct
Recorded so nobody re-checks them:
| Path | Why it was already fine |
|---|---|
warp-killswitch down / up |
restarts systemd-resolved directly |
netmaster fix (S5/S6 ladder) |
restarts systemd-resolved at S6 |
dns-toggle |
restarts the resolver and resets server features (2.1.0) |
checkdns |
read-only |
netcheck |
its resolvectl flush-caches is unrelated — it provokes a real upstream round-trip so the DoT probe isn't answered from cache |
Background
Bringing the WARP tunnel up or down kills every DNS lookup in flight over it. Those fail DNSSEC validation with failed-auxiliary — the transport carrying the DS/DNSKEY chain vanished mid-lookup — and systemd-resolved caches the failure. Routing is fine immediately afterwards; only the cache is stale, and it reads as "no internet" until something clears it.
v2.1.0 — resolver flush on WARP toggle, real DNS failover
Toggling WARP off left the resolver answering from a poisoned cache — which reads as "no internet" while the network is actually fine.
Fixed
warp-tray now flushes the resolver after any WARP state change. Tearing the tunnel down kills every DNS lookup in flight over it. Those fail DNSSEC validation with failed-auxiliary, because the transport carrying the DS and DNSKEY chain vanished mid-lookup, and systemd-resolved caches the failure. Firefox's captive-portal probe is usually among the casualties, and one failed probe is enough for it to declare the link down.
The teardown itself is clean — verified, not assumed. With WARP disconnected the nftables table, the ip rule and routing table 65743 are all gone, the route falls back to the LAN gateway, and both ping and DNS work. Only the cache is stale.
resolvectl flush-caches and reset-server-features now run after any state change, on a thread so the tray's event loop isn't blocked; the killswitch menu path flushes too. Measured across a teardown with lookups deliberately in flight: 4 of 12 probes failed without the flush, 0 of 12 with it. One run, small sample — directional, not a proof.
dns-toggle now appends Quad9 to DNS=, giving real resolver failover. FallbackDNS= never provided this. Per resolved.conf(5) it is "only used if no other DNS server information is known" — only when DNS= is empty. It is not consulted when the servers in DNS= stop answering, which is exactly the case people assume it covers. Both branches previously listed a single provider, so both entries failed together.
The trade-off is documented in the script: resolved sticks with whichever server last worked, so after a failover you stay on Quad9 — malware filtering, no ad blocking — until it restarts.
Packaging bug: the tray icons were never shipped. Both applets load icons by absolute path from ~/.local/share/icons, but the repository didn't contain them, so a fresh install produced trays with no artwork — which looks like a crash. All four SVGs now ship in share/icons/ and install.sh places them.
Added
A README section on the two tray toggles, with an annotated screenshot showing each icon in place and what both of its states mean.
v2.0.1 — running-browser detection actually works
Running 2.0.0 for real on a live machine exposed three defects in ech-browsers.sh, one of which meant an advertised safety property had never worked. Recommended upgrade for anyone on 2.0.0.
Fixed
The "will not edit a running Chromium" guard never fired. It tested [ -e SingletonLock ], but that lock is a symlink to hostname-pid — not a real path — so -e follows it and returns false every time. Only -L can see a dangling symlink.
A sandboxed browser's lock records its namespace pid. Testing that against host pids is not merely unreliable but actively wrong: a stale Flatpak lock named pid 2, and pid 2 is kthreadd, alive on every Linux system ever booted. That is why a closed browser was reported as running. Both detectors now look for an open file descriptor under the profile — the only evidence that survives sandboxing — and consult lock files only for non-sandboxed profiles.
The descriptor scan then failed silently under pipefail. find -print -quit | grep -q . lets grep close the pipe on first match, find dies of SIGPIPE with status 141, and pipefail reports the pipeline as failed — so the check returned "not running" precisely when it had found something. It passed in isolation because that shell had no pipefail.
Hand-set prefs were duplicated rather than replaced. Applying to a profile that already had ECH configured by hand left every pref defined twice — harmless while the values agreed, a silent conflict the moment they did not. Pre-existing copies are now commented out as // PANOPTES-SUPERSEDED, and --revert restores them.
Tor Browser and Mullvad Browser tarball paths added to the skip table, so the report names them explicitly. Safety never rested on that list — the browser table is an allowlist and an unlisted profile is never written to — but a skip you cannot see in the output is not a skip anyone should have to take on faith.
Added
The README now explains what ECH is and why it matters: that HTTPS leaves the hostname in the clear, that encrypting DNS hides the question while SNI announces the answer, and that ECH and secure DNS are one mechanism rather than two features. It also says plainly what ECH does not do — your IP is still visible, the site has to publish a config, traffic analysis survives — because overselling it helps nobody.