Skip to main content

Read the OWASP Amass asset database and pipe it into everything else

Project description

oamx

Read the OWASP Amass asset database and pipe it into everything else.

Zero dependencies. Read-only. Works across Amass v4 and v5.


The problem

This is the recon one-liner. Some version of it appears in every Amass tutorial, blog post and cheat sheet written in the last five years:

amass enum -passive -d example.com -o subs.txt
httpx -l subs.txt -silent | nuclei -severity high,critical

Since Amass v5 (August 2025), it writes an empty subs.txt.

v5 moved results into an asset database and stopped populating the text output. The scan works. The data is there. But -o produces nothing, httpx probes nothing, nuclei finds nothing, and the pipeline exits 0. It doesn't fail — it succeeds at doing nothing, quietly, on a schedule, possibly for months.

Amass is a producer with no consumer story. The documented way out is amass subs -names -d example.com, which gives you a flat list of hostnames and nothing else — no IPs, no netblocks, no ports, no provenance, no scoping, no "what changed since yesterday". Everyone else is pasting sqlite3 json_extract incantations out of GitHub issues.

oamx is the consumer.

oamx names -d example.com --resolved-only | httpx -silent | nuclei -severity high,critical

Install

pipx install oamx          # or: pip install oamx

Python 3.10+. No runtime dependencies — deliberately. A recon pipeline that breaks because of a transitive dependency resolution is worse than no tool.

There are two entry points and they are interchangeable:

oamx names -d example.com          # the console script
python -m oamx names -d example.com   # when PATH is not set up for you

The second is the one that keeps working inside a container, a pip install --target layout, or anywhere the scripts directory is not on PATH. Both are checked against the built wheel on every release.


Quickstart

# What is actually in my database?
oamx doctor

# Names, scoped to one target, that actually resolve
oamx names -d example.com --resolved-only

# Everything Amass found listening, ready for httpx or nuclei
oamx targets -d example.com --urls

# What genuinely appeared in the last day
oamx names -d example.com --new --since 24h

# The whole picture, with provenance, for storage or an agent to reason over
oamx json -d example.com > assets.jsonl

oamx doctor is the one to run first when a scan "found nothing":

database        /home/you/.config/amass/amass.sqlite
layout          v5 (entities/edges)
provenance      yes
assets          22

  FQDN                 8
  Service              3
  IPAddress            3
  Identifier           2
  URL                  1
  TLSCertificate       1
  SomeFutureAssetType  1
  Netblock             1
  ContactRecord        1
  AutonomousSystem     1

That is real output from the test fixture rather than an illustration, and everything below the database line is asserted against a live run — see tests/test_readme.py. SomeFutureAssetType is in the fixture deliberately: an asset type this release has never heard of still gets counted, and still comes out of oamx json with a usable value.


Commands

Command Output
names fully qualified domain names
ips IP addresses (--ipv4 / --ipv6)
cidrs netblocks in CIDR notation
asns autonomous system numbers
urls discovered URLs
certs TLS certificates
services responding network services
orgs organisations
emails email addresses
targets host:port pairs, or --urls for scheme://host:port
dns name<TAB>RRTYPE<TAB>target triples
json every matching asset as JSONL, with provenance
graph every matching relation as JSONL
stats counts by asset type
doctor what the database is and what is in it

Filters

Every command takes the same set:

Flag Effect
--db PATH database path (default: auto-discover the usual per-platform locations)
-d, --domain scope to a root domain; repeatable and comma-separated
--scope-depth N graph hops used to scope non-name assets (default 2, 0 disables)
--since DUR last seen within 24h, 7d, 2w
--new with --since, match first seen — genuinely new, not re-confirmed
--resolved-only only names with a DNS record
--source NAME only assets asserted by this Amass plugin
--exclude-source NAME drop assets whose only sources are these
--min-confidence N drop assets below this source confidence (0–100)
--json JSONL with provenance instead of bare values
--count just the number
--fail-empty exit 1 if nothing matched, for CI

Why it works the way it does

Four decisions worth knowing about, because they change what you get back.

It introspects the schema instead of pinning to a version. Amass has renamed its storage at least three times — v3 graph files, v4 assets/relations, v5 entities/edges, plus column churn inside v5 point releases. oamx reads whatever tables and columns it finds and maps them onto one logical model. Asset types that didn't exist when this was written still come out with a usable value rather than raising. The adapter absorbs the churn so your pipeline doesn't.

Scoping follows the graph, but names are matched by suffix. -d example.com seeds on hostname suffix, then walks out --scope-depth hops to pick up the IPs, netblocks and certificates attached to those names. But a hostname reached by graph proximity is never pulled into scope. Without that rule, one CNAME to a shared CDN drags the CDN's other customers into your target list. Use --scope-depth 1 to stay on directly-resolved addresses, or 0 for names only.

Rows that reduce to the same thing are merged. API.Example.COM. from a certificate SAN and api.example.com from a DNS answer are two database rows and one host. oamx merges them: provenance is unioned, the higher confidence wins, and the sighting window widens to cover every contributing row. Merging happens before time and source filters, so a host is never judged on only one of its sightings.

