canopy

module
v0.0.0-...-e27f8b9 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 13, 2026 License: MIT

README

Parameter

Canopy

Subdomain discovery, validation and evidence in one run.


Canopy finds hostnames under a domain, resolves them, and records what it can prove about each one.

$ canopy map -d example.com

  example.com  ·  standard profile  ·  2m 14s

  412 found     289 resolved     64 reachable     37 wildcard (excluded)
  9 sources     1 skipped (censys: no credentials)

  4 hosts point at a CNAME target that does not resolve
    legacy.example.com    → legacy-assets.s3.amazonaws.com
    old-cdn.example.com   → d1x2y3z.cloudfront.net
    docs-v1.example.com   → anytool.github.io
    mail-eu.example.com   → eu.sendgrid.net

  run_01HXYZ  ·  canopy show run_01HXYZ

What a run does

  1. Collect — query every configured source for hostnames under the domain.
  2. Generate — derive candidate names from the naming patterns present in the collected results.
  3. Validate — resolve candidates against trusted resolvers and filter wildcards.
  4. Enrich — follow CNAME chains, read TLS names, record HTTP status.

Each hostname keeps the source, timestamp and evidence it came from. Hosts outside the configured scope are recorded but never resolved or requested. Output is one hostname per line unless you ask for JSON.

Canopy does not guess whether a host is an admin panel, a staging environment or a CDN origin. Those calls are unreliable from a hostname and belong to whoever reads the results. What Canopy reports is checkable: whether a name resolves, what it points at, whether that target exists, what the certificate says, what the server answered, and when it first appeared.

Install

go install github.com/anytool-sh/canopy/cmd/canopy@latest

Or grab a binary from the releases page, or run it in Docker:

docker run anytool/canopy -d example.com

Usage

canopy map -d example.com

Common flags

INPUT:
   -d, -domain string[]   domain to map
   -dL, -list string      file containing domains, one per line
   -l, -hosts string      file of known hosts to enrich instead of discovering

MODE:
   -profile string        passive | standard | deep (default "standard")
   -timeout duration      give up after this long (default 5m)
   -scope string          exact | subdomains (default "subdomains")

OUTPUT:
   -o, -output string     write hosts to a file
   -oD, -output-dir string   one run directory per domain
   -format string         text | json | ndjson (default "text")
   -fields string         comma-separated fields to include
   -silent                hostnames only, nothing else

Run canopy -h for the full list, or canopy schema if you want it as JSON.

Profiles

Profile Stages Touches the target
passive Collect only No
standard Collect, generate, validate, enrich DNS, TLS, HTTP
deep Standard, plus larger wordlists, recursive pivots and all credentialed sources DNS, TLS, HTTP
canopy map -d example.com -profile deep -timeout 20m

Examples

Pipe into other tools. Default output is one hostname per line:

canopy map -d example.com -silent | httpx -json
canopy map -d example.com -silent | nuclei -t takeovers/

Map a list of domains:

canopy map -dL roots.txt -oD ./runs

Filter a finished run:

canopy show run_01HXYZ --state reachable
canopy show run_01HXYZ --dangling-cname
canopy show run_01HXYZ --cname-target '*.s3.amazonaws.com'
canopy show run_01HXYZ --status 401,403 --limit 20 --format ndjson

Show the evidence behind a hostname:

$ canopy show run_01HXYZ --host legacy.example.com --evidence

  legacy.example.com

  crt.sh        2024-03-11   certificate SAN
  argosdns      2025-08-02   historical A record
  wayback       2023-06-19   archived link
  dns           2026-08-12   NXDOMAIN
  dns           2026-08-12   CNAME legacy-assets.s3.amazonaws.com (NXDOMAIN)

Compare two runs:

canopy diff run_01HXYZ run_01J4AB --only added

Export a run:

canopy export run_01HXYZ --format hosts -o subs.txt
canopy export run_01HXYZ --format json -o surface.json

Running stages individually

canopy map runs the full pipeline. Each stage is also its own command and reads from stdin:

# collect only, no requests to the target
canopy collect -d example.com --only ct,argosdns

# generate candidates from names you already have
cat known.txt | canopy generate --pattern '{{sub}}-{{word}}.{{root}}'

# resolve a list someone else gave you, with wildcard filtering
cat candidates.txt | canopy validate -d example.com

# add CNAME chains, TLS names and HTTP status to a host list
canopy enrich -l hosts.txt --http

Stages accept input from other tools:

subfinder -d example.com -silent | canopy validate -d example.com | canopy enrich --http

Sources

Sources that need no credentials — certificate transparency, web archives, public passive DNS, URL indexes — are enabled by default. The rest activate when a key is present.

$ canopy doctor

  ID              ACTIVITY   AUTH    STATUS
  argosdns        passive    key     needs ARGOSDNS_API_KEY
  certspotter     passive    none    ok
  crtname         passive    none    ok
  crtsh           passive    none    ok
  hackertarget    passive    none    ok
  otx             passive    none    ok
  rapiddns        passive    none    ok
  subdomaincenter passive    none    ok
  urlscan         passive    none    ok
  wayback         passive    none    ok

Credentialed built-in sources read their named environment variables:

$ export ARGOSDNS_API_KEY="..."
$ canopy doctor

No key is required. Canopy uses the sources that are configured, reports the ones it skipped, and does not fail a run when a provider is unavailable.

Adding sources

Three ways to add a source without modifying Canopy.

A YAML file in ~/.config/canopy/sources/:

id: acme
activity: passive
auth:
  type: bearer
  secret: ACME_API_KEY
request:
  url: https://api.acme.io/v1/subdomains?q={{root}}
extract:
  hostnames: $.results[*].name
paginate:
  cursor: $.next
