pcap-doctor 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (173) hide show
  1. pcap_doctor-0.2.0/.github/ISSUE_TEMPLATE/bug.md +43 -0
  2. pcap_doctor-0.2.0/.github/ISSUE_TEMPLATE/config.yml +8 -0
  3. pcap_doctor-0.2.0/.github/ISSUE_TEMPLATE/core-change.md +34 -0
  4. pcap_doctor-0.2.0/.github/ISSUE_TEMPLATE/detector.md +53 -0
  5. pcap_doctor-0.2.0/.github/PULL_REQUEST_TEMPLATE.md +31 -0
  6. pcap_doctor-0.2.0/.github/workflows/action-selftest.yml +30 -0
  7. pcap_doctor-0.2.0/.github/workflows/ci.yml +58 -0
  8. pcap_doctor-0.2.0/.github/workflows/release.yml +84 -0
  9. pcap_doctor-0.2.0/.gitignore +25 -0
  10. pcap_doctor-0.2.0/AGENTS.md +118 -0
  11. pcap_doctor-0.2.0/CHANGELOG.md +168 -0
  12. pcap_doctor-0.2.0/CONTRIBUTING.md +136 -0
  13. pcap_doctor-0.2.0/LICENSE +32 -0
  14. pcap_doctor-0.2.0/Makefile +66 -0
  15. pcap_doctor-0.2.0/NOTICE +24 -0
  16. pcap_doctor-0.2.0/PKG-INFO +710 -0
  17. pcap_doctor-0.2.0/README.md +691 -0
  18. pcap_doctor-0.2.0/action.yml +78 -0
  19. pcap_doctor-0.2.0/captures/dns-mdns.pcap +0 -0
  20. pcap_doctor-0.2.0/captures/dns_port.pcap +0 -0
  21. pcap_doctor-0.2.0/captures/dtls12-aes128ccm8.pcap +0 -0
  22. pcap_doctor-0.2.0/captures/ntp.pcap +0 -0
  23. pcap_doctor-0.2.0/captures/quic-with-secrets.pcapng +0 -0
  24. pcap_doctor-0.2.0/captures/retrans-tls.pcap +0 -0
  25. pcap_doctor-0.2.0/captures/sip-rtp.pcapng +0 -0
  26. pcap_doctor-0.2.0/captures/sip.pcapng +0 -0
  27. pcap_doctor-0.2.0/captures/snakeoil-dtls.pcap +0 -0
  28. pcap_doctor-0.2.0/captures/tftp.pcap +0 -0
  29. pcap_doctor-0.2.0/captures/tls-renegotiation.pcap +0 -0
  30. pcap_doctor-0.2.0/captures/tls12-aes128ccm.pcap +0 -0
  31. pcap_doctor-0.2.0/captures/tls12-aes256gcm.pcap +0 -0
  32. pcap_doctor-0.2.0/captures/tls12-chacha20poly1305.pcap +0 -0
  33. pcap_doctor-0.2.0/captures/tls13-rfc8446.pcap +0 -0
  34. pcap_doctor-0.2.0/docs/architecture.md +96 -0
  35. pcap_doctor-0.2.0/docs/assets/logo-dark.svg +9 -0
  36. pcap_doctor-0.2.0/docs/assets/logo-light.svg +9 -0
  37. pcap_doctor-0.2.0/docs/cipher-policy.md +115 -0
  38. pcap_doctor-0.2.0/docs/detector-authoring.md +154 -0
  39. pcap_doctor-0.2.0/docs/issue-board.md +138 -0
  40. pcap_doctor-0.2.0/docs/severity-model.md +56 -0
  41. pcap_doctor-0.2.0/docs/subagent-playbook.md +154 -0
  42. pcap_doctor-0.2.0/docs/tshark-fields.md +70 -0
  43. pcap_doctor-0.2.0/npm/bin/pcap-doctor.js +29 -0
  44. pcap_doctor-0.2.0/npm/package.json +22 -0
  45. pcap_doctor-0.2.0/npm/test/launcher.test.js +50 -0
  46. pcap_doctor-0.2.0/pyproject.toml +57 -0
  47. pcap_doctor-0.2.0/scripts/fetch_captures.sh +47 -0
  48. pcap_doctor-0.2.0/scripts/gen_cipher_suites.py +294 -0
  49. pcap_doctor-0.2.0/scripts/make_fixtures.py +663 -0
  50. pcap_doctor-0.2.0/scripts/pcap-doctor-action.sh +97 -0
  51. pcap_doctor-0.2.0/src/pcapforensics/__init__.py +6 -0
  52. pcap_doctor-0.2.0/src/pcapforensics/baseline.py +27 -0
  53. pcap_doctor-0.2.0/src/pcapforensics/certificates.py +188 -0
  54. pcap_doctor-0.2.0/src/pcapforensics/cli/__init__.py +47 -0
  55. pcap_doctor-0.2.0/src/pcapforensics/cli/__main__.py +5 -0
  56. pcap_doctor-0.2.0/src/pcapforensics/cli/_console.py +43 -0
  57. pcap_doctor-0.2.0/src/pcapforensics/cli/_summary.py +44 -0
  58. pcap_doctor-0.2.0/src/pcapforensics/cli/analyze.py +123 -0
  59. pcap_doctor-0.2.0/src/pcapforensics/cli/ci.py +47 -0
  60. pcap_doctor-0.2.0/src/pcapforensics/cli/inspect.py +133 -0
  61. pcap_doctor-0.2.0/src/pcapforensics/cli/install.py +82 -0
  62. pcap_doctor-0.2.0/src/pcapforensics/cli/rules.py +51 -0
  63. pcap_doctor-0.2.0/src/pcapforensics/cli/watch.py +122 -0
  64. pcap_doctor-0.2.0/src/pcapforensics/cli/why.py +73 -0
  65. pcap_doctor-0.2.0/src/pcapforensics/config.py +187 -0
  66. pcap_doctor-0.2.0/src/pcapforensics/data/cipher_names.tsv +424 -0
  67. pcap_doctor-0.2.0/src/pcapforensics/data/cipher_suites.json +6257 -0
  68. pcap_doctor-0.2.0/src/pcapforensics/data_ciphers.py +194 -0
  69. pcap_doctor-0.2.0/src/pcapforensics/detectors/__init__.py +1 -0
  70. pcap_doctor-0.2.0/src/pcapforensics/detectors/_template.py +52 -0
  71. pcap_doctor-0.2.0/src/pcapforensics/detectors/base.py +76 -0
  72. pcap_doctor-0.2.0/src/pcapforensics/detectors/dns_quic_ssh.py +362 -0
  73. pcap_doctor-0.2.0/src/pcapforensics/detectors/sip_rtp.py +232 -0
  74. pcap_doctor-0.2.0/src/pcapforensics/detectors/tls_cipher.py +573 -0
  75. pcap_doctor-0.2.0/src/pcapforensics/detectors/transport_exposure.py +428 -0
  76. pcap_doctor-0.2.0/src/pcapforensics/handoff.py +162 -0
  77. pcap_doctor-0.2.0/src/pcapforensics/index.py +1117 -0
  78. pcap_doctor-0.2.0/src/pcapforensics/models.py +465 -0
  79. pcap_doctor-0.2.0/src/pcapforensics/output.py +96 -0
  80. pcap_doctor-0.2.0/src/pcapforensics/pipeline.py +101 -0
  81. pcap_doctor-0.2.0/src/pcapforensics/policy.py +49 -0
  82. pcap_doctor-0.2.0/src/pcapforensics/prompts.py +61 -0
  83. pcap_doctor-0.2.0/src/pcapforensics/registry.py +52 -0
  84. pcap_doctor-0.2.0/src/pcapforensics/render/__init__.py +1 -0
  85. pcap_doctor-0.2.0/src/pcapforensics/render/markdown.py +532 -0
  86. pcap_doctor-0.2.0/src/pcapforensics/render/mermaid.py +128 -0
  87. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/BEACONING_SHAPE.md +14 -0
  88. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/CLEARTEXT_CREDENTIAL.md +14 -0
  89. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/CLEARTEXT_SERVICE.md +13 -0
  90. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/DNS_CLEARTEXT.md +13 -0
  91. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/DNS_EXTERNAL_RESOLVER.md +13 -0
  92. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/DNS_TUNNEL_SHAPE.md +13 -0
  93. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/HTTP_BASIC_AUTH.md +13 -0
  94. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/HTTP_CLEARTEXT.md +14 -0
  95. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/HTTP_CLEARTEXT_AUTH.md +14 -0
  96. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/HTTP_COOKIE_NO_SECURE.md +13 -0
  97. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/QUIC_PAYLOAD_OPAQUE.md +13 -0
  98. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/RTP_MEDIA_UNPROTECTED.md +13 -0
  99. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/RTP_VOLUME_ANOMALY.md +13 -0
  100. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/SDP_NO_CRYPTO_ATTR.md +13 -0
  101. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/SERVICE_ON_ODD_PORT.md +13 -0
  102. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/SIP_CLEARTEXT_SIGNALLING.md +13 -0
  103. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/SSH_TERRAPIN_EXPOSED.md +14 -0
  104. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/SSH_WEAK_CIPHER.md +13 -0
  105. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/SSH_WEAK_HOSTKEY.md +13 -0
  106. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/SSH_WEAK_KEX.md +13 -0
  107. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/SSH_WEAK_MAC.md +13 -0
  108. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/SYN_SCAN_SHAPE.md +14 -0
  109. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_CERT_EXPIRED.md +14 -0
  110. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_CERT_EXPIRED_NOW.md +13 -0
  111. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_CERT_EXPIRING.md +13 -0
  112. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_CERT_WEAK_KEY.md +13 -0
  113. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_CERT_WEAK_SIGALG.md +14 -0
  114. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_CIPHER_UNKNOWN.md +15 -0
  115. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_CIPHER_WEAK.md +14 -0
  116. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_FATAL_ALERT.md +14 -0
  117. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_HANDSHAKE_TRUNCATED.md +14 -0
  118. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_JA3_FLEET.md +13 -0
  119. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_LEGACY_RECORD_VERSION.md +14 -0
  120. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_NO_FORWARD_SECRECY.md +14 -0
  121. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_OFFERS_WEAK_CIPHERS.md +14 -0
  122. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_SELF_SIGNED_CHAIN.md +13 -0
  123. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_VERSION_DEPRECATED.md +16 -0
  124. pcap_doctor-0.2.0/src/pcapforensics/rule_docs/TLS_WARNING_ALERT.md +13 -0
  125. pcap_doctor-0.2.0/src/pcapforensics/rules.py +75 -0
  126. pcap_doctor-0.2.0/src/pcapforensics/scoring.py +35 -0
  127. pcap_doctor-0.2.0/src/pcapforensics/templates/agent-guide.md +20 -0
  128. pcap_doctor-0.2.0/src/pcapforensics/templates/workflow.yml +25 -0
  129. pcap_doctor-0.2.0/src/pcapforensics/tshark.py +805 -0
  130. pcap_doctor-0.2.0/tests/conftest.py +82 -0
  131. pcap_doctor-0.2.0/tests/fixtures/beaconing.pcap +0 -0
  132. pcap_doctor-0.2.0/tests/fixtures/dns_external.pcap +0 -0
  133. pcap_doctor-0.2.0/tests/fixtures/dns_tunnel.pcap +0 -0
  134. pcap_doctor-0.2.0/tests/fixtures/ftp_ldap_creds.pcap +0 -0
  135. pcap_doctor-0.2.0/tests/fixtures/http_bare_token.pcap +0 -0
  136. pcap_doctor-0.2.0/tests/fixtures/http_basic.pcap +0 -0
  137. pcap_doctor-0.2.0/tests/fixtures/http_cleartext.pcap +0 -0
  138. pcap_doctor-0.2.0/tests/fixtures/mixed_ciphers.pcap +0 -0
  139. pcap_doctor-0.2.0/tests/fixtures/no_pfs_tls12.pcap +0 -0
  140. pcap_doctor-0.2.0/tests/fixtures/sip_rtp.pcap +0 -0
  141. pcap_doctor-0.2.0/tests/fixtures/snmp_creds.pcap +0 -0
  142. pcap_doctor-0.2.0/tests/fixtures/ssh_weak.pcap +0 -0
  143. pcap_doctor-0.2.0/tests/fixtures/strong_tls13.pcap +0 -0
  144. pcap_doctor-0.2.0/tests/fixtures/syn_scan.pcap +0 -0
  145. pcap_doctor-0.2.0/tests/fixtures/telnet_login.pcap +0 -0
  146. pcap_doctor-0.2.0/tests/fixtures/weak_tls.pcap +0 -0
  147. pcap_doctor-0.2.0/tests/test_baseline.py +80 -0
  148. pcap_doctor-0.2.0/tests/test_ci_install.py +91 -0
  149. pcap_doctor-0.2.0/tests/test_cipher_registry.py +147 -0
  150. pcap_doctor-0.2.0/tests/test_citations.py +41 -0
  151. pcap_doctor-0.2.0/tests/test_cli_smoke.py +27 -0
  152. pcap_doctor-0.2.0/tests/test_config.py +108 -0
  153. pcap_doctor-0.2.0/tests/test_corpus.py +288 -0
  154. pcap_doctor-0.2.0/tests/test_detectors.py +508 -0
  155. pcap_doctor-0.2.0/tests/test_docs.py +77 -0
  156. pcap_doctor-0.2.0/tests/test_handoff.py +133 -0
  157. pcap_doctor-0.2.0/tests/test_http_cleartext.py +63 -0
  158. pcap_doctor-0.2.0/tests/test_install.py +51 -0
  159. pcap_doctor-0.2.0/tests/test_json_out.py +53 -0
  160. pcap_doctor-0.2.0/tests/test_policy.py +68 -0
  161. pcap_doctor-0.2.0/tests/test_profile.py +74 -0
  162. pcap_doctor-0.2.0/tests/test_prompts.py +64 -0
  163. pcap_doctor-0.2.0/tests/test_rebrand.py +27 -0
  164. pcap_doctor-0.2.0/tests/test_release_meta.py +49 -0
  165. pcap_doctor-0.2.0/tests/test_review_fixes.py +123 -0
  166. pcap_doctor-0.2.0/tests/test_rules_catalog.py +52 -0
  167. pcap_doctor-0.2.0/tests/test_rules_cli.py +40 -0
  168. pcap_doctor-0.2.0/tests/test_sarif.py +78 -0
  169. pcap_doctor-0.2.0/tests/test_scoring.py +38 -0
  170. pcap_doctor-0.2.0/tests/test_summary.py +67 -0
  171. pcap_doctor-0.2.0/tests/test_tshark_layer.py +236 -0
  172. pcap_doctor-0.2.0/tests/test_watch.py +70 -0
  173. pcap_doctor-0.2.0/tests/test_why.py +80 -0
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: Bug
3
+ about: Something the analyzer gets wrong, or cannot run at all
4
+ title: "bug: "
5
+ labels: bug
6
+ assignees: ''
7
+ ---
8
+
9
+ **What happened**
10
+
11
+ **What it should have done**
12
+
13
+ **Reproduce**
14
+
15
+ ```bash
16
+ pcap-doctor analyze <capture> --out /tmp/x --no-cache
17
+ ```
18
+
19
+ **Wrong output, verbatim**
20
+
21
+ ```
22
+ <paste the finding, the note, or the traceback -- exactly as printed>
23
+ ```
24
+
25
+ **Ground truth**
26
+
27
+ How you know it is wrong. `tshark -r <capture> -Y '<filter>' -T fields -e <field>` output is ideal.
28
+
29
+ - [ ] `tshark` version: `pcap-doctor doctor`
30
+ - [ ] capture sha256: `pcap-doctor analyze <capture> | head -5`
31
+ - [ ] cache cleared or `--no-cache` used
32
+
33
+ **Scope**
34
+
35
+ - [ ] parser/index (core-owned)
36
+ - [ ] a detector
37
+ - [ ] rendering
38
+ - [ ] tshark field drift
39
+
40
+ **Suggested location**
41
+
42
+ File and function, if you know it. If this is a schema gap, propose the field rather than working
43
+ around it in a detector: `AGENTS.md` §1.
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: true
2
+ contact_links:
3
+ - name: Severity and confidence policy
4
+ url: https://github.com/Vaibhav91one/pcap-forensics/blob/main/docs/severity-model.md
5
+ about: How to choose severity and confidence for a finding.
6
+ - name: Writing a detector
7
+ url: https://github.com/Vaibhav91one/pcap-forensics/blob/main/docs/detector-authoring.md
8
+ about: The detector contract, the index API, and the fixture rules.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: Core change
3
+ about: Change models.py, index.py, tshark.py, data_ciphers.py or a renderer
4
+ title: "core: "
5
+ labels: core
6
+ assignees: ''
7
+ ---
8
+
9
+ Core changes are a **merge event**, not a parallel one (`docs/subagent-playbook.md`).
10
+
11
+ **What changes**
12
+
13
+ Paths: `models.py` / `index.py` / `tshark.py` / `data_ciphers.py` / `certificates.py` / `render/*`
14
+
15
+ **Does `report.json`'s schema change?**
16
+
17
+ - [ ] no — additive only, `schema_version` unchanged
18
+ - [ ] yes, additive — bump minor
19
+ - [ ] yes, breaking — bump major, and list the migration in the issue
20
+
21
+ **If a new field is proposed**
22
+
23
+ | Field | Type | Example from a real capture | Read by |
24
+ |---|---|---|---|
25
+ | | | | |
26
+
27
+ **Which detectors are blocked without it**
28
+
29
+ **Migration / rollout**
30
+
31
+ - [ ] tests updated or added
32
+ - [ ] `docs/` updated (tshark-fields, severity-model, cipher-policy as applicable)
33
+ - [ ] `make verify` green
34
+ - [ ] agents holding open branches told to rebase
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: Detector
3
+ about: Add a detector (one check, one file, one claim)
4
+ title: "detector: "
5
+ labels: detector
6
+ assignees: ''
7
+ ---
8
+
9
+ **Which detector file**
10
+
11
+ `src/pcapforensics/detectors/<name>.py` — new or existing.
12
+
13
+ **What it detects**
14
+
15
+ One sentence, in the words an analyst would use. Not "analyzes X" — "flags when Y".
16
+
17
+ **Why it matters**
18
+
19
+ What is the consequence if the condition is true and the tool is right?
20
+
21
+ **Input that reproduces it**
22
+
23
+ - capture: `<path or how to build it>`
24
+ - observable: `<the exact frame, field, or shape>`
25
+ - expected output:
26
+
27
+ ```
28
+ <one line: the finding code, severity, confidence>
29
+ ```
30
+
31
+ **Fields it needs from CaptureIndex**
32
+
33
+ | Field | Type | Example value | Already present? |
34
+ |---|---|---|---|
35
+ | `index.…` | | | yes / no (file an issue if no) |
36
+
37
+ **Fixture**
38
+
39
+ - [ ] added to `scripts/make_fixtures.py` and regenerated (`make fixtures`)
40
+ - [ ] deterministic (no randomness, no clock, no network)
41
+ - [ ] negative case too: a fixture where this must stay silent
42
+
43
+ **Checklist (AGENTS.md §3)**
44
+
45
+ - [ ] pure `detect(index) -> list[Finding]`, no I/O
46
+ - [ ] stable `code` and `scope`; ids stable across runs
47
+ - [ ] every finding has frame-numbered evidence with a non-empty value
48
+ - [ ] every finding has a `remediation`, plus references where a standard applies
49
+ - [ ] unknown input yields a finding or a note, never a silent pass
50
+ - [ ] no secret material can reach an artifact
51
+ - [ ] tests assert on codes, not prose
52
+ - [ ] README detector table has its row
53
+ - [ ] `make verify` green, `pcap-doctor doctor` passes
@@ -0,0 +1,31 @@
1
+ Closes #
2
+
3
+ ## What I saw
4
+
5
+ <!-- The bug or gap, with the capture/fixture and frame that shows it. -->
6
+
7
+ ## What changed
8
+
9
+ <!-- Files touched and why. Stay inside the allowlist on the issue (AGENTS.md §1-§2). -->
10
+
11
+ ## What I verified
12
+
13
+ <!-- Paste the evidence, not a claim. -->
14
+
15
+ - [ ] The new test **fails on the old code** (paste the failing line) and passes after
16
+ - [ ] `make verify` is green (ruff, mypy strict, pytest)
17
+ - [ ] `pcap-doctor doctor` passes
18
+ - [ ] New or changed fixtures are generated by `scripts/make_fixtures.py` and regenerate byte-for-byte
19
+
20
+ ## Deliberately not done
21
+
22
+ <!-- Follow-ups, known limits, anything out of scope. File issues for them. -->
23
+
24
+ ## Checklist (AGENTS.md §3)
25
+
26
+ - [ ] Every finding cites a real frame and has a stable `code` and `scope`
27
+ - [ ] Every finding has a remediation and, when a standard applies, a reference a reader can look up
28
+ - [ ] No secret can reach a report (redacted, with a test that greps the artifacts)
29
+ - [ ] Heuristics report `confidence: low`; severity is about impact, not urgency
30
+ - [ ] Tests assert on finding codes, not prose
31
+ - [ ] README detector table and CHANGELOG updated if behaviour changed
@@ -0,0 +1,30 @@
1
+ # Runs this repository's own action (action.yml) on pull requests that change it, against the test fixtures.
2
+ name: action-selftest
3
+
4
+ on:
5
+ pull_request:
6
+ paths:
7
+ - action.yml
8
+ - scripts/pcap-doctor-action.sh
9
+ - src/**
10
+ - .github/workflows/action-selftest.yml
11
+
12
+ permissions:
13
+ contents: read
14
+ pull-requests: write
15
+ security-events: write
16
+
17
+ jobs:
18
+ selftest:
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - uses: actions/checkout@v4
22
+ - id: doctor
23
+ uses: ./
24
+ with:
25
+ captures: "tests/fixtures/*.pcap"
26
+ # This job proves the action runs end to end (install, analysis, comment, SARIF). The gate itself is
27
+ # tested in tests/test_ci_install.py; gating here would fail every PR that adds a fixture.
28
+ fail-on: none
29
+ - name: The summary lists every fixture
30
+ run: test "$(grep -c '^| `tests/fixtures/' "${{ steps.doctor.outputs.summary }}")" -eq "$(ls tests/fixtures/*.pcap | wc -l)"
@@ -0,0 +1,58 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+
8
+ jobs:
9
+ verify:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.12"
16
+ - name: Install tshark
17
+ run: |
18
+ sudo apt-get update
19
+ sudo DEBIAN_FRONTEND=noninteractive apt-get install -y tshark
20
+ tshark --version | head -1
21
+ - uses: astral-sh/setup-uv@v5
22
+ - name: Install project
23
+ run: |
24
+ uv venv
25
+ uv pip install -e '.[dev]'
26
+ - name: Environment check
27
+ run: uv run pcap-doctor doctor
28
+ - name: Fetch regression corpus
29
+ run: ./scripts/fetch_captures.sh
30
+ - name: Fixtures are reproducible
31
+ run: |
32
+ make fixtures
33
+ static=$(.venv/bin/python -c "import sys; sys.path.insert(0, 'scripts'); import make_fixtures as m; print(' '.join('tests/fixtures/' + n for n in m.STATIC_FIXTURES))")
34
+ git diff --exit-code -- $static || (echo "byte-built fixtures drifted; commit the regenerated files" && exit 1)
35
+ - name: Lint
36
+ run: .venv/bin/ruff check .
37
+ - name: Typecheck
38
+ run: .venv/bin/mypy
39
+ - name: Tests
40
+ run: .venv/bin/python -m pytest -q
41
+ - name: Reports render end to end
42
+ run: |
43
+ for cap in captures/*.pcap captures/*.pcapng; do
44
+ .venv/bin/pcap-doctor analyze "$cap" --out /tmp/pf-ci --quiet || true
45
+ done
46
+ test -s /tmp/pf-ci/report.json
47
+ test -s /tmp/pf-ci/04-diagrams.md
48
+
49
+ npm-launcher:
50
+ runs-on: ubuntu-latest
51
+ steps:
52
+ - uses: actions/checkout@v4
53
+ - uses: actions/setup-node@v4
54
+ with:
55
+ node-version: "20"
56
+ - name: Launcher tests
57
+ working-directory: npm
58
+ run: npm test
@@ -0,0 +1,84 @@
1
+ name: release
2
+
3
+ # Publishes to PyPI when a version tag is pushed (issue #50), then the npm launcher (issue #51).
4
+ # Both use trusted publishing (OIDC): no token or secret exists for either registry (#93).
5
+ on:
6
+ push:
7
+ tags: ["v*"]
8
+
9
+ permissions:
10
+ contents: read
11
+
12
+ jobs:
13
+ build:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+ - uses: astral-sh/setup-uv@v5
18
+ - name: Tag matches the package version
19
+ run: |
20
+ version=$(python3 -c "import tomllib; print(tomllib.load(open('pyproject.toml', 'rb'))['project']['version'])")
21
+ if [ "v$version" != "$GITHUB_REF_NAME" ]; then
22
+ echo "tag $GITHUB_REF_NAME does not match pyproject version v$version"
23
+ exit 1
24
+ fi
25
+ npm_version=$(node -p "require('./npm/package.json').version")
26
+ if [ "$npm_version" != "$version" ]; then
27
+ echo "npm/package.json version $npm_version does not match pyproject version $version"
28
+ exit 1
29
+ fi
30
+ - run: uv build
31
+ - uses: actions/upload-artifact@v4
32
+ with:
33
+ name: dist
34
+ path: dist/
35
+
36
+ pypi:
37
+ needs: build
38
+ runs-on: ubuntu-latest
39
+ environment:
40
+ name: pypi
41
+ url: https://pypi.org/p/pcap-doctor
42
+ permissions:
43
+ id-token: write
44
+ steps:
45
+ - uses: actions/download-artifact@v4
46
+ with:
47
+ name: dist
48
+ path: dist/
49
+ - uses: pypa/gh-action-pypi-publish@release/v1
50
+
51
+ npm:
52
+ needs: pypi
53
+ runs-on: ubuntu-latest
54
+ permissions:
55
+ contents: read
56
+ id-token: write # npm trusted publishing: GitHub proves the workflow's identity; provenance is automatic
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+ - uses: actions/setup-node@v4
60
+ with:
61
+ node-version: "22"
62
+ registry-url: https://registry.npmjs.org
63
+ - name: npm with trusted publishing (needs 11.5.1+)
64
+ run: npm install -g npm@latest
65
+ - name: Launcher tests
66
+ working-directory: npm
67
+ run: npm test
68
+ - name: Publish unless the package is new or this version is already on npm
69
+ working-directory: npm
70
+ run: |
71
+ version=$(node -p "require('./package.json').version")
72
+ if ! versions=$(npm view pcap-doctor versions --json 2>&1); then
73
+ if grep -q E404 <<< "$versions"; then
74
+ echo "::notice::pcap-doctor is not on npm yet. A trusted publisher can only be added to an existing package: publish it once by hand (cd npm && npm login && npm publish --access public), then add this workflow as its trusted publisher."
75
+ exit 0
76
+ fi
77
+ echo "$versions"
78
+ exit 1
79
+ fi
80
+ if node -e 'process.exit([].concat(JSON.parse(process.argv[1])).includes(process.argv[2]) ? 0 : 1)' "$versions" "$version"; then
81
+ echo "::notice::pcap-doctor@$version is already on npm; nothing to publish."
82
+ exit 0
83
+ fi
84
+ npm publish --access public
@@ -0,0 +1,25 @@
1
+ # environments
2
+ .venv/
3
+ __pycache__/
4
+ *.py[cod]
5
+ *.egg-info/
6
+ build/
7
+ dist/
8
+
9
+ # tooling caches
10
+ .pytest_cache/
11
+ .mypy_cache/
12
+ .ruff_cache/
13
+ .coverage
14
+ htmlcov/
15
+
16
+ # analysis output
17
+ out/
18
+ *.pf-report/
19
+
20
+ # tshark pass cache
21
+ .cache/
22
+
23
+ # NOTE: captures/ is intentionally NOT ignored in this repo -- the regression
24
+ # suite needs it and the files are small. Set PF_SKIP_CAPTURES=1 in CI to
25
+ # download them at run time instead of storing them.
@@ -0,0 +1,118 @@
1
+ # AGENTS.md — the contract for automated subagents
2
+
3
+ This file is binding for any agent (human or not) working in this repository. It exists so that
4
+ four agents can work at once, in isolation, without stepping on each other.
5
+
6
+ Read this before you touch anything. Then read `docs/detector-authoring.md` if you are adding a
7
+ detector, or `docs/subagent-playbook.md` if you are running a subagent loop.
8
+
9
+ ---
10
+
11
+ ## 1. Ownership map
12
+
13
+ Exactly one owner per file class. If a task needs a file you do not own, you file an issue; you do
14
+ not edit it.
15
+
16
+ | Path | Owner | Notes |
17
+ |---|---|---|
18
+ | `src/pcapforensics/models.py` | **core** | the schema. frozen after Phase 0 |
19
+ | `src/pcapforensics/index.py` | **core** | builds the only view detectors get |
20
+ | `src/pcapforensics/tshark.py` | **core** | the tshark boundary |
21
+ | `src/pcapforensics/data_ciphers.py` | **core** | crypto reasoning helpers |
22
+ | `src/pcapforensics/certificates.py` | **core** | openssl enrichment |
23
+ | `src/pcapforensics/data/*` | **core** | generated by `make regenerate` |
24
+ | `src/pcapforensics/detectors/<yourname>.py` | **you** | one detector, one file |
25
+ | `src/pcapforensics/render/*` | **core** | detectors must not add renderers |
26
+ | `src/pcapforensics/cli/__init__.py`, `cli/_console.py` | **core** | the app and the command loader |
27
+ | `src/pcapforensics/cli/<command>.py` | **you** (one command, one file) | exports `register(app)`; never edit another command's file |
28
+ | `tests/test_cipher_registry.py`, `test_tshark_layer.py` | **core** | |
29
+ | `tests/fixtures/<yours>.pcap` + its test | **you** | |
30
+ | `README.md` detector table row for your detector | **you** (one line) | do not touch other sections |
31
+ | `pyproject.toml` | **core** | new runtime dependencies require an issue |
32
+
33
+ ## 2. Isolation rules
34
+
35
+ 1. **Stay inside your allowlist.** Your allowlist is written on your issue. Edits outside it are
36
+ rejected in review even when they look like an improvement.
37
+ 2. **One issue, one branch, one PR.** Branch name `agent/<issue-number>-<slug>`.
38
+ 3. **Never import a sibling detector.** `detectors/a.py` must not import `detectors/b.py`. If you
39
+ need shared logic, it goes into `index.py` (core-owned) via an issue.
40
+ 4. **Never edit `models.py` to make your life easier.** Add the field in an issue with a proposed
41
+ shape. Schema changes are a merge event, not a parallel event.
42
+ 5. **No new runtime dependency** without an issue. Dev dependencies (pytest, scapy) are fine.
43
+ 6. **Do not rebase or force-push** other agents' branches. If you need a core change, land the
44
+ core issue first and rebase onto it.
45
+
46
+ ## 3. What "done" means
47
+
48
+ A task is done when **all** of these are true. Review checks them mechanically.
49
+
50
+ - [ ] The detector lives in its own file, is named `dN.<slug>`, and has `enabled = True` only if
51
+ it produces findings on at least one fixture.
52
+ - [ ] `detect()` is a pure function of the `CaptureIndex`: no file I/O, no network, no globals.
53
+ - [ ] Every finding has a **stable** `code` (SCREAMING_SNAKE) and a `scope` that is unique per
54
+ flow/host so `Finding.id` is stable across runs.
55
+ - [ ] Every finding carries at least one `Evidence` with a real frame number and a non-empty value.
56
+ - [ ] Every finding carries `remediation` and at least one `references` entry when a standard
57
+ applies. A finding a reader cannot act on is not finished.
58
+ - [ ] Unknown or unparseable input produces a **finding or a note**, never a silent pass.
59
+ - [ ] No secret material can reach a report. If you touch credentials, use
60
+ `index.redact_auth` or add a redaction of the same shape, and add a test.
61
+ - [ ] A synthetic fixture exists under `tests/fixtures/`, is generated by
62
+ `scripts/make_fixtures.py`, and is deterministic.
63
+ - [ ] Tests assert on **finding codes**, not on prose.
64
+ - [ ] Every new finding `code` has a `RULES` entry in `rules.py` (category and title) and a
65
+ `rule_docs/<CODE>.md` with the four sections *What it means*, *Why it matters*, *How to fix*
66
+ and *How to verify*. `tests/test_rules_catalog.py` and `tests/test_prompts.py` fail otherwise.
67
+ - [ ] The detector row in the README table is updated.
68
+ - [ ] `make verify` is green: `ruff check .`, `mypy` (strict), `pytest`.
69
+ - [ ] `pcap-doctor doctor` still passes.
70
+
71
+ ## 4. Honesty rules
72
+
73
+ These are the project's rules about itself, and a reviewer will fail a PR that breaks them.
74
+
75
+ - Do not claim a fact the capture does not contain. If forward secrecy is undecidable, the answer
76
+ is `None` with a reason, not `False`.
77
+ - Do not round a heuristic into a certainty. If it is a heuristic, set `confidence="low"`.
78
+ - Do not use a severity to express urgency rather than impact.
79
+ - Do not suppress a finding because it is noisy. If it is noisy, that is a policy conversation,
80
+ and the policy lives in `docs/severity-model.md`.
81
+ - If you find a bug in core while working, **do not fix it in your PR**. File it, link it, and keep
82
+ your detector working against the current contract.
83
+
84
+ ## 5. Severity and confidence
85
+
86
+ Severity answers "how bad is this if true"; confidence answers "how sure am I from this capture".
87
+ They are independent and both are required.
88
+
89
+ | Severity | Use when |
90
+ |---|---|
91
+ | `critical` | credential or key material is exposed, or a prohibited cipher is negotiated |
92
+ | `high` | confidentiality of captured traffic is at risk without user action (no PFS, deprecated version, cleartext media) |
93
+ | `medium` | downgrade surface, policy violations, likely-relevant heuristics |
94
+ | `low` | legacy fields, weak hints, context a reviewer should see |
95
+ | `info` | inventory and clustering that is not a defect on its own |
96
+
97
+ `confidence` is `high` only when the evidence is directly visible in the capture.
98
+
99
+ ## 6. Communication
100
+
101
+ - Issue title: `detector: <what it detects>` or `core: <what changes>`.
102
+ - Issue body must name the affected paths, the input that reproduces it, and the expected output.
103
+ - PR description: what you saw, what you changed, what you verified, and what you deliberately did
104
+ not do.
105
+ - If you could not finish, say so in the PR. A partial detector behind `enabled = False` is a
106
+ useful contribution; a half-working enabled detector is not.
107
+
108
+ ## 7. Where the truth lives
109
+
110
+ | Question | File |
111
+ |---|---|
112
+ | What can I read from the index? | `src/pcapforensics/models.py` |
113
+ | How is a cipher judged weak? | `docs/cipher-policy.md` |
114
+ | How is severity chosen? | `docs/severity-model.md` |
115
+ | Which tshark fields do we rely on? | `docs/tshark-fields.md` |
116
+ | How do I write a detector? | `docs/detector-authoring.md` |
117
+ | How do I run a fleet of subagents? | `docs/subagent-playbook.md` |
118
+ | What is frozen and why? | `AGENTS.md` §1 |
@@ -0,0 +1,168 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
5
+ [semantic versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.2.0] — 2026-09-30
10
+
11
+ **pcap-forensics is now pcap-doctor**: one command gives a health score, findings grouped by category,
12
+ explanations, a CI gate on new findings, and a handoff to AI coding agents. `pf` stays as an alias.
13
+ `report.json` keeps `schema_version` `1.4.0`: everything new is computed around the report.
14
+
15
+ ### Added — pcap-doctor
16
+
17
+ - Distribution and command `pcap-doctor`, `--version`; `npx pcap-doctor` launcher (runs the same
18
+ version through `uvx` or `pipx`); PyPI and npm releases by trusted publishing,
19
+ with no stored token (#46, #50, #51, #93).
20
+ - The CLI is a package of self-registering command modules (#45).
21
+ - A rule catalog with eight categories, `rules list` / `rules explain`, and a doc per finding code
22
+ (#47, #49, #61).
23
+ - A local 0-100 health score and a summary grouped by category; `--score`, `--verbose` (#48, #53).
24
+ - `--category`, strict option validation (unknown values exit 2 before tshark runs) (#52).
25
+ - `--json` / `--json-out` envelope, `--sarif` (SARIF 2.1.0), `--baseline` to show and gate only new
26
+ findings (#54, #55, #56).
27
+ - `pcap-doctor.toml` / `[tool.pcap-doctor]`: `disable`, `[severity]`, `[[allow]]` with a reason,
28
+ `categories`, `fail_on`, and the `ota` profile (`--config`, `--profile`); every suppression is
29
+ noted in the report (#57, #58).
30
+ - `why <frame|id>` with `--prompt`; an AI handoff menu after interactive scans (Claude Code, Codex,
31
+ Cursor; `--safe`, `--no-handoff`), agent guidance inside agent shells, and a fenced
32
+ untrusted-data prompt builder (#49, #59, #60).
33
+ - `install` writes agent guides (Claude Code skill, Cursor rule, AGENTS.md block) (#62).
34
+ - `ci install` and a composite GitHub Action that compares each capture with its base-branch copy,
35
+ comments once on the PR and uploads SARIF (#63).
36
+ - `watch -i IFACE`: ring-buffer live capture that prints each finding the first time it appears (#64).
37
+ - `HTTP_CLEARTEXT`: plain HTTP is reported per flow, high when a request path looks like a firmware or
38
+ package download (so `--profile ota` fails it) and medium otherwise; query strings are never written
39
+ to a report (#91). A new fixture, `http_cleartext.pcap`, covers it.
40
+
41
+ ### Changed — pcap-doctor
42
+
43
+ - The npm launcher is published with npm trusted publishing (OIDC) instead of an `NPM_TOKEN` secret;
44
+ the release job skips with a notice when the package is not on npm yet (its first publish is
45
+ manual) or the version is already published (#93).
46
+
47
+ ### Fixed — pcap-doctor
48
+
49
+ - RFC citations in TLS version and cipher-policy text (#72).
50
+ - The tool version in reports now comes from the installed package instead of a hard-coded string (#65).
51
+
52
+ **MVP hardening since 0.1.0 (issues #1–#44):**
53
+
54
+ `report.json` `schema_version` is `1.4.0` (additive: `Flow.first_frame`, `Flow` burst statistics,
55
+ `RtpStream.first_frame`, `SshSession.offered_in`, `TelnetLogin`).
56
+
57
+ ### Added
58
+
59
+ - Telnet logins: passwords typed after a `Password:` prompt are reported as `CLEARTEXT_CREDENTIAL`
60
+ (length and frame only, never the text); Telnet sessions now raise `CLEARTEXT_SERVICE` (#37).
61
+ - `SSH_TERRAPIN_EXPOSED`: chacha20-poly1305, or an EtM MAC with a CBC cipher, offered without strict
62
+ key exchange on both sides (#24).
63
+ - FTP `PASS` arguments and LDAP simple-bind passwords are reported as `CLEARTEXT_CREDENTIAL` (#14).
64
+
65
+ ### Fixed — secrets
66
+
67
+ - SNMP community strings and MySQL queries are redacted where the index builds service details (#1).
68
+ - A scheme-less `Authorization` value (bare token) no longer appears in a finding title or evidence (#15).
69
+
70
+ ### Fixed — evidence and correctness
71
+
72
+ - Every finding cites a real frame: RTP, SYN scan, odd-port, beaconing and DNS resolver evidence no
73
+ longer cite frame 0 or a stream index (#4, #6); SSH evidence cites the KEXINIT, not the banner (#25).
74
+ - The server's SSH KEXINIT no longer overwrites the client's offer (#25).
75
+ - `BEACONING_SHAPE` measures regularity of gaps between bursts; the old check could never fail (#10).
76
+ - `SERVICE_ON_ODD_PORT` no longer reports a client's ephemeral port (#16).
77
+ - `DNS_EXTERNAL_RESOLVER` finds the resolver by port and covers IPv6 and all private ranges (#6);
78
+ `DNS_TUNNEL_SHAPE` names the querying host and no longer counts responses as queries (#18).
79
+ - `chacha20-poly1305@openssh.com` is no longer listed as a weak SSH cipher (#2).
80
+ - Invalid or wrong finding references replaced (e.g. `CWE- exfil`, RFC 8999 on TLS findings) (#3).
81
+ - The service field list is shared with the tshark pass; four names that are not tshark fields were removed (#14).
82
+ - `WELL_KNOWN_PORTS`: 506/522/1194 mapped to the wrong service (#23).
83
+
84
+ ### Changed — docs
85
+
86
+ - README redesigned CLI-first: light/dark logo, badges, a numbered get-started path including a CI
87
+ gate, a table of contents, exit codes, and a privacy/telemetry section; the documented default
88
+ output folder is corrected to `<capture>.pf-report/` (#41).
89
+ - CONTRIBUTING gains how-to-start, pull-request and other-ways-to-help sections; a pull request
90
+ template follows the AGENTS.md contract (#41).
91
+
92
+ ### Fixed — project
93
+
94
+ - Mermaid: `04-diagrams.md` emitted an invalid RTP gantt for captures without RTP (28 of 179 generated
95
+ diagrams failed to render), and the README's sequence diagram broke on a `;` (#43).
96
+ - CI could never pass (non-existent tshark preference, fixture drift check on randomly captured TLS
97
+ fixtures); it now runs the full suite (#12).
98
+
99
+ ## [0.1.0] — 2026-09-29
100
+
101
+ First working release. `report.json` `schema_version` is `1.0.0`.
102
+
103
+ ### Added — parsing
104
+
105
+ - `TsharkRunner`: 10 pinned tshark passes, `-T fields` with a documented
106
+ separator/aggregator, content-addressed disk cache keyed by capture SHA-256,
107
+ tshark version and pass arguments.
108
+ - Field and display-filter drift handling: fields validated against
109
+ `tshark -G fields`, protocol tokens against `tshark -G protocols`, preferences
110
+ against `tshark -G defaultprefs`; every drop recorded as a report note.
111
+ - `CaptureIndex`: flows, hosts, TLS/DTLS sessions, HTTP exchanges, DNS queries,
112
+ SIP messages with SDP, RTP streams, SSH sessions, QUIC sessions, and other
113
+ recognised services.
114
+ - SDP-driven RTP decode-as, so media on dynamic ports is dissected at all.
115
+ - Certificate enrichment through `openssl x509`, with an explicit
116
+ `name_source` field and a tshark-flattened fallback.
117
+ - Cipher registry of 424 suites derived from tshark's own value table, with
118
+ weakness tags and forward-secrecy reasoning derived from the IANA name.
119
+
120
+ ### Added — detectors
121
+
122
+ - `d1.tls_cipher`: deprecated versions, weak/prohibited suites, offered-but-unused
123
+ weak suites, missing forward secrecy, certificate expiry/key size/signature,
124
+ self-signed leaf, alerts, truncated handshakes, JA3 fleet clustering.
125
+ - `d2.transport_exposure`: cleartext HTTP auth, Basic auth inside TLS, cookies
126
+ without `Secure`, cleartext services, leaked credentials, services on odd
127
+ ports, unanswered-SYN scan shape, beaconing shape.
128
+ - `d3.sip_rtp`: SIP call graph, cleartext signalling with auth headers, SDP
129
+ without `a=crypto`, unprotected RTP, media volume anomalies.
130
+ - `d4.dns_quic_ssh`: plaintext DNS leakage, DNS tunnelling heuristics, external
131
+ resolvers, QUIC version inventory and payload opacity, weak SSH negotiation.
132
+
133
+ ### Added — output
134
+
135
+ - Six artifacts per run: `index.md`, `01-flows.md`, `02-ciphers.md`,
136
+ `03-findings.md`, `04-diagrams.md`, `report.json`.
137
+ - Mermaid topology, crypto matrix, severity pie, per-detector bar chart, RTP
138
+ timeline and SIP sequence diagrams, all total functions.
139
+ - Stable finding ids (`detector.code.sha256(scope)[:16]`) so reports diff.
140
+ - `pf` CLI: `analyze`, `flows`, `ciphers`, `detectors`, `suites`, `doctor`,
141
+ `schema`, with `--fail-on` for CI gates.
142
+
143
+ ### Added — safety
144
+
145
+ - Credential redaction at the model boundary, with tests that grep the artifacts
146
+ for the original material.
147
+ - One detector raising an exception cannot sink a run; it is reported as a note.
148
+ - Unknown cipher ids, unparseable certificates and missing tshark fields all
149
+ produce findings or notes, never silent passes.
150
+
151
+ ### Added — engineering
152
+
153
+ - 85 tests across four layers: registry, tshark boundary, detectors with
154
+ synthetic fixtures, and regression assertions over the Wireshark sample
155
+ corpus checked by hand against `tshark -V`.
156
+ - Fixtures generated deterministically; TLS fixtures captured from genuine
157
+ OpenSSL handshakes through a logging proxy.
158
+ - `ruff`, `mypy --strict` and `pytest` are the CI gate (`make verify`).
159
+ - `AGENTS.md` and `docs/subagent-playbook.md`: the subagent isolation contract
160
+ and a 54-item seed issue board (`docs/issue-board.md`).
161
+
162
+ ### Known limits
163
+
164
+ - Encrypted payloads (QUIC, TLS 1.3 application data) are opaque without a keylog.
165
+ - Certificate subject/issuer require openssl; without it the report says so.
166
+ - DNS tunnelling and beaconing are heuristics, reported at low confidence.
167
+ - Server/client orientation uses port heuristics; odd ports are flagged instead
168
+ of trusted.