Help us shape the future of Go! Take the 2026 Go Developer Survey

asm

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 2026 License: MIT Imports: 29 Imported by: 0

Documentation

Overview

Package asm implements the attack surface discovery pipeline.

A scan runs a sequence of stages against a root domain:

subdomains (subfinder) -> dns (dnsx) -> ipdr (ipinfo.io) -> ports (naabu) -> certs (tlsx)

ipdr, ports and certs work from the hosts the DNS stage resolved. ports is opt-in (Options.PortScan).

Index

Constants

This section is empty.

Variables

View Source
var ErrInvalidDomain = errors.New("invalid domain")

ErrInvalidDomain is returned for input that is not a registrable hostname.

Functions

func NormalizeDomain

func NormalizeDomain(input string) (string, error)

NormalizeDomain accepts a bare domain or a URL and returns the lowercased hostname, e.g. "https://Example.com/path" -> "example.com".

Types

type CertChange

type CertChange struct {
	Before TLSCert `json:"before"`
	After  TLSCert `json:"after"`
}

CertChange is an endpoint (host:port) that presented a different certificate in the newer scan.

type CertDiff

type CertDiff struct {
	SetDiff[TLSCert]
	Changed []CertChange `json:"changed"`
}

CertDiff also reports endpoints whose certificate was replaced.

type DNSRecord

type DNSRecord struct {
	Host  string `json:"host"`
	Type  string `json:"type"`
	Value string `json:"value"`
}

DNSRecord is a single resolved record for a host.

type Diff

type Diff struct {
	Subdomains SubdomainDiff      `json:"subdomains"`
	DNSRecords SetDiff[DNSRecord] `json:"dns_records"`
	TLSCerts   CertDiff           `json:"tls_certs"`
	IPInfo     IPInfoDiff         `json:"ip_info"`
}

Diff describes what changed between two scans of the same domain.

func Compare

func Compare(prev, cur *Result) Diff

Compare returns the changes from prev to cur. Subdomains are compared by host, DNS records by (host, type, value), certificates by endpoint (a changed fingerprint counts as a replaced certificate) and ipinfo data by IP, field by field.

func (Diff) Empty

func (d Diff) Empty() bool

Empty reports whether nothing changed.

type FieldChange

type FieldChange struct {
	Field  string `json:"field"`
	Before string `json:"before"`
	After  string `json:"after"`
}

FieldChange is one flattened ipinfo field ("asn.name") that changed. An empty Before or After means the field was absent on that side.

type IPInfo

type IPInfo struct {
	IP    string          `json:"ip"`
	Data  json.RawMessage `json:"data"`
	Error string          `json:"error,omitempty"`
}

IPInfo is the ipinfo.io record for one IP address. Data is the API response exactly as returned (fields vary by plan); Error is set instead when the lookup failed.

type IPInfoChange

type IPInfoChange struct {
	IP     string        `json:"ip"`
	Fields []FieldChange `json:"fields"`
}

IPInfoChange is an IP present in both scans with different ipinfo data.

type IPInfoDiff

type IPInfoDiff struct {
	// Compared is false when the previous scan predates IP lookups, so there
	// is no baseline and nothing else in the diff is meaningful.
	Compared bool `json:"compared"`
	SetDiff[IPInfo]
	Changed []IPInfoChange `json:"changed"`
}

IPInfoDiff lists IPs that appeared or disappeared and IPs whose ipinfo data changed (new owner, ASN, location...).

type OpenPort

type OpenPort struct {
	Port     int    `json:"port"`
	Protocol string `json:"protocol"`
}

OpenPort is a single open port found on an IP.

type Options

type Options struct {
	// SubfinderProviderConfig is the path to a subfinder provider-config.yaml
	// holding API keys for passive sources. Optional.
	SubfinderProviderConfig string
	// SubfinderMaxTime caps passive enumeration time.
	SubfinderMaxTime time.Duration
	// Resolvers overrides the DNS resolvers used by dnsx ("udp:1.1.1.1:53").
	// When empty, dnsx's public resolvers are used, falling back to the
	// system resolver if they are unreachable.
	Resolvers []string
	// DNSConcurrency is the number of hosts resolved in parallel.
	DNSConcurrency int
	// TLSConcurrency is the number of hosts probed for TLS in parallel.
	TLSConcurrency int
	// TLSPorts are the ports probed for certificates.
	TLSPorts []int
	// TLSTimeout is the per-connection timeout.
	TLSTimeout time.Duration
	// IPInfoToken authenticates ipinfo.io lookups. Optional: without it the
	// free tier's lower rate limits apply.
	IPInfoToken string
	// IPInfoBaseURL defaults to https://ipinfo.io.
	IPInfoBaseURL string
	// IPInfoConcurrency is the number of ipinfo lookups in flight.
	IPInfoConcurrency int
	// HTTPClient is used for ipinfo lookups.
	HTTPClient *http.Client
	// PortScan enables the naabu port-scanning stage. A full SYN scan of all
	// TCP ports is heavy and needs raw-socket privileges, so it is opt-in.
	PortScan bool
	// SkipIPDR disables the IPDR stage (IP data records via ipinfo.io).
	SkipIPDR bool
	// SkipCerts disables the certificate stage (TLS inspection via tlsx).
	SkipCerts bool
	// PortScanType selects the naabu scan mode: "syn" (half-open, needs raw
	// sockets, libpcap and a routable interface) or "connect" (full TCP
	// connect, works through NAT such as a Docker bridge / WSL2). Defaults to
	// "syn".
	PortScanType string
	// PortScanPorts is the naabu port spec; "-" means all 65535 TCP ports.
	PortScanPorts string
	// PortScanRate is packets per second.
	PortScanRate int
	// PortScanRetries is the per-port retry count.
	PortScanRetries int
	// PortScanThreads is naabu's worker count.
	PortScanThreads int
	// PortScanTimeout is the per-port timeout.
	PortScanTimeout time.Duration
	// Logger receives stage diagnostics. Defaults to slog.Default().
	Logger *slog.Logger
}

