Skip to content

feat(audit): ask the questions a port number cannot answer - #8

Merged
gclluch merged 1 commit into
mainfrom
phase3-depth
Aug 2, 2026
Merged

feat(audit): ask the questions a port number cannot answer#8
gclluch merged 1 commit into
mainfrom
phase3-depth

Conversation

@gclluch

@gclluch gclluch commented Aug 2, 2026

Copy link
Copy Markdown
Owner

The complaint this answers: "right now it's pretty just a naked little scan that looks for open ports". That was a coverage problem, not an architecture one - ten ports and no version detection, so on a clean LAN the audit produced a single info line. On the same LAN it now produces five, all evidence-backed and none of them invented.

  • --ports top100 - nmap's frequency ranking as a named set, mixable with numbers (--ports top100 3000). The port scan is now flat across every (ip, port) rather than a pool of devices each walking its own list, so a packet-dropping device costs one timeout instead of a hundred consecutive ones.
  • New probe.py - the layer between "what is here" and "what it means". It speaks just enough TLS, SMB, DNS and SSH to turn 445 is open into this server accepted the 1996 dialect. Parsers are split from sockets, so the fiddly half is bytes in, a value out. audit.py still opens no socket and does not import it.
  • Six new rules - smb-v1 (high), ssh-weak-algorithms and tls-cert-expired (medium), service-version, tls-cert-untrusted and dns-recursion-open (info). The last two are explicitly labelled not-a-problem, for the same reason open-ports-noted is: every home router would fire them, and a report that flags the normal case trains you to ignore it.
  • TTL OS hint on each device, persisted. Only an exact match on 64/128/255 earns a family name - an unrecognised TTL prints as the bare number, because 32 is not "nearly 64".

Three bugs found on the way, each of the fail-silently kind:

  • The SMB header was 34 bytes, not 32. The probe would never have fired and would have reported "no SMBv1 anywhere" forever. SMB_HEADER_SIZE is now asserted by a test rather than trusted.
  • parse_kexinit read all ten name-lists, so compression none - which every SSH server offers - was read as the cipher none. It would have flagged every SSH server on earth. Only the first six lists are read now.
  • service-version fired on HTTP/1.0 200 OK, reporting the protocol version as the software. Caught by an existing test - the anti-theater half of test_audit.py doing its job.

Verified against real servers rather than only fixtures: two Samba containers with SMBv1 on and off (both replies are now the test fixtures), a real OpenSSH server, a CA-issued certificate off the wire, and a live LAN. DEFAULT_PORTS is unchanged on purpose - adding even port 53 would report port-opened on everyone's router on their next scan, which is the one failure mode diff.py exists to avoid.

@gclluch
gclluch merged commit 9941ae6 into main Aug 2, 2026
8 checks passed
@gclluch
gclluch deleted the phase3-depth branch August 2, 2026 00:26
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