Passive scanner architectureΒΆ
The architecture behind the π Passive scanner spike @GitHub: its components, how data moves through them, and who is responsible for what. The aim is a spike that can grow without being rebuilt, while the Desk β Forge β Scanner workflow stays intact.
PrinciplesΒΆ
The fingerprint logic is immutable design input.
fingerprint.yamldefines the probes and the match logic, and that layer does not change when a provider is swapped underneath it.Providers are replaceable services. External datasets (Netlas, Censys, and the rest) are reached through one defined interface.
The evaluator is deterministic. Match logic applies to observations and produces no side effects.
The controller orchestrates rather than decides.
scan.pysequences the flow; it does not implement probe logic.
Directory structureΒΆ
The top-level layout maps straight onto the subsystems:
passive-scanner/
βββ scan.py # CLI & orchestration
βββ fingerprint/
β βββ loader.py # fingerprint.yaml β internal model
β βββ model.py # Probe, MatchLogic, Fingerprint classes
βββ providers/
β βββ base.py # Abstract search provider interface
β βββ netlas.py # Netlas implementation
β βββ censys.py # Optional Censys implementation
βββ engine/
β βββ planner.py # Probe β provider query translation
β βββ evaluator.py # Apply match_logic
β βββ evidence.py # Normalize and store evidence
βββ io/
β βββ targets.py # Load IPs / netblocks
β βββ output.py # JSONL results writer
βββ FINDINGS.md # Spike results & limitations
Component responsibilitiesΒΆ
Fingerprint layerΒΆ
loader.py: parsesfingerprint.yamlinto internal data structures.model.py: defines theProbe,Fingerprint, andMatchResultobjects.The job: a tool-agnostic representation of probes and match logic.
ProvidersΒΆ
base.py: the abstract class definingsearch_probe(probe, targets).netlas.py: the Netlas-specific queries.censys.py: an optional fallback provider.The job: return observations per probe, and leave the match logic well alone.
PlannerΒΆ
planner.py: maps probes to provider query syntax.For example:
http.headers.server:"DeviceOS/2.1.4"The job: keep the API-specific logic in one place.
Evaluation engineΒΆ
evaluator.py: appliesmatch_logicto grouped observations.evidence.py: structures the evidence per IP for JSONL output.The job: deterministic, testable logic evaluation.
CLI / controllerΒΆ
scan.py: loads fingerprints, reads targets, calls planner, provider, and evaluator in turn, and emits results.The job: glue, and no logic decisions of its own.
IOΒΆ
Not wired into the spike yet. It is for later.
targets.py: handles IP and netblock input.output.py: emits JSONL lines:<timestamp>, <ip>, <match_result>, <evidence_snippet>.
Data flowΒΆ
scan.pyloadsfingerprint.yamlandtargets.txt.planner.pyturns each probe into provider-specific queries.The provider runs the queries and returns raw observations.
evaluator.pygroups observations per IP, appliesmatch_logic, and produces aMatchResult.output.pywrites JSONL with the matched IPs and the evidence.
No packets are sent. This is passive, internet-facing scanning.
Spike success criteriaΒΆ
The CLI tool runs and consumes one
fingerprint.yaml.It queries Netlas for 100 test IPs and outputs structured results.
The findings are documented, API limits, data quality, and gaps included.
The end-to-end flow is verified, with no assumptions made about full scale or active scanning.
Scaling considerationsΒΆ
More providers: Netlas β Censys β local datasets, eventually.
Caching and pagination: wrap the providers.
Parallelisation: a later enhancement; the controller stays as it is.
Bulk fingerprints: the evaluator is untouched, the planner handles the translation.
Active probes: a separate provider module, with no effect on the fingerprint model.
Non-goals, for the current spikeΒΆ
GUI
Concurrent fingerprint evaluation
Active probing
Optimisation for speed
Full error handling
This is the backbone for the passive-scanner spike: testable, modular, and ready to grow a piece at a time. Last updated: 01 June 2026