Plugin System¶
Amass employs an event-driven plugin architecture where plugins register handlers for specific asset types. The system coordinates multiple discovery plugins through a central registry that builds processing pipelines.
Plugin Architecture¶
Handler Registration¶
Plugins register handlers with the Registry, which builds processing pipelines based on:
| Property | Description |
|---|---|
| Priority | Execution order (1-9, lower runs first) |
| EventType | Which OAM asset types trigger the handler |
| Transforms | Asset type conversions produced |
| Callback | Function executed when events match |
| MaxInstances | Concurrent handler limit |
type Handler struct {
Name string
Priority int // 1-9
EventType oam.AssetType // FQDN, IPAddress, etc.
Transforms []oam.AssetType
Callback func(*Event) error
MaxInstances int
}
Plugin Categories¶
DNS Plugins¶
Core DNS-based discovery handlers:
| Plugin | Priority | Function |
|---|---|---|
dns_txt |
1 | Extract data from TXT records |
dns_cname |
2 | Follow CNAME chains |
dns_ip |
3 | Resolve A/AAAA records |
dns_subdomain |
4 | Subdomain enumeration |
dns_reverse |
5 | Reverse DNS lookups |
dns_nsec |
6 | NSEC/NSEC3 walking |
API Integration Plugins¶
External service integrations:
| Plugin | Source | Data Provided |
|---|---|---|
api_shodan |
Shodan | Open ports, services, banners |
api_censys |
Censys | Certificate and host data |
api_virustotal |
VirusTotal | Passive DNS records |
api_securitytrails |
SecurityTrails | Historical DNS data |
api_gleif |
GLEIF | Legal entity identifiers |
api_aviato |
Aviato | Company intelligence |
api_bgptools |
BGP.Tools | ASN and netblock data |
Service Discovery Plugins¶
Infrastructure and service detection:
| Plugin | Function |
|---|---|
http_probe_fqdn |
Probe HTTP services on domains |
http_probe_ip |
Probe HTTP services on IPs |
cert_analysis |
TLS certificate extraction |
service_fingerprint |
Service identification |
WHOIS/Network Plugins¶
Network intelligence gathering:
| Plugin | Function |
|---|---|
whois_domain |
Domain registration data |
whois_ip |
IP allocation records |
rdap_lookup |
RDAP protocol queries |
reverse_whois |
Find domains by registrant |
Plugin Lifecycle¶
stateDiagram-v2
[*] --> Instantiated: Factory Function
Instantiated --> Configured: Load Settings
Configured --> Registered: Register Handlers
Registered --> Active: Session Started
Active --> Processing: Event Received
Processing --> Active: Event Handled
Active --> Stopped: Session Ended
Stopped --> [*]
Initialization Phase¶
- Plugin instantiation via factory functions
- Configuration with API credentials and settings
- Handler registration with the registry
- Priority assignment for execution ordering
Execution Phase¶
Plugins operate within an event-driven model:
- Assets flow through discovery pipelines
- Each plugin's handler processes relevant asset types
- Handlers generate new assets or enriched data
- System feeds new assets back into pipelines
Asset Flow Through Plugins¶
Example: FQDN Discovery Flow¶
Input: example.com (FQDN)
│
├─► DNS TXT Handler (Priority 1)
│ └─► Discovers SPF, DKIM records
│
├─► DNS CNAME Handler (Priority 2)
│ └─► Follows CNAME to cdn.example.com
│
├─► DNS A Handler (Priority 3)
│ └─► Resolves to 192.0.2.1
│
├─► HTTP Probe (Priority 4)
│ └─► Detects nginx/1.18.0
│
└─► Certificate Analysis (Priority 5)
└─► Extracts SAN: *.example.com
Output: Enriched FQDN + IP + Certificate + Relations
API Plugin Pattern¶
External API integrations follow a consistent pattern:
sequenceDiagram
participant Plugin
participant Support
participant RateLimiter
participant API
participant Database
Plugin->>Support: GetAPI() credentials
Support-->>Plugin: API key/secret
Plugin->>RateLimiter: Check rate limit
RateLimiter-->>Plugin: Proceed
Plugin->>API: HTTP Request
API-->>Plugin: JSON Response
Plugin->>Plugin: Parse response
Plugin->>Database: Create OAM assets
Plugin->>Database: Establish relations
Implementation Steps¶
- Credential Management: Retrieve API keys via
support.GetAPI() - Rate Limiting: Enforce per-service QPS limits
- HTTP Communication: Submit requests to external services
- Response Parsing: Extract relevant asset data
- Asset Creation: Generate OAM entities with relationships
Session Context¶
Plugins operate within isolated session contexts providing:
| Resource | Purpose |
|---|---|
| Configuration | Session-specific settings |
| Asset Cache | Deduplication of discovered assets |
| Graph Database | Persistent storage access |
| Scope Validator | Target filtering rules |
| Rate Limiter | Per-resolver QPS control |
| Resolver Pool | DNS resolution infrastructure |
Writing Custom Plugins¶
Basic Plugin Structure¶
package myplugin
import (
"github.com/owasp-amass/amass/v5/engine"
"github.com/owasp-amass/oam"
)
type MyPlugin struct {
engine.BasePlugin
}
func NewMyPlugin() *MyPlugin {
return &MyPlugin{}
}
func (p *MyPlugin) Start(r *engine.Registry) error {
r.RegisterHandler(&engine.Handler{
Name: "my_handler",
Priority: 5,
EventType: oam.FQDN,
Callback: p.handleFQDN,
})
return nil
}
func (p *MyPlugin) handleFQDN(e *engine.Event) error {
fqdn := e.Asset.(oam.FQDN)
// Process the FQDN
// Create new assets
// Return results
return nil
}
Registering Multiple Handlers¶
func (p *MyPlugin) Start(r *engine.Registry) error {
handlers := []*engine.Handler{
{Name: "fqdn_handler", Priority: 1, EventType: oam.FQDN, Callback: p.handleFQDN},
{Name: "ip_handler", Priority: 2, EventType: oam.IPAddress, Callback: p.handleIP},
}
for _, h := range handlers {
r.RegisterHandler(h)
}
return nil
}
Priority Guidelines¶
| Priority Range | Plugin Type | Examples |
|---|---|---|
| 1-3 | Core DNS | TXT, CNAME, A records |
| 4-5 | Secondary DNS | Reverse, NSEC |
| 6-7 | Service Discovery | HTTP probes, certificates |
| 8-9 | Enrichment | API integrations, WHOIS |
Priority Best Practices
- Lower priorities run first and should handle foundational discovery
- Higher priorities should focus on enrichment and validation
- Avoid priority conflicts between related handlers
Technical Reference¶
Plugin Instantiation Structure¶
graph TB
PluginFunc["Plugin Constructor<br/>NewDNS(), NewAviato(), etc."]
PluginInstance["et.Plugin Instance<br/>&dnsPlugin{}"]
PluginFields["Plugin Fields<br/>• name: string<br/>• log: *slog.Logger<br/>• source: *et.Source<br/>• handler instances"]
Registry["et.Registry"]
StartMethod["Start(r et.Registry) error"]
PluginFunc -->|"returns"| PluginInstance
PluginInstance -->|"contains"| PluginFields
Registry -->|"passed to"| StartMethod
PluginInstance -->|"implements"| StartMethod
HandlerReg["r.RegisterHandler(&et.Handler{...})"]
StartMethod -->|"calls"| HandlerReg
Handler Registration Structure¶
graph LR
Handler["et.Handler struct"]
Plugin["Plugin: et.Plugin"]
Name["Name: string"]
Priority["Priority: int<br/>1-9 (lower=higher)"]
MaxInstances["MaxInstances: int<br/>Concurrency limit"]
Transforms["Transforms: []string<br/>Asset types produced"]
EventType["EventType: oam.AssetType<br/>Trigger asset type"]
Callback["Callback: func(*et.Event) error<br/>Processing function"]
Handler --> Plugin
Handler --> Name
Handler --> Priority
Handler --> MaxInstances
Handler --> Transforms
Handler --> EventType
Handler --> Callback
DNS Plugin Handler Details¶
The DNS plugin registers six handlers in its Start() method, each with a specific priority and callback:
| Handler Name | Priority | EventType | Transforms | Callback | Purpose |
|---|---|---|---|---|---|
| DNS-TXT | 1 | oam.FQDN |
["FQDN"] |
dnsTXT.check |
Extract organization IDs from TXT records |
| DNS-CNAME | 2 | oam.FQDN |
["FQDN"] |
dnsCNAME.check |
Resolve CNAME aliases |
| DNS-IP | 3 | oam.FQDN |
["IPAddress"] |
dnsIP.check |
Resolve A/AAAA records to IPs |
| DNS-Subdomains | 4 | oam.FQDN |
["FQDN"] |
dnsSubs.check |
Enumerate NS/MX/SRV records |
| DNS-Apex | 5 | oam.FQDN |
["FQDN"] |
dnsApex.check |
Build domain hierarchy |
| DNS-Reverse | 8 | oam.IPAddress |
["FQDN"] |
dnsReverse.check |
PTR lookups for IPs |
Priority Assignment Rationale¶
graph TD
P1["Priority 1: DNS-TXT<br/>Extract organization data<br/>from verification records"]
P2["Priority 2: DNS-CNAME<br/>Resolve aliases before<br/>IP resolution"]
P3["Priority 3: DNS-IP<br/>A/AAAA records to IPs<br/>Foundation for other lookups"]
P4["Priority 4: DNS-Subdomains<br/>NS/MX/SRV enumeration<br/>Expands FQDN scope"]
P5["Priority 5: DNS-Apex<br/>Build domain tree<br/>Establish hierarchy"]
P6["Priority 6-7: API Enrichment<br/>GLEIF, Company searches"]
P8["Priority 8: DNS-Reverse<br/>PTR lookups<br/>Requires IP resolution"]
P9["Priority 9: Service Discovery<br/>Active probing<br/>HTTP/TLS/JARM"]
P1 --> P2
P2 --> P3
P3 --> P4
P4 --> P5
P5 --> P6
P6 --> P8
P8 --> P9
style P1 fill:#f9f9f9
style P2 fill:#f9f9f9
style P3 fill:#f9f9f9
style P9 fill:#f9f9f9
Plugin Loading and Initialization¶
sequenceDiagram
participant Load as LoadAndStartPlugins()
participant Funcs as pluginNewFuncs[]
participant Plugin as et.Plugin
participant Registry as et.Registry
participant Handler as Handler Callback
Load->>Funcs: Iterate constructor functions
Funcs->>Plugin: Call NewDNS(), NewAviato(), etc.
Plugin->>Plugin: Initialize fields<br/>(name, source, log)
Load->>Plugin: Call Start(registry)
Plugin->>Registry: RegisterHandler(&et.Handler{...})
Registry->>Registry: Add to pipeline<br/>by priority
Note over Plugin: Plugin started,<br/>handlers registered
Note over Registry,Handler: During execution...
Registry->>Handler: Invoke callback(event)
Handler->>Handler: Execute check() logic
The loading process iterates through the pluginNewFuncs array, instantiates each plugin, and calls Start(). If any plugin fails to start, all previously started plugins are stopped to ensure a clean state.
Plugin Constructor Pattern:
All plugins follow a consistent constructor pattern:
- Return a struct that implements
et.Plugin - Initialize the plugin's
namefield - Create an
et.Sourcestruct with name and confidence score - Initialize any plugin-specific fields (handlers, state)
Handler Callback Pattern¶
Handler callbacks follow a consistent four-phase pattern: check → lookup → query → store → process. This pattern ensures efficient TTL-based caching and clean separation of concerns.
graph TD
Entry["check(e *et.Event) error"]
ExtractAsset["Extract asset from e.Entity<br/>Type assertion to expected type"]
CheckTTL["TTLStartTime()<br/>Calculate TTL window"]
MonitoredCheck["AssetMonitoredWithinTTL()<br/>Check if recently queried"]
Lookup["lookup(e, since)<br/>Query cache for existing data"]
Query["query(e)<br/>Perform DNS/API query"]
Store["store(e, results)<br/>Save to cache with edges"]
MarkMonitored["MarkAssetMonitored()<br/>Record query timestamp"]
Process["process(e, results)<br/>Dispatch new events"]
Return["return nil or error"]
Entry --> ExtractAsset
ExtractAsset --> CheckTTL
CheckTTL --> MonitoredCheck
MonitoredCheck -->|"Within TTL"| Lookup
MonitoredCheck -->|"Expired/Never"| Query
Query --> Store
Store --> MarkMonitored
Lookup --> Process
MarkMonitored --> Process
Process --> Return
Phase Breakdown:
- Check: Entry point, validates event and calculates TTL window
- Lookup: Retrieves cached data if available within TTL
- Query: Performs actual DNS/API queries if cache miss or TTL expired
- Store: Persists results to session cache with appropriate edges/properties
- Process: Dispatches new events to trigger cascading discovery
Example: DNS TXT Handler Implementation