Attack surface management
Attack surface management, written as a runbook.
External attack surface management is not a product category. It is a workflow — discover, resolve, classify, probe, deduplicate, notify — scheduled against the assets you declare you own. Skydra is the engine; the surface is yours.
- Scope-safe by default
- Deduplicated across runs
- Full execution history
- No fake AI classification
The workflow
Five stages, every one an auditable step.
Attack surface management is a pipeline. Skydra does not hide the pipeline behind a dashboard; it is literally what the runbook looks like.
Discover
Pull inventory from your ASM vendor, DNS zone, cloud account, or an uploaded list. Certificate transparency, passive DNS, crawl, surface-crawler, and surface-extract widen the view.
Resolve and classify
Resolve DNS, probe TCP and HTTP, parse response fingerprints, extract certificates. Every output is a typed value — a Domain, a URL, a port — that downstream steps consume without parsing strings.
Probe for exposure
Dangling DNS and subdomain takeover, exposed admin interfaces, soft 404s, broken TLS, missing security headers, cache deception, SSRF-reachable metadata, GraphQL introspection.
Deduplicate
Collections carry a declared identity rule. A certificate seen in last night's sweep and tonight's is one record with a bumped observation count, not two findings.
Persist as assets and findings
An asset page shows provenance: which sweep saw this hostname first, which saw it last, which findings reference it. Findings carry severity, confidence, evidence, and remediation.
Notify, triage, re-run
Slack, Discord, email, generic webhook. Approvals when a high-severity finding should pause the next step. One click resumes just the failed step; the completed steps are not re-executed.
A representative runbook
External sweep, run nightly against the declared scope.
inputs:
rootDomains: list<Domain>
steps:
ct_discover:
action: ct.certificates
with: { domains: inputs.rootDomains }
produces: list<Domain>
passive_dns:
action: passive.urls
forEach: inputs.rootDomains
all_candidates:
refine: unique(concat(ct_discover.output.hostnames, passive_dns.output.hostnames))
resolve:
action: dns.resolve
forEach: all_candidates.output
maxConcurrency: 32
live_hosts:
refine: filter(resolve.output, item.records ne [])
http_probe:
action: http.get
forEach: live_hosts.output
with: { url: concat("https://", item.host) }
retry: { attempts: 3, backoff: exponential }
takeover:
action: takeover
forEach: resolve.output
with: { records: item.records }
tls_broken:
action: tls.audit
forEach: live_hosts.output
write_assets:
action: assets.record
forEach: live_hosts.output
notify:
action: slack.post
condition: >
takeover.output.findings ne [] or
tls_broken.output.findings ne []
with:
channel: "#security-ops"
summary: templates.asm_summary.render(takeover.output, tls_broken.output)Every arrow in that pseudo-code is a real typed edge. The step picker in the console refuses to wire a step whose input type does not match the upstream output. Nothing here is "best effort" — the engine proves the shape before the schedule runs.
Named detectors used by the ASM path
Each is a real action in packages/actions.
Discovery and enumeration
- ct.certificates — fetch certificate-transparency logs for a root domain
- passive.urls — passive DNS and URL discovery
- crawl — authenticated and anonymous web crawling with robots-aware policy
- surface.crawler, surface.extract — guided crawling for discovery
- dns.resolve, dns.axfr, dns.cache — DNS resolution and misconfiguration probes
- host.resolver — classify a host as live, dark, or restricted
Takeover and ownership
- takeover — subdomain takeover across the known patterns
- dangling.dns — dangling CNAME and NS records
- dangling.domain — expired or claimable domain pointers
- hosting.takeover — reclaimable cloud hosting URLs
- spf.dangling — dangling SPF includes
- rdap.status — current registration posture
Exposure and configuration
- exposure, origin.exposure — exposed-origin probes
- exposed.sensitive_port — Redis, Mongo, MySQL, Memcached, SMB
- docker.api, docker.registry_v2 — Docker remote API without auth
- k8s.api, kubelet, kubernetes.dashboard — Kubernetes exposure
- grafana, kibana, jenkins, argocd, hasura, open_webui — product-specific probes
- cors, host.header_injection, cache.poisoning, cache.deception, http.smuggling — web-layer vulnerabilities
TLS, DNS, and email hygiene
- tls audit — expiry, protocol, cipher, HSTS, SAN validation
- dnssec.audit — DNSSEC validation and configuration
- mta_sts.audit — MTA-STS policy posture
- caa.audit — CAA records and permissive CA scope
- spf.dangling — SPF includes pointing to abandoned infrastructure
- email.auth_migration — SPF/DMARC/DKIM transition hygiene
What this does not promise
Honest about what EASM can and cannot do.
- Skydra discovers surface it can reach. Internal-only assets (behind corporate VPN, on private cloud networks) are discovered through cloud account audits, not through outside-in probes.
- Attribution — "is this host actually ours?" — is a workspace scope decision, not a product feature. The scope policy must be set correctly; Skydra does not guess ownership.
- Shadow IT discovery through SaaS billing and SSO logs is not shipped. Skydra discovers from DNS, certificates, cloud accounts, and web crawling — not from billing export.
- Dark web monitoring and credential leak detection are out of scope. The signals Skydra operates on are observable from the public internet or from inside your cloud accounts.
Point an ASM runbook at your scope.
Declare the root domains your workspace owns, clone the starter ASM runbook, and let it run. Scope enforcement prevents it from touching anything you did not list.