Rediscovery is not discovery. --new --since 24h reports hosts first seen in that window. A host you've known for a month, which a second data source just re-confirmed, is not new and won't alert. That false-positive class is precisely what trains people to ignore their monitoring.


Output schema

--json emits one object per line, versioned so downstream code can depend on it even as Amass changes underneath:

{
  "schema": "oamx/1",
  "kind": "asset",
  "id": 3,
  "type": "FQDN",
  "value": "api.example.com",
  "first_seen": "2026-06-24 12:00:00.000000000+00:00",
  "last_seen": "2026-07-24 10:00:00.000000000+00:00",
  "sources": [{"name": "crtsh", "confidence": 95}, {"name": "DNS-IP", "confidence": 100}],
  "attrs": {"name": "api.example.com"}
}

graph emits "kind": "edge" records with from, to, label, and decoded DNS record types (rr_type: 1 becomes rr_name: "A", because nobody wants to grep for 5).


As a library

from oamx.integrations import query, values

hosts = values("names", domains=["example.com"], resolved_only=True)
records = query("all", domains=["example.com"], since="7d")

query returns the same records oamx json emits, as dicts. values returns just the values. Both raise OamxError with a message written to be read.

Reading is all it does: the database is opened mode=ro, so nothing here can start a scan or send traffic. That is a useful property if you are handing it to an agent — but oamx ships no agent-framework adapter, deliberately. Build that on your side, where your own conventions for tool schemas and error handling live.

The package ships py.typed, so annotations survive into whatever imports it.


Known limits

Stated plainly, because a recon tool that overstates itself is worse than useless.

  • SQLite only. Amass also supports PostgreSQL and Neo4j. The reader is written behind an interface so those are clean additions, but shipping untested database drivers would defeat the point.
  • Field names are verified for FQDN, IPAddress, Service and Identifier against the Open Asset Model documentation and, for Identifier, the struct in owasp-amass/open-asset-model itself. The remaining ~17 asset types use a best-effort key list with a generic fallback. If one of them extracts the wrong field for you, that's a one-line fix in model.py and a very welcome issue — Identifier was exactly that, reporting the unique_id dedupe key where the value lives in id.
  • The whole graph is loaded into memory to make scoping and merging correct. Fine for target-scoped databases; if yours has millions of entities, this will want a streaming path.
  • Amass v3 graph files are not supported. Only the SQLite asset database.

Compatibility

Amass Layout Status
v5.x entities / edges tested
v4.x assets / relations tested
v3.x graph files not supported

Both layouts are covered by fixtures built from the documented schemas, not from guesses.


Development

python3 -m venv .venv
.venv/bin/pip install -e ".[dev]"
.venv/bin/pytest

122 tests in about a second, gated on branch coverage rather than line coverage — this codebase is mostly branching, and line coverage calls a half-tested if fully covered. ruff, mypy --strict and pylint run from the same dev extra, so what CI runs and what you run cannot drift; see CONTRIBUTING.md for the order.

The dev extra is not part of the zero-dependency promise, which is about what an installed oamx pulls in. CI proves that separately by installing the package alone and asserting nothing third-party came with it.

If you add behaviour, break it on purpose first and check something goes red. A test that has never been seen to fail has not been shown to test anything.

Upstream

The gap this fills is arguably an Amass issue, not a third-party one. If the project wants an amass export that emits structured, scoped, provenance-carrying output, the schema in oamx/model.py is a reasonable starting point and this tool should stop being necessary. That would be the good outcome.

Licensed Apache-2.0, matching the Amass ecosystem.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

oamx-0.1.0.tar.gz (54.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

oamx-0.1.0-py3-none-any.whl (30.1 kB view details)

Uploaded Python 3

File details

Details for the file oamx-0.1.0.tar.gz.

File metadata

  • Download URL: oamx-0.1.0.tar.gz
  • Upload date:
  • Size: 54.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for oamx-0.1.0.tar.gz
Algorithm Hash digest
SHA256 3687055ad975495be76043cd73299676301e2211450d8cd8b2f921d574c3eb60
MD5 df01fca556dc0d3ff36c29d5c949def4
BLAKE2b-256 e28303642a203c1bec8557ec89e8e6eb3928e47555d9696bf9c3209d76b23231

See more details on using hashes here.

Provenance

The following attestation bundles were made for oamx-0.1.0.tar.gz:

Publisher: release.yml on coolhandle01/oamx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oamx-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: oamx-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 30.1 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.14

File hashes

Hashes for oamx-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 41f629617eb5a549249b67b80e62d8da2ddba38f1d597052c3b633833e85cb23
MD5 a5e2affc37260f8116e1593660c5b7f3
BLAKE2b-256 937abf1f0e3ce68fd43b53700e6b969b9b4a088394a4480a9f4b11e624adb612

See more details on using hashes here.

Provenance

The following attestation bundles were made for oamx-0.1.0-py3-none-any.whl:

Publisher: release.yml on coolhandle01/oamx

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page