Skip to content

feat(here): ask what this network does to you, not what is on it - #10

Merged
gclluch merged 1 commit into
mainfrom
here
Aug 2, 2026
Merged

feat(here): ask what this network does to you, not what is on it#10
gclluch merged 1 commit into
mainfrom
here

Conversation

@gclluch

@gclluch gclluch commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Every command so far asks what is on this network. In a cafe that is the wrong question and an impolite one - those are not your devices, and the README's Scope section rules out scanning them. netdiff here inverts it: what does this network do to me?

here: 10.24.8.0/22 - 1 high, 3 medium, 2 info
gateway 10.24.8.1, resolver(s) 10.24.8.1

    high      this network is reading your encrypted traffic to example.com
    medium    this network intercepts web traffic - example.com answered 302
    medium    this network's resolver invents answers for names that do not exist
    medium    2 service(s) on this machine are bound to the network, not loopback
    info      31 other device(s) on this network are reachable from here

Eight rules: TLS interception, captive portal, a resolver that invents answers for .invalid (reserved, so an answer is fabrication rather than a lookup), a public name answered with a private address, one MAC answering for three or more addresses, your own network-bound services, and client isolation reported either way - the good news has to be printed, or it cannot be told from a check that did not run.

Structure follows the existing grain. here.py opens the sockets and parses the bytes; audit.py decides what any of it means and still opens none. The rule specs go in the same RULES table, so audit --explain here-tls-intercepted works with no new code, and findings are ordinary Findings, so the verbosity ladder and --json come for free.

It records nothing. A cafe network in ~/.netdiff/history.db would put strangers' MACs in your inventory and make the next scan at home report a wall of appeared/vanished.

Deliberately not claimed

  • No comparison against a public resolver: CDNs legitimately answer differently per resolver, so a difference is not evidence. The two DNS rules need no comparison to be certain.
  • An interceptor whose CA your machine already trusts is invisible. Nothing observable distinguishes it from an honest network, so the README says so rather than guessing.

False positives caught in review, before they shipped

Each of these would have accused a network that was fine, and each is now a test:

  • An empty trust store. unable to get local issuer certificate is byte-for-byte what an interception CA produces and what a python.org build whose Install Certificates.command was never run produces on every network forever. An empty store is a fact about the computer, so the question goes unanswered rather than answered wrongly. A wrong clock is named in the lesson for the same reason.
  • is_private("0.0.0.0") is True. So is 127.0.0.1. Both are how a filtering resolver says no - school wifi, guest wifi, a pi-hole - and refusing to answer is the opposite of steering you somewhere. They no longer count.
  • only the gateway answered ARP with an empty ARP table. Reachable two ordinary ways: the ARP command failing to parse, and a VPN, where the default route is a tunnel peer that was never on this segment. Both handed the reader "Nothing. This is the good result." about a check that had not run.
  • The broadcast address counted as a neighbour. macOS caches it as ff:ff:ff:ff:ff:ff; one such row flips isolation-on to isolation-off.
  • here 10.0.0.0/8 reached nudge without the MAX_HOSTS guard discover has.
  • here-own-ports-exposed asserted reachability its probe cannot establish - the check runs from this machine to its own address, which a host firewall never sees. Title and lesson now say what is actually proved, which is the binding.

One unrelated fix

resolve_subnet printed the inferred-subnet notice to stdout, so netdiff scan --json with no subnet emitted something no parser would accept. It is a diagnostic, not data - stderr. Fixed at the shared function, so scan, audit and here all get it.

278 → 289 tests. Half of test_here.py asserts something is not reported.

@gclluch
gclluch merged commit 20de449 into main Aug 2, 2026
8 checks passed
@gclluch
gclluch deleted the here branch August 2, 2026 01:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant