Passive OSINT enrichment¶
Use ZettelForge's OSINT executor to enrich seed entities — domains, IP addresses, ASNs, and IP blocks — with infrastructure intelligence gathered from passive sources. The executor runs registered collectors, validates each result against the ontology, canonicalizes entity values, and writes nodes and edges into the knowledge graph.
Prerequisites¶
- ZettelForge installed and initialized.
- For DNS, WHOIS, and certificate transparency collectors, install the OSINT extra:
pip install "zettelforge[osint]"
The [osint] extra adds dnspython, python-whois, and ipwhois. The base package already includes httpx, so the BGP collector works without it.
Supported seed types¶
The executor accepts five seed types: DomainName, IPv4Address, IPv6Address, ASNumber, and Netblock. Seed type must match exactly — the executor raises ValueError for any other value.
Collectors registered per seed type (Phase 1 / 1.5):
| Seed type | Collectors run |
|---|---|
DomainName |
dns_collector, whois_collector, cert_collector, builtwith_collector, wappalyzer_collector |
IPv4Address |
whois_collector, port_scanner (active-gated; see Safety controls) |
IPv6Address |
whois_collector, port_scanner (active-gated) |
ASNumber |
bgp_collector |
Netblock |
None registered yet — valid seed type but no collectors return results today |
Run a passive enrichment¶
from zettelforge.osint import run_osint_collection
result = run_osint_collection("DomainName", "Example.COM.")
print(result.canonical_input_value) # example.com
print(result.collectors_run) # ['dns_collector', 'cert_collector', ...]
print(result.persisted_count)
print(result.error_count)
Input values are canonicalized before any KG writes: "Example.COM." becomes "example.com", "AS15169" becomes "15169". Alternate spellings of the same entity collapse onto the same KG node.
The function also accepts a collect_osint() alias, which is identical — some integrations prefer the verb-first naming.
Result shape¶
run_osint_collection returns an OSINTCollectionResult dataclass:
| Field | Type | Notes |
|---|---|---|
input_entity_type |
str |
Seed type you passed |
input_value |
str |
Seed value you passed |
canonical_input_value |
str |
Canonicalized form |
seed_node_id |
str \| None |
KG node ID for the seed (None when persist=False) |
collectors_run |
list[str] |
Names of collectors that ran |
tuples_collected |
int |
Total tuples emitted across all collectors |
persisted_count |
int |
Tuples actually written to the KG |
error_count |
int |
Collector or tuple errors (non-fatal) |
errors |
list[OSINTExecutionError] |
Per-collector error detail |
started_at |
str |
ISO-8601 timestamp |
finished_at |
str \| None |
ISO-8601 timestamp |
A failing collector does not abort the run. Its error is captured in result.errors and execution continues with the remaining collectors.
Dry-run without KG writes¶
Pass persist=False to run collectors and validate output without writing to the knowledge graph. Useful for testing new seeds or inspecting what a run will produce:
result = run_osint_collection("ASNumber", "AS15169", persist=False)
# result.seed_node_id is None
# result.persisted_count is 0
# result.error_count shows any validation failures
Run only specific collectors¶
Use collector_names to restrict which collectors run for a given seed:
result = run_osint_collection(
"DomainName",
"example.com",
collector_names=["dns_collector", "cert_collector"],
)
Any name not in the registry is silently skipped — no error is raised for an unknown collector name.
Safety controls¶
Active port scanning is off by default. Without an explicit opt-in, the port scanner collector returns an empty result and sends no packets:
export ZETTELFORGE_OSINT_ACTIVE_SCAN=1
Set this variable only when you own the target network or have written authorization to scan it. Accepted values: 1, true, TRUE, yes. Anything else (including an empty string) keeps scanning off.
Even with the flag set, the port scanner requires python-nmap and the nmap binary. If either is missing, the collector returns an empty result and logs a warning — it never raises.
What gets persisted¶
The executor writes canonical entity values to the knowledge graph. Duplicate spellings collapse onto the same node. For example, AS15169 and 15169 resolve to the same canonical ASN node (15169), and Example.COM. and example.com write to the same domain node.
Each persisted tuple also writes a KG edge with an osint: true property and a source property set to the collector name, so you can later query which collector produced a given edge.
Phase 2-5 collectors (in progress)
Breach, social, and people enrichment collectors (HIBP, HaveIBeenPwned, Holehe, Hunter.io, and others) are registered in the transform registry but use seed types such as EmailAddress, Alias, and Hashtag that are not yet in SUPPORTED_SEED_TYPES. Passing those types raises ValueError today. Support for those seed types is in progress.