limits:
  rps: 5
$ canopy sources
  acme          passive    key         ok

An executable named canopy-source-<name> on PATH, in any language. It answers two calls:

$ canopy-source-internal describe
{"id":"internal","activity":"passive","capabilities":["hostnames"]}

$ canopy-source-internal discover --root example.com
{"hostname":"vpn.example.com","evidence":"internal-cmdb","observed_at":"2026-08-12T18:02:11Z"}
{"hostname":"hr.example.com","evidence":"internal-cmdb","observed_at":"2026-08-12T18:02:11Z"}

A wrapper around an existing binary, whose output Canopy normalizes into observations.

The same three options apply to candidate generators, validators and enrichers.

Every source declares itself passive or active, and the runtime enforces it. A source declared passive that sends DNS or HTTP to a discovered host is terminated and reported.

Output

Each run writes a directory:

.canopy/runs/run_01HXYZ/
├── summary.json          counts, timing, sources used and skipped
├── assets.ndjson         one record per host
├── observations.ndjson   every raw claim with its source
└── hosts.txt             one hostname per line

Plain files, readable with jq, grep, diff and git.

Running under an agent

Canopy is meant to be driven by automation as well as by hand, so output stays bounded:

  • canopy map prints a short summary and writes the rest to disk
  • canopy show returns filtered slices rather than the whole run
  • every command takes --limit and --fields, and marks truncated results
  • errors state the cause and the command to run next
  • canopy schema describes every command as JSON, so nothing parses --help
canopy map -d example.com --format json
canopy show run_01HXYZ --dangling-cname --limit 10 --format json

Scope

Canopy will not probe anything you haven't authorized. Set a scope file and it becomes a hard boundary for the whole run:

export CANOPY_SCOPE_FILE=./authorized.txt
canopy map -d example.com

Hosts outside that boundary are still recorded with their evidence, but nothing resolves them, connects to them or requests them.

Contributing

Sources and pattern sets are the most useful contributions, and most of them need no Go. Issues and pull requests are welcome.

License

Canopy is distributed under the MIT License.

Directories

Path Synopsis
cmd
canopy command
Command canopy discovers hostnames under a domain, validates them and records the evidence.
Command canopy discovers hostnames under a domain, validates them and records the evidence.
internal
cli
Package cli is the command-line surface.
Package cli is the command-line surface.
core
Package core defines the records a run produces.
Package core defines the records a run produces.
netgate
Package netgate issues the only HTTP clients and DNS resolvers a module may use.
Package netgate issues the only HTTP clients and DNS resolvers a module may use.
pipeline
Package pipeline runs the stages of a map and writes the run directory.
Package pipeline runs the stages of a map and writes the run directory.
report
Package report formats a finished run for a person.
Package report formats a finished run for a person.
scope
Package scope parses the authorization policy and answers the only question that matters before any packet is sent: may this host be contacted?
Package scope parses the authorization policy and answers the only question that matters before any packet is sent: may this host be contacted?
source
Package source defines what a collector is and holds the registry of them.
Package source defines what a collector is and holds the registry of them.
source/all
Package all registers the built-in sources.
Package all registers the built-in sources.
source/argosdns
Package argosdns collects passive DNS hostnames from ArgosDNS.
Package argosdns collects passive DNS hostnames from ArgosDNS.
source/certspotter
Package certspotter collects hostnames from SSLMate's Cert Spotter API, a second view of the certificate transparency logs.
Package certspotter collects hostnames from SSLMate's Cert Spotter API, a second view of the certificate transparency logs.
source/crtname
Package crtname collects hostnames from crt.name, an index of subdomains built from the certificate transparency firehose plus historical backfills and active probing.
Package crtname collects hostnames from crt.name, an index of subdomains built from the certificate transparency firehose plus historical backfills and active probing.
source/crtsh
Package crtsh collects hostnames from crt.sh, a search interface over the public certificate transparency logs.
Package crtsh collects hostnames from crt.sh, a search interface over the public certificate transparency logs.
source/fixture
Package fixture provides sources that touch no network, so the pipeline and the run directory can be exercised end to end before a real provider exists.
Package fixture provides sources that touch no network, so the pipeline and the run directory can be exercised end to end before a real provider exists.
source/hackertarget
Package hackertarget collects hostnames from HackerTarget's hostsearch API, a passive DNS view built from its own scanning.
Package hackertarget collects hostnames from HackerTarget's hostsearch API, a passive DNS view built from its own scanning.
source/otx
Package otx collects hostnames from AlienVault OTX passive DNS.
Package otx collects hostnames from AlienVault OTX passive DNS.
source/rapiddns
Package rapiddns collects hostnames from rapiddns.io, a searchable archive of DNS records.
Package rapiddns collects hostnames from rapiddns.io, a searchable archive of DNS records.
source/sourcetest
Package sourcetest is the conformance suite every collector must pass.
Package sourcetest is the conformance suite every collector must pass.
source/subdomaincenter
Package subdomaincenter collects hostnames from subdomain.center, an aggregator that answers with a plain list of names.
Package subdomaincenter collects hostnames from subdomain.center, an aggregator that answers with a plain list of names.
source/urlscan
Package urlscan collects hostnames from urlscan.io's search over public scans.
Package urlscan collects hostnames from urlscan.io's search over public scans.
source/wayback
Package wayback collects hostnames from the Internet Archive's CDX index.
Package wayback collects hostnames from the Internet Archive's CDX index.
store
Package store reads and writes run directories.
Package store reads and writes run directories.
pkg
schema
Package schema is Canopy's published contract: the commands, their flags, the exit codes, and the version of the run-directory record shapes.
Package schema is Canopy's published contract: the commands, their flags, the exit codes, and the version of the run-directory record shapes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL