Assets
Assets are organization-scoped observed attack-surface records. Canonical Asset types are:
organizationdomainsubdomainipservicewebappcertificate
The organization Asset is system-managed and used as the Asset Graph root and as an organization-level Threat fallback. It is hidden from the normal operational inventory and dashboard counts.
Scan Scope remains a separate configuration/input model. Existing scope records and scope approval workflow are not part of the Asset lifecycle. Domain and IP may exist both as Scope records and as discovered/managed Assets.
Manual Asset Creation
Authorized users can add an asset directly from the All Assets page without waiting for scan discovery.
Allowed roles:
adminhackerwith access to the organization
Clients can view assets but cannot create them.
Manual asset creation endpoint:
POST /api/v1/organizations/{organization_id}/assets
Required fields:
{
"type": "domain",
"value": "example.com"
}
Optional fields:
{
"criticality": "high",
"approved": true,
"disabled": false,
"stale": false
}
Manual assets use normal asset inventory storage and deduplication. The backend normalizes the value and rejects duplicates for the same organization, type, and normalized value. Manual assets are marked with source_plugin=manual and metadata source manual.
Defaults:
approved=truedisabled=falsestale=falsecriticality=unknown
Creating an Asset manually does not create or approve a Scan Scope and does not start a scan. Asset inventory and Scan Scope are separate concepts. To scan a manual asset, use the existing manual plugin execution action or create/approve a scan scope separately.
Supported manual Asset types are:
domainsubdomainipservicewebappcertificate
The system-managed organization Asset cannot be created manually. Removed Asset types such as url, cidr, ip_range, asn, port, path, parameter, and technology are not canonical Assets and cannot be created as normal inventory.
Asset Lifecycle
Assets have three lifecycle flags in addition to criticality:
| Field | Meaning |
|---|---|
approved |
The asset has been reviewed or confirmed by an authorized hxEASM user. |
disabled |
The asset remains stored and visible, but is excluded from asset-derived active execution. |
stale |
The asset has been manually marked as outdated or no longer recently confirmed. |
Existing assets are migrated as approved=true, disabled=false, and stale=false so upgrades do not make current inventories unusable. New assets seeded directly from explicitly approved scopes are created as approved. Assets discovered automatically by plugins are created as unapproved by default unless the global scan setting auto_approve_discovered_assets is enabled. Rediscovery does not overwrite an existing lifecycle state.
Changing auto_approve_discovered_assets only affects newly inserted scan/plugin-discovered assets. It does not approve or unapprove previously discovered assets.
Users with admin or hacker role can update lifecycle state for assets in organizations they can access. Clients can read lifecycle state but cannot modify it.
The All Assets page supports multi-select bulk approval for authorized users. The header checkbox selects only assets currently loaded in the visible list response, which normally means the current page. It does not select every matching asset across all pages.
Lifecycle update endpoint:
PATCH /api/v1/assets/{asset_id}/lifecycle
{
"approved": true,
"disabled": false,
"stale": false
}
Any subset of the fields may be sent. The response is the updated asset.
Bulk approval endpoint:
PATCH /api/v1/organizations/{organization_id}/assets/bulk
{
"asset_ids": ["asset-uuid-1", "asset-uuid-2"],
"approved": true
}
Bulk approval is limited to 500 asset IDs per request. It only changes approved to true; disabled, stale, and criticality are left unchanged. Assets that are already approved are counted as unchanged and do not generate duplicate approval change events.
List filtering supports approved=true|false, disabled=true|false, and stale=true|false. These filters compose with existing type, criticality, sort_by, sort_order, page, and page_size filters. Supported sort fields are type, value, criticality, lifecycle, source, confidence, first_discovered, and threat_count; sort order is asc or desc.
Asset list responses include threat_count, the total number of Threat records associated with the asset through the canonical threats.asset_id relationship. Assets without associated Threats return 0.
Disabled assets are not deleted. They remain visible in asset inventory, graph details, history, reports, and relationships to vulnerabilities or files. Disabled prevents manual plugin execution against that saved asset and excludes saved assets when the worker reconstructs scan targets from the asset inventory.
A disabled asset may still be rediscovered by an independent approved scan scope that includes the same infrastructure. Disabling an asset does not automatically disable or alter approved scope records.
Automatic stale calculation is not implemented yet. stale is a persisted manual flag in this version.
Managed Context and Observed State
Human-managed Asset context is stored separately from scanner data:
assets.managed_contextstores operator-maintained description, owner, team, environment, and tags.assets.metadata.asset_infostores scanner-owned observed technical state.
Scanners and plugins must not write Managed Context. User edits must not be stored as observed scanner state.
asset_info is intentionally partial. One plugin can update asset_info.service.version without deleting asset_info.tls.supported_versions written by another plugin. Omitted fields are not removals. Explicit replacement/removal semantics are reserved for producer-declared authoritative sets.
Canonical observed-state producers currently include DNSX, Naabu, Nmap, HTTPX, Katana, and TLSX. Observed-state patches create full scanner-owned snapshots and field-level Scan Changes in backend storage. Asset Detail reads Scan Changes for the Scan mode of the History tab, the two-category change chart, and Recent Changes. See Asset Observed State.
Asset History
Asset History is a dedicated per-Asset lifecycle timeline. It is separate from Exposure Changes and Audit Log:
- Audit Log answers who performed an action.
- Exposure Changes answer what changed in the global attack surface/security feed.
- Asset History answers what happened to one specific Asset over time.
The Asset detail panel reads:
GET /api/v1/assets/{asset_id}/history
The Asset detail panel also reads Scan Changes separately:
GET /api/v1/assets/{asset_id}/scan-changes
GET /api/v1/assets/{asset_id}/change-summary
asset_history remains the System Changes source. asset_scan_changes remains the Scan Changes source. The Asset Detail chart collapses all system history event subtypes into one System Changes category and all scan change types into one Scan Changes category. The History tab defaults to System and offers a System / Scan switch. Recent Changes merges the latest rows from both domains.
V1 history events include:
asset.discoveredasset.created.manualasset.approval.changedasset.disabled.changedasset.stale.changedasset.criticality.changed
History rows include event type, field when applicable, old value, new value, source type (user, scan, or system), actor user information for user-driven changes, source plugin and scan context for scan-created assets, and timestamp.
Existing Assets are not backfilled with synthetic history rows. Asset History starts recording accurate events after the feature is deployed.
Asset History does not record noisy refresh fields such as last_seen_at, updated_at, or generic full metadata churn. Rediscovery of an existing asset does not create history unless a future selected meaningful property rule is added.
Some events intentionally appear in more than one subsystem. For example, a manual Criticality change can create an Audit Log row for who performed the action and an Asset History row for the per-Asset lifecycle timeline.
Asset Criticality
Asset criticality is a manual business-importance marker for future prioritization and risk scoring work. It is not vulnerability severity. A low-severity issue on a critical VPN may be more important than a medium issue on a test host.
Supported values:
unknownlowmediumhighcritical
New assets default to unknown. The source is always manual in this MVP. hxEASM does not infer criticality from hostnames, banners, technologies, vulnerabilities, exposure changes, LLMs, agents, or heuristics. Suggested criticality and AI-assisted recommendations are future work.
Users with admin or hacker role can update criticality. Clients can read the field but cannot modify it. Asset list and graph responses include criticality so the UI can show it in tables and node details.
API update endpoint:
PATCH /api/v1/assets/{asset_id}/criticality
{ "criticality": "high" }
The response is the updated asset. List filtering supports criticality=critical, criticality=high, criticality=medium, criticality=low, and criticality=unknown.