Defining Pentest Scope
Overview
A pentest scope is a list of permission boundaries. Get it wrong
and you either (a) miss real exposure by failing to test something
the customer expected covered, or (b) probe something you weren't
allowed to touch and turn the engagement into a liability event.
Both failure modes share a root cause: the scope list was a vague
narrative ("test the marketing site and the API") rather than a
machine-readable, syntactically-validated, conflict-checked
artifact.
This skill takes the in-scope and out-of-scope sections from a ROE
and produces three deliverables:
- Normalized target list — every entry parsed into a
structured form (host vs CIDR vs URL path vs cloud account vs
SaaS tenant), with explicit type tagging. Downstream cluster
1-4 skills consume this list rather than raw strings.
- IP allowlist — flat list of IPv4 and IPv6 addresses /
CIDRs ready to paste into scanner configurations (nmap target
list, Burp scope file, AWS WAF allowlist, etc.).
- Conflict report — Findings flagging syntactically-malformed
entries, overlap between in-scope and out-of-scope, inclusion
of reserved ranges (RFC1918, link-local, multicast), and known
third-party SaaS infrastructure that needs separate authz.
The skill does NOT perform DNS resolution or network probing —
that would itself be a "first probe" of the target, which by the
governance model must happen AFTER scope is locked.
When the skill produces findings
| Finding |
Severity |
Threshold |
Affected control |
| Malformed target syntax |
HIGH |
Entry doesn't parse as host / CIDR / URL / account-id |
(legal) |
| In-scope overlaps out-of-scope |
CRITICAL |
An in-scope target falls within an out-of-scope CIDR |
(legal) |
| Reserved range without acknowledgement |
HIGH |
RFC1918, link-local (169.254/16), multicast (224/4), broadcast in in-scope list |
(operational) |
| Known third-party SaaS in scope |
HIGH |
In-scope IP matches a known SaaS range (AWS, Cloudflare, GitHub, etc.) without separate authz |
(legal) |
| Duplicate target |
INFO |
Same target appears multiple times |
(operational) |
Wildcard subdomain (e.g. *.acme.example) |
INFO |
Wildcards expand at scan time |
(informational) |
| All targets validated cleanly |
INFO |
Positive confirmation |
(informational) |
Prerequisites
- Python 3.9+
- ROE file at
./roe.yaml (or pass --roe FILE)
- Optional
.scope-extension.yaml listing additional targets
added mid-engagement (each must reference an authz amendment)
Target syntax forms
| Form |
Example |
Notes |
| Hostname |
app.acme.example |
DNS-resolvable name |
| Wildcard subdomain |
*.acme.example |
Resolved at scan time; flag for explicit acknowledgement |
| IPv4 address |
203.0.113.10 |
Single host |
| IPv4 CIDR |
203.0.113.0/24 |
Network range |
| IPv6 address |
2001:db8::10 |
Single host |
| IPv6 CIDR |
2001:db8::/32 |
Network range |
| URL with path |
https://app.acme.example/api/v2 |
Path-restricted scope |
| Cloud account ID |
aws:123456789012 or gcp:acme-prod |
Cloud control-plane scope |
| SaaS tenant |
okta:acme-corp or auth0:acme |
SaaS-tenant scope |
Instructions
Step 1 — Provide the scope source
The skill reads the ROE's in_scope_targets and
out_of_scope_targets sections by default. Override with
--roe FILE if the engagement ROE isn't at the default path.
Step 2 — Run the scope definition
python3 ./scripts/define_scope.py --roe engagements/acme-2026-q2/roe.yaml
Options:
Usage: define_scope.py [OPTIONS]
Options:
--roe FILE Path to ROE YAML (default: ./roe.yaml)
--emit-allowlist FILE Write flat IP allowlist to FILE
--emit-targets FILE Write normalized target list to FILE
--extension FILE Additional scope extension YAML
--output FILE Findings output
--format FMT json | jsonl | markdown (default: markdown)
--min-severity SEV default info
Step 3 — Review the conflict report
CRITICAL findings (overlap between in-scope and out-of-scope) must
be resolved before any scan runs. Either narrow the in-scope range
or remove the out-of-scope overlap; the customer's authorizer
decides which.
HIGH findings (malformed targets, third-party SaaS, reserved
ranges) require explicit acknowledgement — either fix the entry
or document in the ROE why the range is intentionally included.
Step 4 — Hand off the allowlist
python3 ./scripts/define_scope.py --roe engagements/acme-2026-q2/roe.yaml \
--emit-allowlist /tmp/allowed-ips.txt \
--emit-targets /tmp/normalized-targets.json
The allowlist file is one IP/CIDR per line, ready to paste into:
- nmap:
nmap -iL /tmp/allowed-ips.txt
- AWS WAF rule: convert to JSON via your standard tooling
- Burp Suite: paste into Target → Scope → Include
- This pack's cluster 1 skills: pass via the target argument
Examples
Example 1 — Generate scope artifacts for a new engagement
python3 ./scripts/define_scope.py \
--roe engagements/acme-2026-q2/roe.yaml \
--emit-allowlist engagements/acme-2026-q2/scope/allowed-ips.txt \
--emit-targets engagements/acme-2026-q2/scope/normalized-targets.json \
--output engagements/acme-2026-q2/scope/scope-report.md
Example 2 — Validate a mid-engagement scope extension
python3 ./scripts/define_scope.py \
--roe engagements/acme-2026-q2/roe.yaml \
--extension engagements/acme-2026-q2/scope-extension-20260615.yaml
The extension YAML follows the same target format. The skill
validates that every extension entry has an associated
authorization reference and emits a CRITICAL finding for any
extension entry that doesn't.
Example 3 — Pre-scan validation gate
python3 ./scripts/define_scope.py --roe engagements/acme-2026-q2/roe.yaml \
--min-severity high \
--format json --output /tmp/scope-issues.json
jq -e '. == []' /tmp/scope-issues.json || { echo "Scope issues block scan"; exit 1; }
Output
JSON / JSONL / Markdown per lib/report.py. Exit codes: 0 clean,
1 high/critical, 2 error.
Each Finding includes:
id — scope::<issue>::<target> (e.g. scope::malformed::foo[bar)
severity — CRITICAL / HIGH / MEDIUM / INFO
category — engagement-scope
summary — what's wrong with the entry
evidence — original entry, parsed form, conflict source, line in ROE
Error Handling
- ROE missing → emits CRITICAL finding, exits 1.
- In-scope section missing or empty → CRITICAL finding, exits 1.
- Unparseable entry → HIGH finding per entry, scan continues
for other entries.
- Extension file referenced but missing → HIGH finding.
- IPv6 CIDR with very large mask (e.g.
::/0) → CRITICAL —
almost certainly a typo; refuse to expand.
Resources
references/THEORY.md — Why scope is the load-bearing artifact
of pentest legality, target-type taxonomy, known SaaS-range
classification (AWS, Cloudflare, GCP, Azure), DNS resolution
policy (when/whether to resolve at scope-definition time),
CIDR-overlap detection theory
references/PLAYBOOK.md — Per-engagement-type scope templates
(web app, internal network, red team, cloud account, SaaS tenant),
scope-extension protocol, allowlist-emission patterns per
scanner, common scope-mistake patterns
1---2name: defining-pentest-scope3description: Parse the ROE scope definition, enumerate every in-scope target (hostnames, IPs, CIDRs, URLs, cloud accounts, SaaS tenants), validate syntax, detect overlap with out-of-scope or known third-party SaaS ranges, and emit a normalized target list plus IP allowlist for scanning tools. Runs after confirming-pentest- authorization and before any cluster 1-4 scan. Use when: starting an engagement, expanding scope mid-engagement, validating that a target list matches the ROE, or generating an allowlist for an external scanner. Threshold: malformed syntax, in-scope overlap with out-of-scope, reserved or third-party SaaS ranges without acknowledgement. Trigger with: "define scope", "enumerate targets", "validate target list", "generate IP allowlist".4license: MIT5---6
7# Defining Pentest Scope
8
9## Overview
10
11A pentest scope is a list of permission boundaries. Get it wrong
12and you either (a) miss real exposure by failing to test something
13the customer expected covered, or (b) probe something you weren't
14allowed to touch and turn the engagement into a liability event.
15Both failure modes share a root cause: the scope list was a vague
16narrative ("test the marketing site and the API") rather than a
17machine-readable, syntactically-validated, conflict-checked
18artifact.
19
20This skill takes the in-scope and out-of-scope sections from a ROE
21and produces three deliverables:
22
231. **Normalized target list** — every entry parsed into a
24 structured form (host vs CIDR vs URL path vs cloud account vs
25 SaaS tenant), with explicit type tagging. Downstream cluster
26 1-4 skills consume this list rather than raw strings.
272. **IP allowlist** — flat list of IPv4 and IPv6 addresses /
28 CIDRs ready to paste into scanner configurations (nmap target
29 list, Burp scope file, AWS WAF allowlist, etc.).
303. **Conflict report** — Findings flagging syntactically-malformed
31 entries, overlap between in-scope and out-of-scope, inclusion
32 of reserved ranges (RFC1918, link-local, multicast), and known
33 third-party SaaS infrastructure that needs separate authz.
34
35The skill does NOT perform DNS resolution or network probing —
36that would itself be a "first probe" of the target, which by the
37governance model must happen AFTER scope is locked.
38
39## When the skill produces findings
40
41| Finding | Severity | Threshold | Affected control |
42|---|---|---|---|
43| Malformed target syntax | **HIGH** | Entry doesn't parse as host / CIDR / URL / account-id | (legal) |
44| In-scope overlaps out-of-scope | **CRITICAL** | An in-scope target falls within an out-of-scope CIDR | (legal) |
45| Reserved range without acknowledgement | **HIGH** | RFC1918, link-local (169.254/16), multicast (224/4), broadcast in in-scope list | (operational) |
46| Known third-party SaaS in scope | **HIGH** | In-scope IP matches a known SaaS range (AWS, Cloudflare, GitHub, etc.) without separate authz | (legal) |
47| Duplicate target | **INFO** | Same target appears multiple times | (operational) |
48| Wildcard subdomain (e.g. `*.acme.example`) | **INFO** | Wildcards expand at scan time | (informational) |
49| All targets validated cleanly | **INFO** | Positive confirmation | (informational) |
50
51## Prerequisites
52
53- Python 3.9+
54- ROE file at `./roe.yaml` (or pass `--roe FILE`)
55- Optional `.scope-extension.yaml` listing additional targets
56 added mid-engagement (each must reference an authz amendment)
57
58## Target syntax forms
59
60| Form | Example | Notes |
61|---|---|---|
62| Hostname | `app.acme.example` | DNS-resolvable name |
63| Wildcard subdomain | `*.acme.example` | Resolved at scan time; flag for explicit acknowledgement |
64| IPv4 address | `203.0.113.10` | Single host |
65| IPv4 CIDR | `203.0.113.0/24` | Network range |
66| IPv6 address | `2001:db8::10` | Single host |
67| IPv6 CIDR | `2001:db8::/32` | Network range |
68| URL with path | `https://app.acme.example/api/v2` | Path-restricted scope |
69| Cloud account ID | `aws:123456789012` or `gcp:acme-prod` | Cloud control-plane scope |
70| SaaS tenant | `okta:acme-corp` or `auth0:acme` | SaaS-tenant scope |
71
72## Instructions
73
74### Step 1 — Provide the scope source
75
76The skill reads the ROE's `in_scope_targets` and
77`out_of_scope_targets` sections by default. Override with
78`--roe FILE` if the engagement ROE isn't at the default path.
79
80### Step 2 — Run the scope definition
81
82```bash
83python3 ./scripts/define_scope.py --roe engagements/acme-2026-q2/roe.yaml
84```
85
86Options:
87
88```
89Usage: define_scope.py [OPTIONS]
90
91Options:
92 --roe FILE Path to ROE YAML (default: ./roe.yaml)
93 --emit-allowlist FILE Write flat IP allowlist to FILE
94 --emit-targets FILE Write normalized target list to FILE
95 --extension FILE Additional scope extension YAML
96 --output FILE Findings output
97 --format FMT json | jsonl | markdown (default: markdown)
98 --min-severity SEV default info
99```
100
101### Step 3 — Review the conflict report
102
103CRITICAL findings (overlap between in-scope and out-of-scope) must
104be resolved before any scan runs. Either narrow the in-scope range
105or remove the out-of-scope overlap; the customer's authorizer
106decides which.
107
108HIGH findings (malformed targets, third-party SaaS, reserved
109ranges) require explicit acknowledgement — either fix the entry
110or document in the ROE why the range is intentionally included.
111
112### Step 4 — Hand off the allowlist
113
114```bash
115python3 ./scripts/define_scope.py --roe engagements/acme-2026-q2/roe.yaml \
116 --emit-allowlist /tmp/allowed-ips.txt \
117 --emit-targets /tmp/normalized-targets.json
118```
119
120The allowlist file is one IP/CIDR per line, ready to paste into:
121
122- nmap: `nmap -iL /tmp/allowed-ips.txt`
123- AWS WAF rule: convert to JSON via your standard tooling
124- Burp Suite: paste into Target → Scope → Include
125- This pack's cluster 1 skills: pass via the target argument
126
127## Examples
128
129### Example 1 — Generate scope artifacts for a new engagement
130
131```bash
132python3 ./scripts/define_scope.py \
133 --roe engagements/acme-2026-q2/roe.yaml \
134 --emit-allowlist engagements/acme-2026-q2/scope/allowed-ips.txt \
135 --emit-targets engagements/acme-2026-q2/scope/normalized-targets.json \
136 --output engagements/acme-2026-q2/scope/scope-report.md
137```
138
139### Example 2 — Validate a mid-engagement scope extension
140
141```bash
142python3 ./scripts/define_scope.py \
143 --roe engagements/acme-2026-q2/roe.yaml \
144 --extension engagements/acme-2026-q2/scope-extension-20260615.yaml
145```
146
147The extension YAML follows the same target format. The skill
148validates that every extension entry has an associated
149authorization reference and emits a CRITICAL finding for any
150extension entry that doesn't.
151
152### Example 3 — Pre-scan validation gate
153
154```bash
155python3 ./scripts/define_scope.py --roe engagements/acme-2026-q2/roe.yaml \
156 --min-severity high \
157 --format json --output /tmp/scope-issues.json
158jq -e '. == []' /tmp/scope-issues.json || { echo "Scope issues block scan"; exit 1; }
159```
160
161## Output
162
163JSON / JSONL / Markdown per `lib/report.py`. Exit codes: 0 clean,
1641 high/critical, 2 error.
165
166Each Finding includes:
167
168- `id` — `scope::<issue>::<target>` (e.g. `scope::malformed::foo[bar`)
169- `severity` — CRITICAL / HIGH / MEDIUM / INFO
170- `category` — `engagement-scope`
171- `summary` — what's wrong with the entry
172- `evidence` — original entry, parsed form, conflict source, line in ROE
173
174## Error Handling
175
176- **ROE missing** → emits CRITICAL finding, exits 1.
177- **In-scope section missing or empty** → CRITICAL finding, exits 1.
178- **Unparseable entry** → HIGH finding per entry, scan continues
179 for other entries.
180- **Extension file referenced but missing** → HIGH finding.
181- **IPv6 CIDR with very large mask** (e.g. `::/0`) → CRITICAL —
182 almost certainly a typo; refuse to expand.
183
184## Resources
185
186- `references/THEORY.md` — Why scope is the load-bearing artifact
187 of pentest legality, target-type taxonomy, known SaaS-range
188 classification (AWS, Cloudflare, GCP, Azure), DNS resolution
189 policy (when/whether to resolve at scope-definition time),
190 CIDR-overlap detection theory
191- `references/PLAYBOOK.md` — Per-engagement-type scope templates
192 (web app, internal network, red team, cloud account, SaaS tenant),
193 scope-extension protocol, allowlist-emission patterns per
194 scanner, common scope-mistake patterns