Options tunes the scanner. Zero values fall back to defaults.

type PortScan

type PortScan struct {
	IP    string     `json:"ip"`
	Hosts []string   `json:"hosts"`
	Ports []OpenPort `json:"ports"`
}

PortScan is the set of open ports found on one IPv4 address, with the hosts that resolved to it.

type ProgressFunc

type ProgressFunc func(Stage)

ProgressFunc is called when the scan enters a new stage.

type Result

type Result struct {
	Domain     string      `json:"domain"`
	Subdomains []Subdomain `json:"subdomains"`
	DNSRecords []DNSRecord `json:"dns_records"`
	TLSCerts   []TLSCert   `json:"tls_certs"`
	IPInfo     []IPInfo    `json:"ip_info"`
	PortScans  []PortScan  `json:"port_scans"`
}

Result is everything discovered for a domain.

type Scanner

type Scanner struct {
	// contains filtered or unexported fields
}

Scanner runs the discovery pipeline. It is safe for concurrent use.

func New

func New(opts Options) *Scanner

New returns a Scanner configured with opts.

func (*Scanner) Run

func (s *Scanner) Run(ctx context.Context, target Target, progress ProgressFunc) (*Result, error)

Run executes every stage against the target and returns the combined result. progress may be nil.

type SetDiff

type SetDiff[T any] struct {
	Added   []T `json:"added"`
	Removed []T `json:"removed"`
}

SetDiff lists items present in only one of two scans.

type Stage

type Stage string

Stage identifies a step of the scan pipeline.

const (
	StageSubdomains Stage = "subdomains"
	StageDNS        Stage = "dns"
	StageIPDR       Stage = "ipdr"
	StagePorts      Stage = "ports"
	StageCerts      Stage = "certs"
)

type Subdomain

type Subdomain struct {
	Host    string   `json:"host"`
	Sources []string `json:"sources"`
	// CarriedOver is true when no source reported the host in this scan and
	// it was kept from an earlier one (subdomains are append-only).
	CarriedOver bool `json:"carried_over"`
}

Subdomain is a hostname discovered by passive enumeration.

type SubdomainDiff

type SubdomainDiff struct {
	SetDiff[Subdomain]
	NoLongerReported []Subdomain `json:"no_longer_reported"`
}

SubdomainDiff also reports hosts that sources stopped returning. Since subdomains are append-only these stay in the scan as carried over, so they don't show up as Removed.

type TLSCert

type TLSCert struct {
	Host              string    `json:"host"`
	IP                string    `json:"ip"`
	Port              int       `json:"port"`
	TLSVersion        string    `json:"tls_version"`
	Cipher            string    `json:"cipher"`
	SubjectCN         string    `json:"subject_cn"`
	SubjectOrg        []string  `json:"subject_org"`
	SANs              []string  `json:"sans"`
	IssuerCN          string    `json:"issuer_cn"`
	IssuerOrg         []string  `json:"issuer_org"`
	Serial            string    `json:"serial"`
	NotBefore         time.Time `json:"not_before"`
	NotAfter          time.Time `json:"not_after"`
	Expired           bool      `json:"expired"`
	SelfSigned        bool      `json:"self_signed"`
	Mismatched        bool      `json:"mismatched"`
	Wildcard          bool      `json:"wildcard"`
	FingerprintSHA256 string    `json:"fingerprint_sha256"`
}

TLSCert is the leaf certificate presented by a host.

type Target

type Target struct {
	Domain string
	// KnownSubdomains are subdomains found by earlier scans of Domain. They
	// are kept in the result even if passive sources no longer report them,
	// and still go through DNS and TLS so their current state is captured.
	KnownSubdomains []Subdomain
}

Target is what a scan runs against.

Jump to

Keyboard shortcuts

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