# THEORY — Scope as the Load-Bearing Artifact

## Why scope is the load-bearing legal artifact

Authorization is the gate (see `confirming-pentest-authorization`),
but SCOPE is the perimeter the authorization applies to. A signed
ROE that says "test ACME's stuff" authorizes nothing in practice
because it doesn't define ACME's stuff. The scope list is the
projection of "what we're allowed to touch" into a list of concrete
targets a tester or tool can verify against.

The skill's purpose is to convert the human-readable ROE scope
section into a machine-readable, syntactically-validated, conflict-
checked artifact that downstream cluster 1-4 skills can consume
without ambiguity.

## Target-type taxonomy

Real-world scope lists mix several target types. The taxonomy:

| Type | Use | Validation |
|---|---|---|
| Hostname | `app.acme.example` | Valid DNS character set, length |
| Wildcard | `*.acme.example` | Wildcard prefix + valid suffix |
| IPv4 | `203.0.113.10` | Valid 4-octet form |
| IPv4 CIDR | `203.0.113.0/24` | Valid network + mask |
| IPv6 | `2001:db8::10` | Valid hex form |
| IPv6 CIDR | `2001:db8::/32` | Valid network + mask |
| URL with path | `https://app.acme.example/api` | Valid URL parse + non-empty netloc |
| Cloud account | `aws:123456789012` | Recognized cloud prefix |
| SaaS tenant | `okta:acme-corp` | Recognized SaaS prefix |

Mixing types in one list is normal; the skill's `--emit-targets`
output preserves the type tagging so downstream tools can route
each entry to the right scanner.

## Why DNS resolution is NOT done at scope-definition time

It would be tempting to resolve every hostname to its current IP
at scope-definition time and bake the IPs into the allowlist. This
is wrong, for two reasons:

1. **DNS resolution is itself a probe.** A DNS query against the
   target's authoritative DNS server (or against a recursive
   resolver that forwards to it) is detectable by sophisticated
   defenders. By the governance model, no probes happen before
   scope is locked.

2. **DNS changes during the engagement.** Resolving `app.acme.example`
   to `203.0.113.10` at scope-definition time, then having the
   customer's load balancer rotate to `203.0.113.11` mid-engagement,
   means the tester's IP allowlist is stale. Hostname-based scope
   resolves at scan time and tracks current DNS state.

The downstream cluster 1 skills resolve hostnames at scan time
themselves. The allowlist this skill emits contains the explicit
IPs/CIDRs the ROE listed, not resolved hostnames.

## CIDR overlap detection

Python's `ipaddress` module provides exact CIDR-overlap checks
via `IPv4Network.overlaps()` / `IPv6Network.overlaps()`. The skill
uses these directly.

A subtle case: `203.0.113.0/24` overlaps `203.0.113.128/25` (the
second is a sub-range of the first). If the ROE has the /24 in
in-scope and the /25 in out-of-scope, the skill emits a CRITICAL
finding: probing within the /25 sub-range would violate the
exclusion.

The customer's authorizer decides the resolution:

- Narrow in-scope to `203.0.113.0/25` (excludes the /25 explicitly)
- Remove the out-of-scope /25 entry (broaden authorization)
- Keep both and require per-target check at scan time

## Known third-party SaaS ranges

The skill ships an illustrative (NOT exhaustive) map of well-known
cloud / CDN / SaaS ranges. The most common ones in customer scope
lists by accident:

- AWS — customer mistakes their AWS-hosted endpoint's CDN front
  for their own infrastructure.
- Cloudflare — customer's domain resolves to Cloudflare; the IP
  is Cloudflare's, not the customer's.
- GitHub — customer's hosted-status page or docs site is on GitHub
  Pages.
- Vercel / Netlify — customer's marketing site frontend.

Probing these without separate authorization is testing the SaaS
vendor's infrastructure, which is almost universally prohibited
without the SaaS vendor's own pentest-authorization process.

Real engagements should consult the authoritative published IP
ranges:

- AWS: https://ip-ranges.amazonaws.com/ip-ranges.json
- Cloudflare: https://www.cloudflare.com/ips/
- GCP: https://www.gstatic.com/ipranges/cloud.json
- Azure: published quarterly as a JSON file

A future skill enhancement could ingest these authoritative
sources at scope-definition time and verify against current data.

## Reserved ranges

The skill flags RFC1918 (`10/8`, `172.16/12`, `192.168/16`),
link-local (`169.254/16`), multicast (`224/4`), loopback
(`127/8`), and IPv6 equivalents.

In an internal pentest, RFC1918 ranges are expected — these are
the customer's internal networks. The skill flags them as MEDIUM
rather than HIGH/CRITICAL: "verify intentional." The customer's
acknowledgement of the internal-pentest context resolves the
finding.

In an external pentest, RFC1918 in scope is a configuration error —
the tester would be testing their own network, not the customer's.
The skill cannot determine engagement type from the ROE alone, so
the operator interprets the finding in context.

## Wildcard subdomain semantics

`*.acme.example` is a scope-by-pattern. It says "any subdomain of
acme.example is in scope." The skill flags wildcards as INFO
because they're not malformed but they ARE broad — the customer
should confirm intent.

Wildcard expansion happens at scan time, when the tester:

1. Enumerates subdomains via passive sources (Certificate
   Transparency, DNS history, brute-force).
2. Each enumerated subdomain is checked against the wildcard's
   pattern.
3. Matched subdomains become concrete targets.

The cluster 2 fingerprinting skills handle subdomain enumeration.

## Scope hygiene patterns

Three patterns characterize well-defined scope lists:

1. **Explicit inclusion + explicit exclusion.** Always have both
   lists, even if one is empty. "Nothing else is included"
   beats "I assume the rest is excluded."

2. **Type-tagged entries.** Use the schema's `host:` / `cidr:` /
   `url:` keys explicitly. Bare string entries work but require
   the skill to guess the type, which can produce subtle errors.

3. **Notes on every entry.** Why is this in scope? Why is this
   out? Future you, six months from now, won't remember.

## When scope changes mid-engagement

The protocol:

1. Tester requests amendment via the engagement's documented
   communication channel.
2. Authorizer signs an amendment YAML referencing the original
   ROE's `engagement_id`.
3. Tester archives the amendment alongside the original ROE.
4. Re-run this skill with `--extension` pointing at the
   amendment.
5. The new combined scope becomes the authoritative scope from
   the amendment timestamp forward.

Never extend scope by verbal agreement. The whole point of the
ROE schema is that scope changes are auditable.
