Wave 8: UDP, QUIC, IKEv2, tunnels, the cleartext inventory, and the docs - #51
Merged
Merged
Conversation
The last wave of the backlog. PR-40, PR-41, PR-42 and PR-43, plus a documentation pass, built by five agents concurrently and integrated here. 1149 tests -> 1508. pcapscan/datagrams.py -- the UDP seam (new, integrator) pcapscan/quic.py -- PR-40, QUIC Initial decryption (RFC 9001) pcapscan/ikev2.py -- PR-41, IKEv2 SA proposals over UDP pcapscan/tunnels.py -- PR-42, GRE/ERSPAN/VXLAN/GENEVE (#12, offline) pcapscan/cleartext.py -- PR-43, the inventory of what is not protected docs/ -- eight pages; README cut from 344 lines to 228 -------------------------------------------------------------------------- The blocker: this tool could not read a UDP packet -------------------------------------------------------------------------- `decode_frame` walked to TCP and returned None for everything else, with no counter and no mention in the stats. Measured over the capture corpus that was **30,530 of 159,929 packets -- 19% -- dropped in silence**, including 11,722 QUIC packets on port 443. A monitor whose purpose is to report what cryptography is in use was blind to the transport that now carries a growing share of the web's TLS 1.3 handshakes, and the three UDP protocols this wave adds would each have had to invent their own plumbing to get at it. So the seam came first, and it is the integrator's work rather than any one PR's: * **`decode_datagram`**, sharing the link-layer, VLAN, IPv4-options and IPv6-extension-chain walk with `decode_frame` through a new `_walk_network`. One walk, not two. Every defect that walk has ever had -- the fixed 14-byte Ethernet header, the fixed 20-byte IPv4 header, the unwalked IPv6 chain -- produced *plausible wrong offsets* rather than an error, and a second copy would be a second place to reintroduce them. It also refuses non-initial IPv4 fragments, which TCP never needed and a near-MTU QUIC Initial very much does. * **`pcapscan/datagrams.py`**, which keys UDP flows and *discovers* handlers rather than requiring registration: `HANDLER_MODULES` names the modules, each is imported if present and skipped if not. That is what let four people write four protocols at once without editing one shared list. * Wired into `SessionBuilder`, so the CLI, `iter_sessions` and the upload sandbox all get UDP without further change. -------------------------------------------------------------------------- PR-40 -- QUIC, and the post-quantum traffic nobody could see -------------------------------------------------------------------------- This is the headline and it is a large one. RFC 9001 derives Initial packet protection from a **published constant salt** and the client's Destination Connection ID, which is in the clear in the header. No keys are needed and none are used: the secrecy of a QUIC handshake never rested on the Initial keys. The handler walks long headers, removes header protection, opens the packet with AES-128-GCM, reassembles CRYPTO frames by offset across coalesced packets, and hands the result to **`pcapscan.sessions.Session`** -- the same object the TCP path builds. That reuse is the design. There is no second ClientHello parser and no second document shape, so JA4, the extension census, ECH, the key-share group, the resumption logic and the post-quantum classification all arrived with **no change to `cryptomon/analysis.py`, the CBOM, the CSV export or the dashboard**. Over the corpus: 86 QUIC flows claimed, 468 Initials seen, **468 decrypted, 0 undecryptable**, 86 ClientHellos and 60 ServerHellos recovered. 69 of the 86 hellos were still in flight across more than one packet, so a single-packet reader would have got a fragment of each. clients offering a post-quantum hybrid 69 / 86 = 80.2% (TCP: 71.6%) servers accepting one 35 / 60 = 58.3% (TCP: 20.4%) ECH offered 74 / 86 = 86.0% (TCP: 36.5%) ALPN h3 on all 86; no TCP hello offers it Whole-corpus effect, measured before and after: hybrid key exchanges 81 -> 116 (+43%) quantum-safe fraction 14.4% -> 18.6% TLS 1.3 sessions 499 -> 585 (+17%) **Sixty-nine post-quantum key exchanges were happening over QUIC and this tool could not see one of them.** Thirty-five of them completed. The honest bound on that: of the 11,722 QUIC packets, 10,984 are short-header 1-RTT and will never be read by any keyless tool. They are now counted, not read. The reachable population is 762 long-header packets and the 468 Initials inside them, and that is the whole handshake, which is the point. Verification was not straightforward and is worth recording. **tshark 3.4 on this machine cannot decrypt QUIC v1** -- it predates RFC 9000, carries draft-29's salt, and fails every Initial with a checktag error while calling version 1 "Unknown". Rather than take that as a disagreement, the agent decrypted one packet under three candidate salts to show which was right, then built a different oracle: it re-framed all 86 decrypted CRYPTO streams as ordinary TLS-over-TCP in a synthetic capture and let tshark's TLS dissector read them. 86/86 parsed as ClientHello, SNI agreed 86/86, and tshark reported key-share code points 0x6399 and 0x11EC exactly where we name X25519Kyber768Draft00 and X25519MLKEM768. RFC 9001 Appendix A's published test vectors are pinned in the test file as well. -------------------------------------------------------------------------- PR-41 -- IKEv2 -------------------------------------------------------------------------- IKEv2 is the one protocol here that *states* its cryptography instead of implying it: RFC 7296 puts the encryption algorithm, the PRF, the integrity algorithm and the key exchange on the wire as four separately numbered transforms, in the clear, before anything is encrypted. So almost nothing is inferred. Transform names are chosen to be exactly the strings `cryptomon/analysis.py` already knows, so **no additions to the marker tables were needed**. The RFC 9370 case is the good one: a `KE=x25519` with `ADDKE1=ml-kem-768` is emitted as the single name `x25519+ml-kem-768`, which normalises to `x25519mlkem768`, in which the existing table finds one post-quantum marker and one classical one and answers **hybrid** -- the true answer, and the one neither half gives alone. Port 4500 carries IKE, ESP and NAT keepalives on the same port, distinguished by a four-byte non-ESP marker. Getting that wrong means parsing ciphertext as an IKE header and reporting a proposal made of noise; a marker-shaped datagram is therefore tried both ways. ESP is reported as present-and-opaque, which is a useful finding rather than a failure -- and `opaque_flows` is a new readiness field so that "we could not see it" is never reported as "there was none". **There is no IKEv2 in the corpus**, verified independently rather than taken on trust: 0 packets on UDP 500 or 4500 in all 160,221. Everything quoted about its behaviour comes from eight generated fixtures. The one corpus-backed claim is the negative: 30,531 real datagrams offered to the detector, 0 claimed, 0 exceptions. The CBOM now fills `ikev2TransformTypes` -- `encr`, `prf`, `integ`, `ke` -- which is a CycloneDX 1.6 field this project has had a schema for and nothing to put in. `esn` and `auth` are deliberately left empty: ESN is not carried, and IKEv2's authentication method is negotiated inside the encrypted IKE_AUTH, so a passive observer never sees it. Emitting either would invent a value the traffic did not contain. -------------------------------------------------------------------------- PR-42 -- tunnels (#12, the offline half) -------------------------------------------------------------------------- Every measurement in this repository comes from a capture taken on an endpoint. That is not how anyone monitors a corporate network: they mirror a port, and the traffic arrives wrapped in GRE inside IP. To this tool, every one of those frames was unreadable. Unwrapping happens **before** framing, in its own module, not as recursion inside `decode_frame`. Peeling a tunnel does not yield a transport header -- it yields another whole frame needing the entire walk run over it again -- and putting that inside `decode_frame` would hand every caller, the live eBPF adapter included, a recursion it did not ask for with an attacker-chosen depth. The stage generalises: GRE (RFC 2784/2890, with the variable header its flag bits imply), ERSPAN Types I, II and III, VXLAN, GENEVE, IP-in-IP and 6in4, bounded to four levels. ERSPAN Type I is the trap and is handled: it is distinguished from Type II only by the GRE sequence flag, so a parser assuming a header is always present eats eight bytes of the real Ethernet frame and then decodes plausible nonsense. The proof is the one that matters: `tls12_certificate.pcap` wrapped frame-by-frame in ERSPAN Type II. Today's pipeline drops 19 of 19 frames and reports nothing. With the stage, the pipeline returns the **identical session document** -- `==` on the dict, same ciphersuite, same `sha384.badssl.com`, same timestamp to the microsecond. The ERSPAN truncation bit is carried through to a counter and onto the session, because a switch saying "I cut this frame short" is *labelled* missing data, and both times this project has reported a wrong answer that looked right -- truncated ClientHellos parsed as whole, tshark's desegmenter giving up in silence -- the data was missing without saying so. **The live eBPF path is still blind to tunnels**, and #12 should not be closed as though it covered both halves. -------------------------------------------------------------------------- PR-43 -- what is not protected at all -------------------------------------------------------------------------- Scope changed on evidence. The backlog's PR-43 leads with DTLS; there is not one DTLS record in the corpus, nor any WireGuard. What is there is 30,531 UDP datagrams, most of them entirely unencrypted. "This uses RSA-2048, which a quantum computer breaks" and "this uses nothing, which anyone on the segment breaks today" are both CBOM findings, and the second is actionable this afternoon. Fourteen protocols identified by content: DNS (with DNSSEC state read properly, from the EDNS0 DO bit and the RRSIG/DS/DNSKEY records), mDNS, LLMNR, NetBIOS-NS and -DGM, DHCP, NTP, CLDAP, STUN/TURN, HSRP v1 and v2, SNMP v1/v2c/v3, syslog, SSDP and BigFix. 1,058 flows claimed over the corpus, **zero false positives**, and no flow on port 443 taken. Of the non-QUIC UDP: **61.4% has no confidentiality and none of it is encrypted.** The four-rung protection ladder earns its place on the HSRP result -- all 15,867 hellos carry an all-zero plaintext auth field *and* a keyed-MD5 authentication TLV, so the gateway redundancy is authenticated by a 1991 hash. That is `obsolete`, which is neither `none` nor `authenticated only`, and the distinction is the finding. Also: **zero DNSSEC and zero EDNS0** in 2,134 DNS messages -- not one client on this network even asks for signatures. 764 of those queries (35.8%) are for HTTPS resource records, which is where the ECH configuration lives: this corpus asks for ECH keys in the clear 764 times. **Nothing identifying is recorded.** Not a query name, not an mDNS instance name, not a NetBIOS name, not a DHCP hostname. Service *types* and record *types* survive; names are counted via keyed BLAKE2 tokens under a per-process random key, because a stable digest of a hostname is a hostname when the name space is a corporate network. No community string, HSRP auth string or ICE username is ever written down -- only that one is present and in the clear. -------------------------------------------------------------------------- Documentation -------------------------------------------------------------------------- `README.md` restructured to route rather than explain (344 -> 228 lines), and `docs/` added: install, configuration, offline analysis, the service, the live sensor, reading a report, architecture, and a 623-line troubleshooting page mined from this repository's own commit history and module docstrings. Every command with a prompt was run; anything needing Linux, root, bcc or a live interface is marked "not verified on this machine" rather than implied. Thirteen inaccuracies in the old README are fixed, among them: port 990 documented as sftp when it is FTPS, "IPv6 support" still listed as a TODO four waves after it shipped, `http://0.0.0.0:8000/docs` offered as a browsable address, and an `enp0s1` interface default that does not exist. -------------------------------------------------------------------------- Defects found and fixed -------------------------------------------------------------------------- * **Every non-SSH record was counted as a TLS session.** `Summary.add` fell through to `_add_tls`, so the 1,058 cleartext UDP flows arrived in the readiness report as resumed TLS sessions -- moving the quantum-safe fraction by inventing sessions that never negotiated anything. Found independently by two agents. Fixed with an explicit branch, plus `_add_ikev2` for the protocol that does have a key exchange to classify. * **The upload sandbox was TCP-only.** `pcapscan/sandbox.py` never got the UDP seam, so a capture uploaded through the browser was analysed by a strictly weaker parser than the same file on the command line -- silently, because a report of nothing looks like a capture with nothing in it. * **`--no-certificates` lost the data it promised to keep.** `--help` said "keep the raw chain"; the flag passed `lambda _body: []`, so the chain was parsed to nothing and the DER discarded. Now a `KEEP_DER` sentinel. * **Four scripts were committed without their executable bit**, so `./ubuntu-setup.sh` was "permission denied" on a fresh clone -- and `deploy/README.md`'s own quickstart begins `sudo ./create-service.sh`. * **`BrokenPipeError` reached stderr** on `| head` and `| mongoimport`, both of which the README recommends: catching the write is not enough, because CPython flushes stdout again at shutdown where no `except` can reach it. * **The UDP flow counters did not sum.** `flows_unrecognised` was only incremented at the 64-datagram cap, so a two-datagram exchange was counted nowhere and 1,071 of 1,164 flows fell in the gap. A stats line whose parts do not add up is worse than no stats line. Now `flows_undetected` closes it: 1058 + 86 + 8 + 18 + 2 = 1172. * **`DatagramRouter.push` was unguarded** while `_detect` was guarded. One handler raising ended the capture for every other protocol sharing the loop. * **`DES`, `DES40` and the IKEv2 `ENCR_DES` family had no strength entry**, so they were reported with no symmetric strength at all. This also fixes `TLS_RSA_EXPORT_WITH_DES40_CBC_SHA` on the TLS side. * **The upload form read "up to 0 MB"** below a 1 MiB cap, and said MB for MiB. * And one of mine, in the seam: `self.router = router or None`, three lines below a comment warning that `DatagramRouter` defines `__len__` so an empty one is falsy. The comment now names the incident. -------------------------------------------------------------------------- Verified -------------------------------------------------------------------------- pytest -q 1508 passed, 19 skipped (was 1149, 18) pytest -m smoke -q 1370 passed (was 1032) with a corpus MongoDB 1523 passed, 4 skipped compileall clean fuzz, 11 targets 249,193 cases, 0 failures tshark oracle no drift The whole real corpus, end to end: 2,404 sessions (1,346 TLS, 1,058 cleartext), 624 key exchanges performed, 116 hybrid, 508 classical, 61 post-quantum offers refused, quantum-safe fraction 18.6%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The last wave of the backlog. PR-40, PR-41, PR-42 and PR-43, plus a documentation pass, built by five agents concurrently and integrated here. 1149 tests -> 1508.
pcapscan/datagrams.py -- the UDP seam (new, integrator)
pcapscan/quic.py -- PR-40, QUIC Initial decryption (RFC 9001)
pcapscan/ikev2.py -- PR-41, IKEv2 SA proposals over UDP
pcapscan/tunnels.py -- PR-42, GRE/ERSPAN/VXLAN/GENEVE (#12, offline)
pcapscan/cleartext.py -- PR-43, the inventory of what is not protected
docs/ -- eight pages; README cut from 344 lines to 228
-------------------------------------------------------------------------- The blocker: this tool could not read a UDP packet --------------------------------------------------------------------------
decode_framewalked to TCP and returned None for everything else, with no counter and no mention in the stats. Measured over the capture corpus that was 30,530 of 159,929 packets -- 19% -- dropped in silence, including 11,722 QUIC packets on port 443. A monitor whose purpose is to report what cryptography is in use was blind to the transport that now carries a growing share of the web's TLS 1.3 handshakes, and the three UDP protocols this wave adds would each have had to invent their own plumbing to get at it.So the seam came first, and it is the integrator's work rather than any one PR's:
decode_datagram, sharing the link-layer, VLAN, IPv4-options and IPv6-extension-chain walk withdecode_framethrough a new_walk_network. One walk, not two. Every defect that walk has ever had -- the fixed 14-byte Ethernet header, the fixed 20-byte IPv4 header, the unwalked IPv6 chain -- produced plausible wrong offsets rather than an error, and a second copy would be a second place to reintroduce them. It also refuses non-initial IPv4 fragments, which TCP never needed and a near-MTU QUIC Initial very much does.pcapscan/datagrams.py, which keys UDP flows and discovers handlers rather than requiring registration:HANDLER_MODULESnames the modules, each is imported if present and skipped if not. That is what let four people write four protocols at once without editing one shared list.SessionBuilder, so the CLI,iter_sessionsand the upload sandbox all get UDP without further change.-------------------------------------------------------------------------- PR-40 -- QUIC, and the post-quantum traffic nobody could see -------------------------------------------------------------------------- This is the headline and it is a large one.
RFC 9001 derives Initial packet protection from a published constant salt and the client's Destination Connection ID, which is in the clear in the header. No keys are needed and none are used: the secrecy of a QUIC handshake never rested on the Initial keys. The handler walks long headers, removes header protection, opens the packet with AES-128-GCM, reassembles CRYPTO frames by offset across coalesced packets, and hands the result to
pcapscan.sessions.Session-- the same object the TCP path builds.That reuse is the design. There is no second ClientHello parser and no second document shape, so JA4, the extension census, ECH, the key-share group, the resumption logic and the post-quantum classification all arrived with no change to
cryptomon/analysis.py, the CBOM, the CSV export or the dashboard.Over the corpus: 86 QUIC flows claimed, 468 Initials seen, 468 decrypted, 0 undecryptable, 86 ClientHellos and 60 ServerHellos recovered. 69 of the 86 hellos were still in flight across more than one packet, so a single-packet reader would have got a fragment of each.
clients offering a post-quantum hybrid 69 / 86 = 80.2% (TCP: 71.6%)
servers accepting one 35 / 60 = 58.3% (TCP: 20.4%)
ECH offered 74 / 86 = 86.0% (TCP: 36.5%)
ALPN h3 on all 86; no TCP hello offers it
Whole-corpus effect, measured before and after:
hybrid key exchanges 81 -> 116 (+43%)
quantum-safe fraction 14.4% -> 18.6%
TLS 1.3 sessions 499 -> 585 (+17%)
Sixty-nine post-quantum key exchanges were happening over QUIC and this tool could not see one of them. Thirty-five of them completed.
The honest bound on that: of the 11,722 QUIC packets, 10,984 are short-header 1-RTT and will never be read by any keyless tool. They are now counted, not read. The reachable population is 762 long-header packets and the 468 Initials inside them, and that is the whole handshake, which is the point.
Verification was not straightforward and is worth recording. tshark 3.4 on this machine cannot decrypt QUIC v1 -- it predates RFC 9000, carries draft-29's salt, and fails every Initial with a checktag error while calling version 1 "Unknown". Rather than take that as a disagreement, the agent decrypted one packet under three candidate salts to show which was right, then built a different oracle: it re-framed all 86 decrypted CRYPTO streams as ordinary TLS-over-TCP in a synthetic capture and let tshark's TLS dissector read them. 86/86 parsed as ClientHello, SNI agreed 86/86, and tshark reported key-share code points 0x6399 and 0x11EC exactly where we name X25519Kyber768Draft00 and X25519MLKEM768. RFC 9001 Appendix A's published test vectors are pinned in the test file as well.
-------------------------------------------------------------------------- PR-41 -- IKEv2
-------------------------------------------------------------------------- IKEv2 is the one protocol here that states its cryptography instead of implying it: RFC 7296 puts the encryption algorithm, the PRF, the integrity algorithm and the key exchange on the wire as four separately numbered transforms, in the clear, before anything is encrypted. So almost nothing is inferred.
Transform names are chosen to be exactly the strings
cryptomon/analysis.pyalready knows, so no additions to the marker tables were needed. The RFC 9370 case is the good one: aKE=x25519withADDKE1=ml-kem-768is emitted as the single namex25519+ml-kem-768, which normalises tox25519mlkem768, in which the existing table finds one post-quantum marker and one classical one and answers hybrid -- the true answer, and the one neither half gives alone.Port 4500 carries IKE, ESP and NAT keepalives on the same port, distinguished by a four-byte non-ESP marker. Getting that wrong means parsing ciphertext as an IKE header and reporting a proposal made of noise; a marker-shaped datagram is therefore tried both ways. ESP is reported as present-and-opaque, which is a useful finding rather than a failure -- and
opaque_flowsis a new readiness field so that "we could not see it" is never reported as "there was none".There is no IKEv2 in the corpus, verified independently rather than taken on trust: 0 packets on UDP 500 or 4500 in all 160,221. Everything quoted about its behaviour comes from eight generated fixtures. The one corpus-backed claim is the negative: 30,531 real datagrams offered to the detector, 0 claimed, 0 exceptions.
The CBOM now fills
ikev2TransformTypes--encr,prf,integ,ke-- which is a CycloneDX 1.6 field this project has had a schema for and nothing to put in.esnandauthare deliberately left empty: ESN is not carried, and IKEv2's authentication method is negotiated inside the encrypted IKE_AUTH, so a passive observer never sees it. Emitting either would invent a value the traffic did not contain.-------------------------------------------------------------------------- PR-42 -- tunnels (#12, the offline half)
-------------------------------------------------------------------------- Every measurement in this repository comes from a capture taken on an endpoint. That is not how anyone monitors a corporate network: they mirror a port, and the traffic arrives wrapped in GRE inside IP. To this tool, every one of those frames was unreadable.
Unwrapping happens before framing, in its own module, not as recursion inside
decode_frame. Peeling a tunnel does not yield a transport header -- it yields another whole frame needing the entire walk run over it again -- and putting that insidedecode_framewould hand every caller, the live eBPF adapter included, a recursion it did not ask for with an attacker-chosen depth. The stage generalises: GRE (RFC 2784/2890, with the variable header its flag bits imply), ERSPAN Types I, II and III, VXLAN, GENEVE, IP-in-IP and 6in4, bounded to four levels.ERSPAN Type I is the trap and is handled: it is distinguished from Type II only by the GRE sequence flag, so a parser assuming a header is always present eats eight bytes of the real Ethernet frame and then decodes plausible nonsense.
The proof is the one that matters:
tls12_certificate.pcapwrapped frame-by-frame in ERSPAN Type II. Today's pipeline drops 19 of 19 frames and reports nothing. With the stage, the pipeline returns the identical session document --==on the dict, same ciphersuite, samesha384.badssl.com, same timestamp to the microsecond.The ERSPAN truncation bit is carried through to a counter and onto the session, because a switch saying "I cut this frame short" is labelled missing data, and both times this project has reported a wrong answer that looked right -- truncated ClientHellos parsed as whole, tshark's desegmenter giving up in silence -- the data was missing without saying so.
The live eBPF path is still blind to tunnels, and #12 should not be closed as though it covered both halves.
-------------------------------------------------------------------------- PR-43 -- what is not protected at all
-------------------------------------------------------------------------- Scope changed on evidence. The backlog's PR-43 leads with DTLS; there is not one DTLS record in the corpus, nor any WireGuard. What is there is 30,531 UDP datagrams, most of them entirely unencrypted. "This uses RSA-2048, which a quantum computer breaks" and "this uses nothing, which anyone on the segment breaks today" are both CBOM findings, and the second is actionable this afternoon.
Fourteen protocols identified by content: DNS (with DNSSEC state read properly, from the EDNS0 DO bit and the RRSIG/DS/DNSKEY records), mDNS, LLMNR, NetBIOS-NS and -DGM, DHCP, NTP, CLDAP, STUN/TURN, HSRP v1 and v2, SNMP v1/v2c/v3, syslog, SSDP and BigFix. 1,058 flows claimed over the corpus, zero false positives, and no flow on port 443 taken.
Of the non-QUIC UDP: 61.4% has no confidentiality and none of it is encrypted. The four-rung protection ladder earns its place on the HSRP result -- all 15,867 hellos carry an all-zero plaintext auth field and a keyed-MD5 authentication TLV, so the gateway redundancy is authenticated by a 1991 hash. That is
obsolete, which is neithernonenorauthenticated only, and the distinction is the finding.Also: zero DNSSEC and zero EDNS0 in 2,134 DNS messages -- not one client on this network even asks for signatures. 764 of those queries (35.8%) are for HTTPS resource records, which is where the ECH configuration lives: this corpus asks for ECH keys in the clear 764 times.
Nothing identifying is recorded. Not a query name, not an mDNS instance name, not a NetBIOS name, not a DHCP hostname. Service types and record types survive; names are counted via keyed BLAKE2 tokens under a per-process random key, because a stable digest of a hostname is a hostname when the name space is a corporate network. No community string, HSRP auth string or ICE username is ever written down -- only that one is present and in the clear.
-------------------------------------------------------------------------- Documentation
--------------------------------------------------------------------------
README.mdrestructured to route rather than explain (344 -> 228 lines), anddocs/added: install, configuration, offline analysis, the service, the live sensor, reading a report, architecture, and a 623-line troubleshooting page mined from this repository's own commit history and module docstrings. Every command with a prompt was run; anything needing Linux, root, bcc or a live interface is marked "not verified on this machine" rather than implied.Thirteen inaccuracies in the old README are fixed, among them: port 990 documented as sftp when it is FTPS, "IPv6 support" still listed as a TODO four waves after it shipped,
http://0.0.0.0:8000/docsoffered as a browsable address, and anenp0s1interface default that does not exist.-------------------------------------------------------------------------- Defects found and fixed
Summary.addfell through to_add_tls, so the 1,058 cleartext UDP flows arrived in the readiness report as resumed TLS sessions -- moving the quantum-safe fraction by inventing sessions that never negotiated anything. Found independently by two agents. Fixed with an explicit branch, plus_add_ikev2for the protocol that does have a key exchange to classify.pcapscan/sandbox.pynever got the UDP seam, so a capture uploaded through the browser was analysed by a strictly weaker parser than the same file on the command line -- silently, because a report of nothing looks like a capture with nothing in it.--no-certificateslost the data it promised to keep.--helpsaid "keep the raw chain"; the flag passedlambda _body: [], so the chain was parsed to nothing and the DER discarded. Now aKEEP_DERsentinel../ubuntu-setup.shwas "permission denied" on a fresh clone -- anddeploy/README.md's own quickstart beginssudo ./create-service.sh.BrokenPipeErrorreached stderr on| headand| mongoimport, both of which the README recommends: catching the write is not enough, because CPython flushes stdout again at shutdown where noexceptcan reach it.flows_unrecognisedwas only incremented at the 64-datagram cap, so a two-datagram exchange was counted nowhere and 1,071 of 1,164 flows fell in the gap. A stats line whose parts do not add up is worse than no stats line. Nowflows_undetectedcloses it: 1058 + 86 + 8 + 18 + 2 = 1172.DatagramRouter.pushwas unguarded while_detectwas guarded. One handler raising ended the capture for every other protocol sharing the loop.DES,DES40and the IKEv2ENCR_DESfamily had no strength entry, so they were reported with no symmetric strength at all. This also fixesTLS_RSA_EXPORT_WITH_DES40_CBC_SHAon the TLS side.self.router = router or None, three lines below a comment warning thatDatagramRouterdefines__len__so an empty one is falsy. The comment now names the incident.-------------------------------------------------------------------------- Verified
pytest -q 1508 passed, 19 skipped (was 1149, 18)
pytest -m smoke -q 1370 passed (was 1032)
with a corpus MongoDB 1523 passed, 4 skipped
compileall clean
fuzz, 11 targets 249,193 cases, 0 failures
tshark oracle no drift
The whole real corpus, end to end: 2,404 sessions (1,346 TLS, 1,058 cleartext), 624 key exchanges performed, 116 hybrid, 508 classical, 61 post-quantum offers refused, quantum-safe fraction 18.6%.