Adds a domain_to_dns enricher that resolves a domain's A (IPv4) and AAAA (IPv6) records using ProjectDiscovery's dnsx, as requested in #79. Each resolved IP becomes an Ip node linked to the source domain with a RESOLVES_TO relationship.
The existing domain_to_ip enricher uses Python's socket.gethostbyname, which returns a single IPv4 address and no IPv6. This new enricher leverages the dnsx toolkit (the tool the issue explicitly asks for) to return the full set of A and AAAA records, reusing the existing Ip type and the RESOLVES_TO graph convention so it slots cleanly alongside the current enrichers.
Changes
tools/network/dnsx.py — adds resolve_domain() which runs dnsx -d <domain> -a [-aaaa] -json -silent and parses the JSONL output into a de-duplicated IP list. The a/aaaa field names follow retryabledns.DNSData (the struct dnsx serializes). The existing CIDR launch() path is untouched. Parsing is split into a pure, testable _parse_resolved_ips() static method that skips blank/malformed lines so one bad record never breaks the whole resolution.
flowsint_enrichers/domain/to_dns.py — new DomainToDnsEnricher, auto-registered via @flowsint_enricher. Optional ipv6 toggle (default on) and optional PDCP_API_KEY vault secret. Follows the established tool-backed enricher pattern (e.g. ip_to_ports).
tests/enrichers/test_domain_to_dns.py — unit tests for registry wiring, metadata, JSONL parsing (A/AAAA, dedup, blank/malformed lines, empty output), and scan() with the Docker tool mocked.
Testing
uv run --package flowsint-enrichers pytest flowsint-enrichers/tests/enrichers/ -q
11 passed
The new tests mock the Docker tool, so the suite needs no Docker daemon. I was not able to execute dnsx itself in my environment (it's a network/Docker tool), so the JSON-parsing is written defensively against the documented retryabledns.DNSData field names and degrades to an empty list rather than erroring on unexpected output. Happy to adjust field handling if maintainers spot a version difference.
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.
## 📋 Pull Request Information
**Original PR:** https://github.com/reconurge/flowsint/pull/182
**Author:** [@rachit367](https://github.com/rachit367)
**Created:** 6/9/2026
**Status:** ✅ Merged
**Merged:** 6/20/2026
**Merged by:** [@dextmorgn](https://github.com/dextmorgn)
**Base:** `main` ← **Head:** `feat/domain-to-dns-enricher`
---
### 📝 Commits (1)
- [`b4e5102`](https://github.com/reconurge/flowsint/commit/b4e5102592e161b44ab61ba04b49878b6c7bda78) feat(enrichers): add domain_to_dns enricher using dnsx
### 📊 Changes
**3 files changed** (+336 additions, -1 deletions)
<details>
<summary>View changed files</summary>
➕ `flowsint-enrichers/src/flowsint_enrichers/domain/to_dns.py` (+135 -0)
📝 `flowsint-enrichers/src/tools/network/dnsx.py` (+70 -1)
➕ `flowsint-enrichers/tests/enrichers/test_domain_to_dns.py` (+131 -0)
</details>
### 📄 Description
## What
Adds a `domain_to_dns` enricher that resolves a domain's **A (IPv4)** and **AAAA (IPv6)** records using ProjectDiscovery's [`dnsx`](https://github.com/projectdiscovery/dnsx), as requested in #79. Each resolved IP becomes an `Ip` node linked to the source domain with a `RESOLVES_TO` relationship.
Closes #79
## Why
The existing `domain_to_ip` enricher uses Python's `socket.gethostbyname`, which returns a **single IPv4** address and no IPv6. This new enricher leverages the `dnsx` toolkit (the tool the issue explicitly asks for) to return the full set of **A and AAAA** records, reusing the existing `Ip` type and the `RESOLVES_TO` graph convention so it slots cleanly alongside the current enrichers.
## Changes
- **`tools/network/dnsx.py`** — adds `resolve_domain()` which runs `dnsx -d <domain> -a [-aaaa] -json -silent` and parses the JSONL output into a de-duplicated IP list. The `a`/`aaaa` field names follow `retryabledns.DNSData` (the struct `dnsx` serializes). The existing CIDR `launch()` path is **untouched**. Parsing is split into a pure, testable `_parse_resolved_ips()` static method that skips blank/malformed lines so one bad record never breaks the whole resolution.
- **`flowsint_enrichers/domain/to_dns.py`** — new `DomainToDnsEnricher`, auto-registered via `@flowsint_enricher`. Optional `ipv6` toggle (default on) and optional `PDCP_API_KEY` vault secret. Follows the established tool-backed enricher pattern (e.g. `ip_to_ports`).
- **`tests/enrichers/test_domain_to_dns.py`** — unit tests for registry wiring, metadata, JSONL parsing (A/AAAA, dedup, blank/malformed lines, empty output), and `scan()` with the Docker tool mocked.
## Testing
```
uv run --package flowsint-enrichers pytest flowsint-enrichers/tests/enrichers/ -q
11 passed
```
The new tests mock the Docker tool, so the suite needs **no Docker daemon**. I was not able to execute `dnsx` itself in my environment (it's a network/Docker tool), so the JSON-parsing is written defensively against the documented `retryabledns.DNSData` field names and degrades to an empty list rather than erroring on unexpected output. Happy to adjust field handling if maintainers spot a version difference.
---
<sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
📋 Pull Request Information
Original PR: https://github.com/reconurge/flowsint/pull/182
Author: @rachit367
Created: 6/9/2026
Status: ✅ Merged
Merged: 6/20/2026
Merged by: @dextmorgn
Base:
main← Head:feat/domain-to-dns-enricher📝 Commits (1)
b4e5102feat(enrichers): add domain_to_dns enricher using dnsx📊 Changes
3 files changed (+336 additions, -1 deletions)
View changed files
➕
flowsint-enrichers/src/flowsint_enrichers/domain/to_dns.py(+135 -0)📝
flowsint-enrichers/src/tools/network/dnsx.py(+70 -1)➕
flowsint-enrichers/tests/enrichers/test_domain_to_dns.py(+131 -0)📄 Description
What
Adds a
domain_to_dnsenricher that resolves a domain's A (IPv4) and AAAA (IPv6) records using ProjectDiscovery'sdnsx, as requested in #79. Each resolved IP becomes anIpnode linked to the source domain with aRESOLVES_TOrelationship.Closes #79
Why
The existing
domain_to_ipenricher uses Python'ssocket.gethostbyname, which returns a single IPv4 address and no IPv6. This new enricher leverages thednsxtoolkit (the tool the issue explicitly asks for) to return the full set of A and AAAA records, reusing the existingIptype and theRESOLVES_TOgraph convention so it slots cleanly alongside the current enrichers.Changes
tools/network/dnsx.py— addsresolve_domain()which runsdnsx -d <domain> -a [-aaaa] -json -silentand parses the JSONL output into a de-duplicated IP list. Thea/aaaafield names followretryabledns.DNSData(the structdnsxserializes). The existing CIDRlaunch()path is untouched. Parsing is split into a pure, testable_parse_resolved_ips()static method that skips blank/malformed lines so one bad record never breaks the whole resolution.flowsint_enrichers/domain/to_dns.py— newDomainToDnsEnricher, auto-registered via@flowsint_enricher. Optionalipv6toggle (default on) and optionalPDCP_API_KEYvault secret. Follows the established tool-backed enricher pattern (e.g.ip_to_ports).tests/enrichers/test_domain_to_dns.py— unit tests for registry wiring, metadata, JSONL parsing (A/AAAA, dedup, blank/malformed lines, empty output), andscan()with the Docker tool mocked.Testing
The new tests mock the Docker tool, so the suite needs no Docker daemon. I was not able to execute
dnsxitself in my environment (it's a network/Docker tool), so the JSON-parsing is written defensively against the documentedretryabledns.DNSDatafield names and degrades to an empty list rather than erroring on unexpected output. Happy to adjust field handling if maintainers spot a version difference.🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.