IP Enrichment
IP enrichment turns public IP addresses into useful context. Instead of reading
52.x.x.x in an alert, an analyst can see that the peer is AWS in a given
region, a Tor exit node, a country, or an autonomous system such as Cloudflare
or an ISP.
Enrichment is used in two places:
- In the GUI: IP badges appear in Sessions, Cartography, alerts and drawers.
- In NFQL: enrichment tables let you filter and join traffic by provider, threat feed, country or network owner.
Private and local addresses are not enriched. Your RFC1918, loopback, link-local and ULA addresses remain local cartography, not cloud or threat matches.
Sources
Every source describes itself in the GUI: hover its name (or tab to it) on the Cloud Attribution, Threat Intelligence, GeoIP or ASN page and a card explains what the feed tells you, who publishes it, how far to trust it and how many false positives to expect. The table below is the same information at a glance.
| Source | What it tells you | Good for |
|---|---|---|
| AWS, Azure, Google Cloud, Oracle Cloud, Cloudflare | Cloud provider, and often service or region. | Understanding cloud egress, public services and unexpected cloud peers. |
| FireHOL Level 1 | Known malicious or highly suspicious networks. | Fast triage when an alert touches a bad reputation range. |
| Tor exit nodes | Public exit points from the Tor network. | Inbound traffic that hides its origin. |
| Tor relays | Broader Tor relay and bridge list. | Internal hosts reaching into Tor infrastructure. |
| Emerging Threats — Compromised IPs | Hosts known to be compromised. | Broad, keyless reputation coverage. |
| CINS Army | Reputation from real sensor networks, low false positives. | Trustworthy triage signal. |
| IPsum | Aggregate of ~30 feeds, graded 1–8 by how many sources agree. | Tune your own false-positive threshold (ipsum_level, default 3). |
| FireHOL Proxies / Anonymous | Open proxies and anonymising infrastructure. | Spotting traffic that hides behind proxies. |
| VPN providers (X4BNet) | Commercial VPN exit ranges. | Flagging VPN egress/ingress. |
| Feodo Tracker | Active botnet C2 servers (Emotet, QakBot…). Best free signal-to-noise. | High-confidence C2 detection. Needs a free abuse.ch key. |
| ThreatFox | Community IOCs, tagged with the malware family. | Recent malware families. Needs a free abuse.ch key. |
| GeoIP | Country code for a public IP. | Quick visual context and coarse residency checks. |
| ASN | The network owner, such as a cloud, ISP, hoster or enterprise. | Identifying peers that are not covered by curated cloud lists. |
Google Cloud vs Google Services
Google publishes more than one useful list. Google Cloud is for GCP compute and regions. Google services covers broader Google-owned space, including addresses such as Google Public DNS. Enable both if you want both kinds of context; overlap is handled normally.
Add your own threat-intelligence source
On Connectors → Threat Intelligence, select Add source. A custom source can be either:
- a file upload, published immediately after validation; or
- an HTTP/HTTPS URL, refreshed every 15 minutes to 30 days (24 hours by default). Select Refresh to fetch it immediately.
The accepted formats cover the common IP-feed conventions:
| Format | Accepted shape |
|---|---|
| Auto / Plain | One IPv4, IPv6 or CIDR per line; blank lines and # comments are ignored. Bare addresses become /32 or /128. Gzip is detected automatically. |
| CSV / TSV | RFC-style records, optionally with a header. Set the indicator and detail columns when auto-detection cannot identify them. |
| JSON | An array of strings or objects; choose the indicator/detail fields for generic objects. |
| NDJSON | One JSON value or object per line. |
| STIX 2.x | IPv4/IPv6 address objects and simple equality or IN (…) indicator patterns. |
| MISP | IP attributes such as ip-src, ip-dst and the IP half of `domain |
Every accepted value is canonicalised and exact duplicates are removed. The result reports accepted, invalid and duplicate records. A malformed, empty, oversized or over-limit replacement is rejected before publication, so the last-known-good ranges keep matching. Limits are 64 MiB compressed input, 256 MiB decoded input, one million accepted entries and 32 custom sources.
Custom URL feeds support Basic, Bearer and custom X-* header authentication
through the REST API, CLI or YAML bundle. Secrets are sealed with the master key
at rest and are never returned by the list API. URL resolution blocks loopback,
link-local and private networks by default, follows at most five redirects and
does not forward credentials to another host. Private destinations require the
explicit allow_private_networks opt-in; use it only for a feed you control.
For uploaded sources, Replace file validates and atomically swaps the snapshot. Delete removes an operator-owned source and its ranges; built-in sources cannot be deleted.
YAML configuration bundle
Custom definitions travel under enrichment.custom_sources; downloaded or
uploaded payload bytes do not:
enrichment:
enabled: true
custom_sources:
- name: corp_c2
display_name: Corporate C2 blocklist
enabled: true
transport: url
url: https://intel.example.test/c2.csv
format: csv
refresh_interval: 24h
parser:
indicator_field: ip
detail_field: malware
auth:
type: bearer
secret: enc:v1:…
Import is idempotent: an existing custom definition is updated and its current last-known-good snapshot is retained. A URL source downloads again normally. An uploaded source restored on another instance starts disabled and raises a warning to upload the named file again; full backups contain its range snapshot.
Defaults And Controls
Enrichment is enabled by default on a fresh install so the first investigation already has context.
The Connectors section contains four enrichment pages:
| Page | Controls |
|---|---|
| Cloud Attribution | AWS, Azure, Google Cloud, Google services, Oracle Cloud, Cloudflare. |
| Threat Intelligence | FireHOL Level 1/Proxies/Anonymous, Tor exit nodes and relays, Emerging Threats, CINS Army, IPsum, VPN providers, and the abuse.ch feeds (Feodo, ThreatFox). |
| GeoIP | Country-level tagging. |
| ASN | Network-owner tagging. |
There are two levels of control:
- Global IP enrichment turns every enrichment source on or off.
- Per-source toggles let you disable one source without affecting the rest.
Use the global switch for air-gapped or no-outbound deployments. Use per-source toggles when a source is noisy or not useful for your environment.
What “off” means. Turning a source off stops it matching immediately and releases its ranges from memory — no new traffic is tagged with it, on the cartography, in investigations or in detection context. The global switch does the same for every source at once. Both survive a restart: nothing quietly comes back the next time the daemon starts.
The ranges already downloaded stay on disk, so turning a source back on reloads them straight away, with no download — which also means it works offline. A large feed can take a moment to reload; the row shows a Loading… pill while that happens, and starts matching again as soon as it clears.
Each source also has Refresh now. Most sources refresh automatically within about an hour; GeoIP and ASN refresh less often because they change slowly. Manual refresh is useful when you know a new feed has just been published. It is unavailable while the source, or global enrichment, is off — refreshing something you switched off would put traffic on the wire and start using the feed again.
Purge data
Purge data, on each source row, deletes the ranges that source has downloaded and frees the disk they use. The source itself stays listed and can be refreshed whenever you want — purging data is not removing the feed.
You do not need it to stop a source from matching: the toggle does that instantly and keeps the download. Purge is for reclaiming space, or for discarding a snapshot you no longer trust.
Two things it does not do:
- It does not remove the enrichment already recorded on past traffic. Those annotations are stored by time rather than by source, and age out with your retention settings.
- It is refused while that source is fetching, because the fetch would put the data straight back. Wait for the spinner to clear and try again.
The same action is available from the command line:
obserae-cli enrichment purge firehol_level1
abuse.ch API key
The Feodo Tracker and ThreatFox feeds are provided by abuse.ch and now require a free account Auth-Key. They are enabled by default like the other feeds, but stay idle (no error) until a key is saved — each row shows a Not configured badge and greys its Refresh and toggle. On the Threat Intelligence page:
- Create a free abuse.ch account and copy your Auth-Key.
- On either row, click Edit API key, paste your Auth-Key and Save. The key is shared by both feeds — configuring it once covers them all (the badge flips to Configured on every abuse.ch row). It is stored encrypted (sealed with the master key) and never shown in full again — the modal only reveals a short masked hint of the stored key.
- The badges flip to Configured, the controls become active, and the feeds refresh automatically.
Use Clear in the modal to remove the key; both feeds go back to idle.
IPsum level
IPsum aggregates about 30 upstream lists and grades every IP from 1 to 8 by how
many sources agree. The enrichment.ipsum_level setting (default 3) picks
which grade to import: raise it to shed more false positives, lower it for wider
coverage.
What You See In The GUI
Enrichment appears as small badges and details next to public IPs:
10.0.0.10 (host:webserver) -> 52.10.x.x [AWS / us-west-2 / EC2]
10.0.0.11 (host:db) -> 185.x.x.x [threat / firehol_level1]
203.0.113.44 -> 10.0.0.20 [Tor exit]
In practice, this helps with everyday triage:
- A database server talking to an unexpected country stands out immediately.
- A login attempt through a Tor exit is easier to qualify.
- A host reaching a new cloud provider can become a “first seen” alert.
- ASN context tells you whether an IP belongs to a cloud, residential ISP or hosting provider.
Badges are context, not verdicts. A Tor or FireHOL tag deserves attention; it does not automatically prove compromise.
Querying Enrichment
Use enrichment_ips for traffic already seen by obserae. It contains the exact
public IPs that matched an enabled source.
# Sessions whose server side is on a threat feed
FROM enrichment_ips | WHERE nature == "threat" | KEEP ip, source
> FROM sessions
| LAST 3600
| JOIN ip == server_ip
| KEEP server_ip, prev_source, client_ip, client_to_server_bytes
# Top cloud providers reached in the last hour
FROM enrichment_ips | WHERE nature == "cloud" | KEEP ip, source
> FROM sessions
| LAST 3600
| JOIN ip == server_ip
| STATS bytes = SUM(client_to_server_bytes) BY prev_source
| SORT bytes DESC
Use enrichment_ranges when you want to inspect the source ranges themselves or
test an arbitrary CIDR relationship.
# Sessions whose server is in an AWS range
FROM enrichment_ranges | WHERE source == "aws" | KEEP cidr, details
> FROM sessions
| LAST 3600
| JOIN server_ip WITHIN cidr
| KEEP server_ip, prev_details, client_to_server_bytes
enrichment_ips is usually the best starting point for investigation queries.
enrichment_ranges is better for source review and range-level questions.
Looking one address up, without NFQL
An automation platform should not have to learn a query language to ask “what
do you know about this address?”. GET /api/enrichment/lookup answers it
directly, for up to 50 addresses in one call:
curl -sG https://obserae.corp.example/api/enrichment/lookup \
-H "Authorization: Bearer obs_…" \
--data-urlencode "ip=203.0.113.45"
It requires sessions:read — reading what the feeds say is a read. Configuring
the feeds is a different act and keeps sources:manage, so a playbook token
cannot switch a source off.
Unlike enrichment_ips, which lists what obserae has already seen in traffic,
the lookup consults the loaded catalogues directly: an address that never
appeared in a flow still gets an answer.
Reading the answer. Three shapes of threat_intel mean three different
things, and a playbook must not collapse them:
threat_intel | Meaning |
|---|---|
[{"source": "tor", "verdict": "match", …}] | That feed lists the address. updated_at is when the feed last refreshed — a listing you should weigh by its age. |
[] | Checked against every enabled feed; none lists it. |
| absent | Not checked. The address is not publicly routable, so no feed could apply. |
[{"verdict": "unavailable"}] | The enrichment database could not be consulted. Not “clean” — nothing was checked. |
The same distinction applies to the public fields: asn, as_org, country
and cloud are simply absent when no source matched, rather than present and
empty. An absent field is never a value.
Accuracy And Limits
Cloud attribution tells you which published provider range matched. It may not identify the exact application behind a CDN, proxy or shared cloud service.
Threat-intel feeds are high-signal context, not proof. A match says the peer is listed by that source; your response still depends on direction, asset, service, history and business context.
GeoIP is country-level context. It is useful for fast scanning and rough policy questions, but it is not forensic proof of a server’s physical location, especially for cloud and CDN ranges.
ASN tells you who operates the network. It does not identify the cloud product, tenant, application or region.
Enrichment is applied as the flow is ingested, so it depends on what was loaded at that moment. The catalogues live in memory and are rebuilt at every restart; obserae now does that rebuild in the background so it can start collecting flows straight away, rather than dropping everything the exporters send while it loads. The trade-off is that flows arriving in the first seconds after a restart may carry fewer labels than the same flow would a minute later. The Cockpit’s Enrichment feeds gauge shows when the rebuild is still running. A flow is never re-enriched retroactively, so if you restart during an incident, keep that short window in mind when reading the very first sessions.
Enrichment Inside An Alert
When an alert fires, obserae embeds what it knows about the addresses involved directly in the payload it sends — so a SOAR playbook or an on-call analyst reads the ASN, the country, the cloud provider and any threat-feed listing in the alert itself, without calling obserae back. See Alert outputs for the full shape.
Two details of that block matter when you act on it.
updated_at is the age of the answer. Every threat-intel entry carries
the moment that source last refreshed:
"threat_intel": [
{ "source": "firehol_level1", "verdict": "match",
"updated_at": "2026-06-04T05:00:00Z" }
]
A listing is only as current as the feed behind it. A Tor exit-node match
from a list refreshed two hours ago is actionable; the same match from a
feed that has not refreshed in three weeks says the address was listed
three weeks ago, which is a different claim. Reputation feeds churn — an
address de-listed yesterday still looks listed to a stale copy. Put
updated_at in the escalation branch of your playbook, and in the ticket:
“listed by firehol_level1, feed refreshed 2 h ago” is a sentence an analyst
can act on, “listed by firehol_level1” is not.
A source that has never fetched carries no updated_at at all, rather
than a made-up date.
An empty list is an answer; a missing one is not. "threat_intel": []
means the feeds were consulted and none lists the address. A single entry
with "verdict": "unavailable" means obserae could not consult them —
the enrichment database was unreachable at that moment. Never treat the
second as a clean result.
The block is captured when the rule fires and frozen with the alert, so a retried or resent delivery carries the reading from that moment, not a fresh one. If a feed refreshes between the fire and the retry, the alert still reports what was true when it fired.
Privacy
obserae fetches public provider/feed lists. It does not send your traffic, your local IPs, your cartography or your alerts to enrichment providers.
If the machine must make no outbound HTTPS requests, disable global IP enrichment. The product still works; IPs simply appear without external context.
See Also
- Connectors - where enrichment sources are managed.
- NFQL - enrichment tables and query syntax.
- Alerting - turning enrichment queries into alerts.
- Cartography - where enrichment badges appear on hosts.