Repository navigation
Add CVE enrichment for WiFi and BLE scans (ADR-0010) - #1
Merged
Merged
Conversation
… fixes
CVE enrichment (ADR-0010, sferic-sticks3):
- oui.py: MAC OUI -> vendor via macvendors.com, cached per-OUI, skips
locally-administered/randomized addresses (no IEEE vendor to find).
- cve.py: vendor -> CVEs via CIRCL's cve-search API. Vendor slugs are
probed (candidate slugs derived from the vendor name, first
non-empty /browse/<slug> wins) rather than hand-mapped, so it
self-corrects as cve-search's own vendor list changes. Product
matching is token-based against the device's own SSID/name, with
cve-search's generic slug suffixes ("_firmware") stripped first.
- enrichment.py: wires both into Collector.dispatch for wifi.scan and
ble.scan, mutating each ap/device entry with a `security` block
before the strike is stored. Any lookup failure degrades to no
enrichment -- never fails the underlying strike.
Fixes found in review of the existing emit two-step (ADR-0008 §1):
- emit_dispatch had no disabled-tool check, unlike dispatch/
stream_dispatch -- a disabled emit tool (ir.tvbgone, ir.replay,
ble.hid, usb.hid) could still be armed and fired. Factored the
shared station/capability/disabled gate into Collector._gate() so
all three dispatch paths use the same check.
- emit_dispatch didn't verify a fire call's confirm_token was actually
armed for the tool the call claims -- harmless on the device itself
(it fires whatever was armed, not what the caller names), but the
collector would store the resulting strike under the wrong tool.
Collector now tracks (station -> armed tool, token) and rejects a
fire whose tool doesn't match with a new `tool_mismatch` error.
- SerialStation.run()'s per-call `ms` budget was unbounded (unlike the
streaming tools' clamped duration_ms) and unvalidated, so a huge or
non-numeric value could hold the station's single request lock
indefinitely or raise an unhandled ValueError. Clamped to 120000 and
now raises a typed StationToolError("bad_params", ...) on bad input.
- store.flashes() did one COUNT(*) query per flash row; replaced with
a single GROUP BY join.
Adds httpx as a new dependency.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FzVTmtSkMcpzidmwtye9JJ
ruff format --check failed on cve.py's two hand-wrapped set literals (_PRODUCT_NOISE_WORDS, _SUFFIX_WORDS) -- reproduced locally and applied `ruff format`, no behavior change. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzVTmtSkMcpzidmwtye9JJ
main independently landed an equivalent fix for the same emit_dispatch gaps this branch fixed (missing disabled-check, unscoped confirm_token) -- a different session apparently reviewed the same code concurrently. Reconciled to main's (station, tool)-keyed _armed dict and its uniform `bad_token` error contract (dropping this branch's separate `tool_mismatch` code, since main's keying already makes a wrong-tool fire attempt a no-op against the real armed entry rather than needing a distinct error path), while keeping this branch's `_gate()` helper -- which already covers the disabled-check main added inline -- and its CVE-enrichment wiring in dispatch(), untouched by main's changes. Test suite reconciled the same way: kept main's test names/coverage where they duplicated this branch's, kept this branch's one test main didn't have (disabling a tool after it's already armed, before firing), dropped the redundant duplicate, and fixed the surviving wrong-tool test's expected error code (tool_mismatch -> bad_token). 45 tests pass, ruff format/check clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzVTmtSkMcpzidmwtye9JJ
main landed Milestone 3 (WebSocket station transport, capability auto-routing with optional `station`, `sferic.recall` full-text search, read-only dashboard, fake-station dev harness) since this branch's last sync. Reconciled: - collector.py: rewrote _gate() to wrap main's new _resolve() (named or auto-routed station lookup, raising no_capable_station/ambiguous_station) instead of the old required-station lookup, keeping the disabled-check and CVE-enrichment call in dispatch() this branch added. All three dispatch paths (dispatch/stream_dispatch/emit_dispatch) now take an optional station like main's, gated through the one _gate() helper. - pyproject.toml: unioned the new dependencies (httpx for CVE/OUI lookups, starlette/uvicorn/websockets for the WS listener + fake station); dropped the now-redundant httpx entry from the dev group since it's a core dep. - README.md: merged both branches' additions to the Layout file listing. - uv.lock: regenerated with `uv lock` against the merged pyproject.toml rather than hand-resolving the conflict markers. 82 tests pass (44 from this branch + main's new WS/routing/recall/dashboard suites), ruff format/check clean. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FzVTmtSkMcpzidmwtye9JJ
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.
Implements automatic vendor and CVE enrichment for
wifi.scanandble.scanresults on ingest, providing security leads without blocking strike capture.Summary
This change adds a best-effort enrichment pipeline that attaches vendor information and CVE metadata to scanned access points and BLE devices. The enrichment uses live MAC-OUI lookups and CVE-search API queries, with graceful degradation if external services are unavailable.
Key Changes
New modules:
oui.py: MAC OUI → vendor name resolution via macvendors.com API with per-OUI caching and rate limiting (~1 req/s)cve.py: Vendor-level CVE lookup against cve-search API with intelligent slug candidate generation and product matching heuristicsenrichment.py: Orchestrates OUI + CVE lookups and mutates scan payloads with asecurityblock per itemCollector integration:
Collectornow accepts an optionalEnricherinstance and applies enrichment towifi.scanandble.scanpayloads before storage_gate()methodEmit dispatch improvements:
_armeddict to track pending emit tokens per station, binding tokens to the tool they were armed forStation parameter validation:
serial_station.pynow clamps oversizedmsbudgets to prevent unbounded request lock holdsmsparametersStore optimization:
store.pynow computes strike counts via SQL JOIN instead of separate queriesImplementation Details
Testing
Comprehensive test suite added (
test_enrichment.py) covering:https://claude.ai/code/session_01FzVTmtSkMcpzidmwtye9JJ