Scan Profiles
A scan profile is an ordered list of plugin IDs that the worker executes when a scan starts. Profiles balance coverage, speed, and noise for different scan use cases.
Scan profiles are configuration-driven. The source of truth is backend/configs/config.yaml:
scan_profile_order:
- quick
- default
- deep
scan_profiles:
quick:
name: Quick
description: Fast low-noise discovery
plugins:
- subfinder
- dnsx
- httpx
After editing profile configuration, restart both API and worker so they load the same profile registry.
Runtime Behavior
When a scan is created, the API stores the selected profile ID with the scan record and queues that profile ID for the worker. The worker resolves the plugin execution order from the loaded profile registry.
Plugin order matters. Each plugin runs after the previous plugin and can add normalized assets to the running scan scope, which later plugins may consume.
If a future scan request uses an unknown profile ID, the API rejects it. If an old queued or scheduled scan references a profile that has been removed from config, the worker marks that scan failed with an unknown profile error instead of silently falling back to another profile.
Historical scans keep their stored profile ID and can still be displayed even if that ID is no longer configured.
Validation Rules
API and worker startup fail if scan profile configuration is invalid.
Validation rules:
scan_profilesmust not be empty.- Profile IDs must be non-empty and use lowercase letters, numbers,
_, or-. namemust be non-empty.pluginsmust contain at least one plugin.- Plugin names must be non-empty.
- Duplicate plugin names inside one profile are rejected.
- Every plugin referenced by a profile must be registered in the plugin registry.
- If
scan_profile_orderis set, it must include every configured profile exactly once.
Example startup error:
scan profile "deep" references unregistered plugin "foobar"
API
GET /api/v1/scan-profiles returns the configured profile definitions in scan_profile_order order:
[
{
"id": "quick",
"name": "Quick",
"description": "Fast low-noise discovery",
"plugins": ["subfinder", "dnsx", "httpx"]
}
]
The frontend should render the profiles returned by this endpoint instead of hardcoding profile options.
manual_scan is internal and is not returned by /scan-profiles. It is created automatically by POST /api/v1/assets/{asset_id}/run-plugin for one-off plugin execution against a selected asset.
Built-in Profiles
The default backend/configs/config.yaml ships with these profiles. Keep the IDs stable because scan records store profile IDs.
| ID | Name | Plugin order |
|---|---|---|
quick |
Quick | subfinder -> dnsx -> httpx |
default |
Default | subfinder -> dnsx -> httpx -> naabu -> nuclei |
deep |
Deep | subfinder -> amass -> dnsx -> naabu -> nmap -> httpx -> tlsx -> katana -> nuclei |
stealth |
Stealth | subfinder -> amass -> httpx |
recon_nmap |
Recon + Nmap | subfinder -> dnsx -> nmap |
tls_audit |
TLS Audit | subfinder -> dnsx -> httpx -> tlsx |
web_discovery |
Web Discovery | subfinder -> dnsx -> httpx -> katana -> nuclei |
port_discovery |
Port Discovery | subfinder -> dnsx -> naabu -> nmap |
vuln_scan |
Vulnerability Scan | httpx -> nuclei |
recon_expanded |
Recon Expanded | subfinder -> amass -> dnsx -> httpx -> tlsx |
screenshot |
Screenshot | subfinder -> dnsx -> httpx -> httpx_screenshot |
Pipeline Chaining
The worker merges successful plugin output into the scan scope. Later plugins can consume assets produced earlier in the same scan.
Before the first profile plugin runs, the worker normalizes configured scan targets. ip_range Scope values are expanded at runtime into individual ip scope items, deduplicated with explicit IP scope items and overlapping ranges. The persisted Scope remains one ip_range row, and range membership does not create IP Assets. CIDR scope items are not expanded by this mechanism.
| Produced asset type | Added to scope as |
|---|---|
domain |
domain |
subdomain |
subdomain |
ip |
ip |
webapp |
webapp |
service |
service |
Example chain:
domain
-> subfinder creates subdomains
-> dnsx resolves subdomains to IPs
-> naabu/nmap discover services
-> nmap_vulns optionally checks known services for NSE vulnerabilities
-> httpx creates WebApps
-> katana enriches WebApps with path/parameter metadata
-> nuclei creates vulnerabilities
Adding a Custom Profile
Add the profile to backend/configs/config.yaml:
scan_profile_order:
- quick
- default
- custom_tls
scan_profiles:
custom_tls:
name: Custom TLS Audit
description: Custom certificate inventory
plugins:
- subfinder
- dnsx
- httpx
- tlsx
Rules for custom profiles:
- Choose a stable ID. Renaming it will not rename historical scan records.
- List plugins in the exact order they should run.
- Only reference registered plugin IDs.
- Add the new ID to
scan_profile_orderif that setting is present. - Restart API and worker after editing config.
Database-backed Custom Profiles
Admins can create global custom scan profiles from Settings -> Scans. These profiles are stored in PostgreSQL and are merged with config-backed profiles at runtime.
Config-backed profiles are still defined in backend/configs/config.yaml; the UI does not rewrite that file. A custom profile ID cannot override a built-in config profile ID.
GET /api/v1/scan-profiles returns enabled config profiles plus enabled custom profiles. Admin-only endpoints under /api/v1/admin/scan-profiles can list, create, update, and delete custom profiles.
Custom profiles follow the same validation model as config profiles: stable lowercase ID, non-empty name, at least one plugin, registered enabled plugin IDs that support profile_scan only, no empty plugin IDs, and no duplicate plugins.
Adding nmap_vulns to a Custom Profile
nmap_vulns is registered for custom profiles but is not included in the built-in profiles. Add it after service discovery so it receives service assets instead of broad-scanning hosts.
Recommended custom profile order:
subfinder -> dnsx -> naabu -> nmap -> nmap_vulns
Admins can configure its NSE preset, timing, retry count, and host timeout from Settings -> Scans -> Plugins. These are global plugin settings for the MVP. Arbitrary NSE script names, script paths, and raw CLI arguments are not supported.
If nmap_vulns runs with only an IP/domain/subdomain and no known service assets in scope, it skips cleanly and records that reason in raw output.
Custom hxresearch Templates
nuclei_custom_templates is registered but not included in any built-in scan profile. It runs only templates from hxresearch/nuclei, mounted in worker containers at /opt/hxeasm/hxresearch/nuclei.
Example profile:
scan_profiles:
expert_assessment:
name: Expert Assessment
description: Run internal hxEASM custom detections
plugins:
- subfinder
- dnsx
- httpx
- nuclei_custom_templates
If scan_profile_order is configured, add expert_assessment there too. Restart API and worker after editing the config.
Manual Plugin Execution
Manual plugin execution uses the internal manual_scan profile. It is not configured in backend/configs/config.yaml, not shown in normal scan profile selection, and not available for scheduled scans.
A manual scan:
- belongs to the selected asset's organization
- stores
profile = manual_scan - stores scan metadata with
manual,asset_id,asset_type,asset_value, andplugin - creates exactly one queued scan job
- runs through the same Redis queue and worker processing path as normal scans
- uses only the selected asset as plugin input
Compatibility is defined by plugin registration metadata: SupportedExecutionModes must include manual_scan, and SupportedAssetTypes must include the selected asset type. Unsupported asset/plugin combinations are rejected before a scan is created.
The frontend obtains available manual plugins from GET /api/v1/plugins/manual-capabilities, so adding a new manually executable plugin requires backend registration metadata only. No config.yaml profile entry and no frontend allowlist change are required.
Manual targets are normalized before execution. Domains/subdomains are lowercased, WebApps are passed as webapp scope items, and service assets such as 1.2.3.4:443/tcp are passed to plugins as 1.2.3.4:443.
Removing or Disabling a Profile
To remove a profile from future scan creation, delete it from scan_profiles and remove it from scan_profile_order.
Existing scans are not deleted. Historical scan pages can still show the raw stored profile ID. Scheduled scans using a removed profile should be updated before they run again; otherwise the worker will fail them with an unknown profile error.
Adding a Plugin to a Profile
- Implement the wrapper in
backend/internal/plugins/wrappers/. - Register it in
backend/internal/plugins/wrappers/defaults.go. - Add tool installer configuration if the wrapper runs an external binary.
- Add the plugin ID to one or more
scan_profilesentries inbackend/configs/config.yaml. - Restart API and worker.
Profile validation requires every referenced plugin to be registered. Missing plugin IDs are startup errors, not runtime warnings.
Plugin Catalog
See Plugin Catalog for per-plugin input, output, graph, artifact, dependency, and profile usage details.