Skip to the content.

← All documentation · English | 繁體中文

Security scanning toolchain and reports

Item Value
Purpose Security reviews for government and hospital tenders need a report that can be submitted, not a verbal assurance

Why ZAP does not apply

OWASP ZAP is a dynamic scanner (DAST) for web applications — it crawls HTTP endpoints, injects payloads and inspects the responses. jt-snmpd has no HTTP interface; its attack surface is BER-encoded packets on UDP/161. ZAP has nothing to work with.

The right combination is three layers: static analysis of the source (SAST) + dependency vulnerability scanning (SCA) + protocol-level fuzzing (DAST, but the SNMP-specific kind).


1. SAST — static source analysis

Tool Purpose Why it is needed
Bandit Python security anti-patterns Catches eval, subprocess(shell=True), hardcoded passwords, weak hashes, unsafe tempfiles, assert used for validation
Semgrep Rule-based SAST (p/security-audit, p/secrets) Deeper data flow than Bandit, and custom rules can target this project’s specific hazards
Ruff (S rules) A very fast flake8-bandit port Fast enough to run on every commit; overlaps Bandit’s rules but far quicker
mypy Static type checking Type errors are especially dangerous at the ctypes boundary — the wrong type reads or writes the wrong memory
CodeQL GitHub’s data-flow analysis Traces “unauthenticated input → dangerous operation” across functions; the closest thing to a human review

Patterns specific to this project

These need custom Semgrep rules; the standard rule sets do not catch them:

# Every ctypes foreign function must declare argtypes/restype
# Without them a 64-bit return value is truncated -- measured: drive C: showing 0 GB
- id: ctypes-missing-argtypes
  pattern: $LIB.$FUNC(...)
  pattern-not-inside: |
      $LIB.$FUNC.argtypes = ...
      ...

# OCTET STRINGs must go through octet()
# A bare rfc1902.OctetString(str) raises PyAsn1UnicodeEncodeError on non-ASCII
- id: bare-octetstring
  pattern: rfc1902.OctetString($X)
  pattern-not: rfc1902.OctetString($X.encode(...))

# Internal timing must not use the wall clock
- id: wall-clock-timing
  patterns:
    - pattern-either:
        - pattern: datetime.now()
        - pattern: time.time()

2. SCA — dependency scanning and SBOM

Tool Purpose
pip-audit Python dependency CVEs against the PyPI Advisory DB and OSV
OSV-Scanner Google’s cross-ecosystem scanner; wider coverage than pip-audit
CycloneDX-python Produces the SBOM a security review will ask for
Trivy Also scans the filesystem and the SBOM, and checks for secrets in passing

jt-snmpd has very few dependencies (pysnmppyasn1, plus pywin32 and pyinstaller at packaging time). That is deliberate: fewer dependencies means a cleaner SCA report and an easier review.


3. Secret scanning

Tool Purpose
gitleaks Scans the whole git history, not only the working tree
detect-secrets Supports a baseline, which keeps false-positive fatigue down

A project rule: keys must not appear in clear text in the configuration, the logs, the Event Log or MSI properties. Secret scanning is the automated enforcement of that rule.


4. DAST — protocol-level fuzzing (what replaces ZAP here)

Two are mandatory:

Item Tool Pass condition
24-hour fuzzing boofuzz, sending malformed BER to UDP/161 Zero crashes, zero hangs, no RSS growth
PROTOS c06-snmpv1 The University of Oulu SNMP corpus As above

Added by this document:

Item Method
Pre-parse gate effectiveness Sources outside the allow-list must get zero responses; tests/test_preauth_gate.py has 27 adversarial cases
Unauthenticated packet flood CPU stays within budget, RSS does not grow, a legitimate manager stays inside its SLA
Response size Every response under 1400 bytes, with no IP fragmentation

5. Windows-specific checks

These are invisible to SAST tools and are certain to come up in a security audit:

Item Tool or method Why
Authenticode signature signtool verify /pa /v, Sysinternals sigcheck Until a certificate is in place, this verifies the result after you sign it yourself — see Code signing
Unquoted service path sc qc jt-snmpd, confirming binPath is quoted The single most commonly raised audit finding. The default install path contains a space
Service account and privileges sc qprivs jt-snmpd Confirms the privilege stripping took effect
File and directory ACLs Sysinternals accesschk -d C:\ProgramData lets Users create subdirectories by default, so an attacker can get there first
Weak-ACL escalation paths PowerUp / PrivescCheck Purpose-built for Windows service escalation paths; covers the three items above as well
DLL hijacking Confirm one-folder rather than one-file; watch load paths in Process Monitor one-file extracts to %TEMP% and runs from there
Defender / EDR false positives Install on a machine with Defender and HVCI enabled, and watch for quarantine PyInstaller output has a history of false positives
Memory integrity compatibility Enable HVCI, reboot, confirm the service still runs Customer endpoints commonly have it on

6. Suggested CI arrangement

Every commit (fast)
  ruff check --select S       # security rules
  mypy deploy/
  pytest tests/ -q

Every PR (medium)
  bandit -r deploy/ -f json -o reports/bandit.json
  semgrep --config p/security-audit --config p/secrets --json -o reports/semgrep.json
  pip-audit --format json -o reports/pip-audit.json
  gitleaks detect --report-format json --report-path reports/gitleaks.json
  cyclonedx-py env -o reports/sbom.json

Every release (slow; required before shipping)
  all of the above, plus
  boofuzz for 24 hours against UDP/161
  PROTOS c06-snmpv1
  Windows platform checks (signtool / accesschk / PrivescCheck)
  the installation test matrix
  30-day stability (major releases)

7. Report output

Every tool here can emit JSON or SARIF. Collapse them into one submittable summary:

reports/
├── sbom.json                 CycloneDX SBOM (dependency list; reviews require it)
├── bandit.json               SAST
├── semgrep.json              SAST, including the custom rules
├── pip-audit.json            Dependency CVEs
├── gitleaks.json             Secret scan
├── fuzzing-summary.txt       boofuzz 24-hour result
├── windows-checks.txt        Signature / ACLs / privileges / unquoted path
└── SECURITY-REPORT.md        A human-readable summary of the above, with a pass or fail verdict

The verdict rule: SAST High and Critical findings must be zero or carry a written exception; dependency CVEs at High or above must be zero; fuzzing must produce zero crashes. Anything short of that does not ship (TEST_PLAN.md §10, Release Gate).

8. Current state, and where the results are

Security scan results carries the current baseline with a verdict on every finding. In short: Bandit HIGH 0, pip-audit clean across 62 packages, and a runtime dependency surface of two packages.

Running on every push, in GitHub Actions:

Check Where
The full test suite tests.yml, Linux
Personal data and secret scan tests.yml, Linux
Executable and MSI build tests.yml, Windows
Installer artefact checks, read from the MSI’s own tables tests.yml, Windows

Run by hand, not yet in CI: Bandit and pip-audit. Not yet run at all: Semgrep, gitleaks, the fuzzing tiers, and the Windows platform batch. The results page lists those explicitly rather than leaving this document to imply they happened.