- name
- org-attack-surface
- description
- Org-grade attack-surface mapping: given a company's legal identity, discover its ENTIRE owned internet footprint — corporate family -> owned domains -> owned netblocks/ASN -> live assets — with attribution discipline, not just DNS breadth. The org-first attribution pyramid (legal entity -> LEI/registration -> corporate family -> owned domains -> owned netblocks/ASN -> live assets). Corporate-identity resolution via the GLEIF LEI API (legal name -> LEI, exact-LEI direct-children expansion, downward-only depth-capped BFS, NEVER a name re-resolution), SEC-EDGAR full-text search + Exhibit-21 subsidiary entity names, OpenCorporates entity corroboration, Wikidata SPARQL corporate graph (P856/P355/P749/P1830). Domain attribution via reverse-WHOIS (WhoisXML preview-then-purchase quota guard, SecurityTrails associated-domains — both paid), crt.sh O= certificate-transparency organization pivot (keyless), infrastructure correlation (shared NS/MX/SaaS-TXT, netblock membership, reverse-DNS PTR), and the independent-evidence combiner (1-prod(1-w_i), rule of three, OwnerTier NONE/WEAK/MODERATE/STRONG/CONFIRMED) — discover-only `related:` candidates are NEVER auto-scanned. Netblock/ASN attribution via org-first RIR queries (ARIN Whois-RWS org-handle search, RIPE DB organisation + inverse-org search) that recover 'dark netblocks' with no DNS link to the seed, ASN discovery (RIPEstat + BGPView union), the HYPERSCALER-SCOPE GUARD (never attribute a whole AWS/GCP/Azure/Cloudflare announced range to a tenant — keep only the seed-containing block, tag shared_hosting_cdn), and org-identity-seeded internet-scan-index queries (Shodan/Censys/ZoomEye/FOFA/BinaryEdge org: filters + an always-on keyless crt.sh fallback). Promote-to-scan triage ranks forgotten discover-only netblocks by remote-access exposure (gateway-vendor/KEV/control-plane/datastore port scoring) into an operator queue. Attribution confidence rubric + anti-patterns (namesake grafting via GLEIF name re-resolution, single-signal ownership, hyperscaler over-attribution, privacy-WHOIS pivot poisoning, RIR org-name collisions). Passive/keyless-first OSINT only — every paid-key dependency is enrichment on top of a keyless core, never a hard requirement. Use when mapping an organization's full corporate-family internet footprint, resolving a legal entity to its LEI/subsidiaries, discovering domains/netblocks/ASNs an org owns beyond its one seed domain, auditing M&A/shadow-IT sprawl, or scoping an engagement that starts from a company NAME rather than a domain.
- version
- 1
- sources
- asm_reference_impl, gleif, public_research
- triggers
- ["org attack surface","organization attack surface","org footprint","corporate footprint","corporate family","corporate family mapping","corporate family tree","subsidiary discovery","subsidiary attack surface","subsidiary mapping","GLEIF","LEI lookup","legal entity identifier","legal name to LEI","EDGAR subsidiary","SEC EDGAR","Exhibit 21","10-K subsidiaries","OpenCorporates","Wikidata corporate graph","reverse WHOIS","reverse whois pivot","crt.sh organization search","crt.sh O=","certificate transparency org pivot","CT org pivot","dark netblock","org-first RIR search","ARIN Whois-RWS","ARIN org search","RIPE DB search","RIPE organisation search","[Truncated]"]
# Org Attack Surface — Corporate-Family Footprint Mapping
> Companion skills: [`osint-methodology`](../osint-methodology/) (the "how to think" 5-stage recon
> pipeline this plugs into) and [`offensive-osint`](../offensive-osint/) (the per-host arsenal you run
> once this skill hands you owned domains/netblocks — see its §14 Public Records and §28 Infrastructure
> OSINT for the tool directory this skill deepens rather than duplicates). This skill answers a
> different, upstream question: **not** "what does `acme.com` expose", but "what does **Acme
> Corporation, the legal entity**, own across every domain, netblock, and subsidiary it has — including
> the parts with no DNS trail back to the seed at all."
## 0. When to Use / When NOT
**Use this skill when:**
- The engagement starts from a **company name or legal entity**, not a domain — you need to derive
the domain(s) first, not just enumerate one.
- You need to find an org's **subsidiaries, sister brands, or M&A-acquired footprint** that a
DNS/CT-only sweep of one seed domain would never surface.
- You suspect **"dark" IP space** — netblocks or ASNs registered to the org's legal entity with
no DNS record pointing at them (forgotten datacenter allocations, un-linked M&A infrastructure,
IPs that only ever ran raw services).
- You need **attribution discipline**: every candidate domain/netblock/ASN this skill surfaces
carries an explicit, auditable ownership score — never a bare "looks related."
- You are scoping a large or conglomerate engagement and need to prune scope by real corporate
ownership before spending recon budget on strangers.
**Do NOT use this skill when:**
- You already have a confirmed, bounded target list and just need per-host recon — go straight to
`offensive-osint`.
- The target's authorization isn't established — see §1.
- You need active exploitation or post-exploitation — out of scope here and everywhere in this
skill family.
---
## 1. Authorization & Legal Posture
Same posture as the companion skills: intended for assets the operator owns or has **written
authorization** to assess. This skill is unusually likely to surface entities the operator did NOT
ask about (subsidiaries, sister brands, M&A targets) — that is the point, but it sharpens the scope
question rather than loosening it.
**Soft scope check** — ask once when unclear:
> *"This will surface the target's subsidiaries and related infrastructure, some of which may be
> outside your engagement scope. Should I include the full corporate family, or stay bounded to the
> named entity and its direct domains?"*
**Always-on guardrail specific to this skill:** every asset this skill discovers beyond the seed
domain — related domains, dark netblocks, discover-only ASNs, index-search IPs — is a **lead**, not
a target. See §6.6 and every "discover-only" callout below. Nothing this skill produces is
automatically in scope for active testing; a human confirms ownership first.
---
## 2. Confidence Levels & Ownership Tiers (two axes — do not conflate them)
This skill runs **two separate scoring axes**, and mixing them up is the single most common way to
misreport a finding.
**Axis 1 — Finding confidence** (same as the companion skills): TENTATIVE / FIRM / CONFIRMED.
Answers *"how sure am I this asset/record exists and I read it correctly."*
**Axis 2 — Ownership tier**: answers *"how sure am I this asset belongs to the target
organization."* Computed by combining every `OwnerSignal` that fired for a candidate via an
independent-evidence formula (full mechanics in §8.4):
| Score band | Tier | Meaning |
|---|---|---|
| 0 | `NONE` | No signal fired. |
| 0–39 | `WEAK` | One low-weight signal (e.g. shared nameserver) — a lead, not attribution. |
| 40–69 | `MODERATE` | Multiple weak signals, or one medium signal — worth an operator's eyeball. |
| 70–89 | `STRONG` | Multiple independent signals, or one high-confidence signal — an active-scan-eligible score in a typical ASM promotion gate. |
| 90–100 | `CONFIRMED` | Independent corroboration close to certainty, or a **confirming** signal (e.g. the seed domain itself) that forces 100 directly. |
**A `NOT_OWNED` (stranger-lock) signal caps the score at 20 regardless of how many weak positives
also fired** — so a foreign asset that happens to share a generic nameserver with the seed can never
climb into an owned tier.
**Critical nuance:** ownership tier is advisory, not a gate. Every domain/netblock/ASN/IP this skill
mints beyond the seed carries an explicit **`discover_only=True`** structural flag that keeps it out
of active scanning **independent of its score** — a `related:` domain that happens to score
`STRONG` (85) still does not get auto-scanned. The score tells the operator *which* discover-only
lead to promote first; only an explicit operator action (§10, promote-to-scan) moves an asset into
the active pipeline.
**Rule of three still applies** on top of the numeric score: a single signal type, however
individually weighted, is a lead. Treat anything below `STRONG` as requiring a second, independent
signal class before you say it out loud to a client as "this belongs to them."
---
## 3. Output Format
Every candidate carries the standard finding schema (see companion skill §3) **plus** the
org-attribution evidence block:
```
Finding:
id: <stable hash>
module: org-attack-surface
asset_key: <typed key — e.g. org:acme-corp, related:acmesub.com, net:203.0.113.0/24, asn:64500>
category: ORG_FOOTPRINT | RELATED_DOMAIN | DARK_NETBLOCK | PROMOTE_QUEUE
severity: info # attribution work is discovery, not a vuln — see §6.6
confidence: <tentative|firm|confirmed> # Axis 1 — did I read the record right
title: <one-line summary>
description: <what was found + why it's plausibly the org's>
attribution:
owner_score: <0-100> # Axis 2 — combine() output
owner_tier: <none|weak|moderate|strong|confirmed>
discover_only: true # structural gate; independent of owner_score
signals:
- name: <signal name, e.g. cert_org_match>
weight: <0.0-1.0>
confirming: <bool>
detail: <human-readable evidence, e.g. "crt.sh O= match: Acme Corp">
source: <module/connector>
evidence:
url: <where found>
timestamp: <UTC ISO8601>
raw: <truncated to 2 KiB>
references: [<registry URL, RFC, etc.>]
remediation: <"confirm ownership before promoting to active scan" — always the remediation here>
```
UTC timestamps everywhere. Never collapse `attribution.signals` to a bare count — the reviewing
operator (or an auditor asking "why did you attribute this to them") needs to see exactly which
evidence fired, not just how much.
---
## 4. Source Hygiene & Citations
Same discipline as the companion skills: URL + UTC timestamp + tool/API version + run_id on every
artifact. For registry lookups specifically:
- Record the **exact query string** sent to GLEIF/EDGAR/ARIN/RIPE — registry search results are not
reproducible from a vague "I searched for the company" note months later.
- Cache raw registry JSON responses (GLEIF, ARIN, RIPE, EDGAR) — these APIs change/deprecate fields
and a re-run six months later may not reproduce the same shape.
- GLEIF LEI records and EDGAR filings are durable references (an LEI or CIK doesn't expire); prefer
citing those over an ephemeral index-search hit.
---
## 5. Do NOT
- Do NOT auto-scan a `related:` domain, a `discover_only` netblock/ASN, or a `discover_only` IP.
Ever. That is the entire structural contract of this skill (§2, §6.6).
- Do NOT re-resolve a subsidiary's name against GLEIF/EDGAR/OpenCorporates once you already hold its
exact LEI or CIK — name search is not unique across jurisdictions and will graft a namesake's
subtree onto the wrong parent (§7.1, §11.1).
- Do NOT treat a shared nameserver, shared MX, or a brand string in an ASN org field as ownership.
Each is `WEAK`-tier at best and needs corroboration (§11.1).
- Do NOT attribute an entire hyperscaler-announced ASN range (AWS/GCP/Azure/Cloudflare/Akamai) to a
tenant because one of the tenant's IPs lives in it (§9.5 — the hyperscaler-scope guard).
- Do NOT pivot on a privacy-masked WHOIS registrant org or a registrar's own name/email — the
pivotability guards in §8.1/§8.3 exist because these produce thousands of unrelated false
positives.
- Do NOT treat a fuzzy label match (Wikidata `rdfs:label`, an ASN holder substring match) as
equivalent evidence to a filed registry record (a GLEIF LEI relationship, an EDGAR Exhibit-21
entry, an exact RIR org-handle match). Weight them accordingly (§8.4's signal table).
- Do NOT paste real registrant PII, private beneficial-ownership data, or engagement-client identity
details into cloud LLMs beyond what's needed for the recon task itself.
---
## 6. The Org-First Attribution Pyramid
The mental model for everything in this skill. Most recon starts **bottom-up** from a seed domain
and walks outward (subdomains, then IPs, then maybe an ASN). That misses everything that has no DNS
trail back to the seed. This skill also walks **top-down**, starting from the legal entity itself:
```
┌─────────────────────────┐
│ LEGAL ENTITY (name) │ §7 — the anchor: WHOIS registrant_org,
│ → LEI / registration │ the seed's own TLS cert O=, or a
└────────────┬─────────────┘ seed-owned ASN holder string
│
┌────────────▼─────────────┐
│ CORPORATE FAMILY │ §7.1-7.4 — GLEIF direct-child tree,
│ (subsidiaries, M&A) │ EDGAR Exhibit-21, OpenCorporates,
└────────────┬─────────────┘ Wikidata P355/P749/P1830
│
┌──────────────────┴──────────────────┐
▼ ▼
┌───────────────────────┐ ┌───────────────────────────┐
│ OWNED DOMAINS │ │ OWNED NETBLOCKS / ASN │
│ (seed + related:) │ │ (seed-owned + dark) │
└───────────┬─────────────┘ └─────────────┬───────────────┘
│ §8 — reverse-WHOIS, crt.sh O=, │ §9 — org-first RIR (ARIN/RIPE),
│ infra correlation, independent- bottom-up ASN (RIPEstat/BGPView),
│ evidence combiner hyperscaler-scope guard
└──────────────────┬──────────────────────┘
▼
┌─────────────────────────┐
│ LIVE ASSETS │ hand off to `offensive-osint` once
│ (hosts, services, apps) │ ownership is operator-confirmed
└─────────────────────────┘
```
### 6.1 Why top-down closes gaps bottom-up misses
A DNS/CT-only sweep of `acme.com` will find `mail.acme.com`, `api.acme.com`, subsidiaries that
happen to share the parent's nameservers — and nothing else. It will **never** find:
- A subsidiary acquired in an M&A deal that still runs on its own pre-acquisition domain, nameservers,
and netblocks, with zero DNS/cert linkage to `acme.com`.
- A netblock registered directly to "Acme Corporation" at ARIN or RIPE that has never had a single A
record point into it — a forgotten datacenter allocation, or IP space used only for raw
non-DNS-fronted services.
- A brand's cloud footprint that shows up in Shodan/Censys under `org:"Acme Corp"` with no DNS name at
all — a bare IP serving a banner with the org string baked into a TLS cert or ASN registration.
Each of these requires **starting from the org identity itself** and querying registries/indexes by
*who owns it*, not *what resolves from the seed*. That is what §7–§9 do.
### 6.2 Discovery direction cheat sheet
| Direction | Seeded from | Finds | Section |
|---|---|---|---|
| Top-down: entity → subsidiaries | Legal name / LEI | Corporate family tree | §7 |
| Top-down: entity → domains | Registrant org / cert O= string | Related root domains | §8 |
| Top-down: entity → netblocks/ASN | Org identity string | Dark netblocks, registered ASNs | §9.1–§9.4 |
| Top-down: entity → live IPs | Org identity string | No-DNS live hosts (index search) | §9.6 |
| Bottom-up: seed IP → ASN | Resolved seed IPs | Announced prefixes, adjacent infra | §9.3 |
Both directions run in the same pipeline and reconcile through the same ownership-scoring axis
(§2, §8.4) — a bottom-up-discovered netblock and a top-down-discovered netblock get scored the same
way and land in the same graph.
---
## 7. Corporate Identity Resolution
### 7.1 GLEIF LEI API — the corporate-family tree
The Global Legal Entity Identifier Foundation publishes, **keyless**, the filed parent/child
relationships between legal entities under ISO 17442 LEIs. This is the highest-precision source in
this entire skill because relationships are *filed*, not inferred.
**Two-hop query pattern:**
1. Resolve a legal name → LEI (only ever done for the **seed** — see why below):
```bash
LEI=$(curl -sk "https://api.gleif.org/api/v1/lei-records?filter[entity.legalName]=Acme%20Corporation&page[size]=1" \
-H "Accept: application/vnd.api+json" | jq -r '.data[0].attributes.lei')
echo "$LEI" # capture it — the next step reuses $LEI
```
```powershell
$r = Invoke-RestMethod -Uri "https://api.gleif.org/api/v1/lei-records?filter[entity.legalName]=Acme%20Corporation&page[size]=1" `
-Headers @{ Accept = "application/vnd.api+json" }
$lei = $r.data[0].attributes.lei
```
2. Fetch that LEI's **direct children** (one level down, filed relationships only):
```bash
curl -sk "https://api.gleif.org/api/v1/lei-records/$LEI/direct-children?page[size]=100" \
-H "Accept: application/vnd.api+json" | \
jq -r '.data[].attributes | .lei + " " + .entity.legalName.name'
```
```powershell
$children = Invoke-RestMethod -Uri "https://api.gleif.org/api/v1/lei-records/$lei/direct-children?page[size]=100" `
-Headers @{ Accept = "application/vnd.api+json" }
$children.data | ForEach-Object { "$($_.attributes.lei) $($_.attributes.entity.legalName.name)" }
```
**Recurse for a multi-level tree — but with two non-negotiable rules:**
1. **Downward-only.** Only ever ask for a LEI's direct children — never ascend to the ultimate
parent and fan into its siblings. Ascending turns "map Acme's subsidiaries" into "map every
company under Acme's ultimate holding conglomerate," which is a different (much larger, mostly
irrelevant) question the operator did not ask.
2. **Exact-LEI expansion for every descendant, name resolution ONLY for the seed.** Once you have a
child's own filed LEI from step 2's response, expand *that entity's* children via
`/lei-records/<child-LEI>/direct-children` directly — never by searching its **name** again.
**Why this matters:** `filter[entity.legalName]=` is a name search, and legal names are **not
unique across jurisdictions**. If entity B (a direct child of your seed) shares a name with an
unrelated entity B' in another country, re-resolving "B" by name can silently return B' — and
you then graft B''s entire subsidiary subtree onto your target's corporate family, under an
authoritative-looking GLEIF badge. Carrying the exact LEI through the recursion makes this
collision structurally impossible.
**Depth and fan-out caps (recommended, mirrors the production implementation):**
| Cap | Suggested value | Why |
|---|---|---|
| Max recursion depth | operator-set; default OFF, 1 level for a "deep" pass | Depth grows the tree exponentially with fan-out; an unbounded walk on a conglomerate never terminates in a useful time. |
| Max nodes minted total | ~200 | Global budget; a capped tree must be flagged as truncated, never silently rendered as complete. |
| Max direct children expanded per parent | ~50 | A parent with hundreds of filed subsidiaries (a large holding company) would otherwise dominate the whole budget. |
**When either cap is hit, flag the affected parent node (`children_truncated`) rather than silently
dropping the rest of its children** — an operator reading a "complete-looking" corporate tree that
was actually cut off is worse than one that visibly says "and N more, not expanded."
**Cycle safety:** track visited LEIs; a diamond ownership structure (A owns B and C, both B and C own
D) should mint D once, under whichever parent reaches it first — not twice, and not infinitely.
### 7.2 SEC-EDGAR — Exhibit-21 subsidiary lists
US-listed companies' 10-K filings include Exhibit 21, a list of subsidiary entity names. EDGAR's
full-text search surfaces filings; you extract entity names, not domains — EDGAR asserts *"this
subsidiary exists,"* not *"this subsidiary owns this URL."*
```bash
curl -sk 'https://efts.sec.gov/LATEST/search-index?q=%22Acme%20Corporation%22&forms=10-K' \
-H "User-Agent: YourOrg-Recon research@yourdomain.example" | \
jq -r '.hits.hits[]._source.display_names[]'
```
```powershell
$headers = @{ "User-Agent" = "YourOrg-Recon research@yourdomain.example" }
$r = Invoke-RestMethod -Uri 'https://efts.sec.gov/LATEST/search-index?q=%22Acme%20Corporation%22&forms=10-K' -Headers $headers
$r.hits.hits | ForEach-Object { $_._source.display_names }
```
**Caveats:**
- **Response shape is documented, not guaranteed** — EFTS full-text search returns *filing-level*
metadata under `_source` (`display_names`, `ciks`, `form`, …), so `display_names` are the filer /
co-filer entity names on matching 10-K filings — **corroboration that the entity files with the
SEC**, not a ready-made subsidiary roster. True subsidiary lists live inside the filing's
**Exhibit 21** document, which you fetch separately from the filing (not from the search index).
Verify the field against a live response before trusting a parse.
- SEC requires a **descriptive User-Agent identifying you** (name + contact) on every request — a
bare `curl` UA gets throttled/blocked. This is SEC's own fair-access policy, not a tooling
quirk.
- **US-SEC-filer-only.** Empty for any org that doesn't file 10-Ks (non-US, private, or a subsidiary
itself rather than the filer). Treat a null result as "not applicable," not "no subsidiaries."
- Entity names from EDGAR feed the **depth-1 entity→domain pivot** (§8.5) — you take each returned
name and pivot it through crt.sh O=, capped, to find candidate domains for that specific
subsidiary.
### 7.3 OpenCorporates — entity corroboration (not domain discovery)
OpenCorporates confirms *"is this a real registered company, and in what jurisdiction"* — it does
not supply domains at all. Use it to corroborate a candidate subsidiary name found elsewhere (GLEIF,
EDGAR, a press release) before spending a crt.sh pivot on it.
```bash
View on GitHub