defining-pentest-scope

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, unsupported exclusions, all-address ranges, or recorded third-party IP matches. Trigger with: "define scope", "enumerate targets", "validate target list", "generate IP allowlist".

Allowed Tools

ReadBash(python3:*)Glob

Provided by Plugin

penetration-tester

25-skill pentest pack with engagement governance, network/code/dependency scans, OWASP Top 10 mapping, and exec-readable reporting. Heavy-hitter compliant; chain-of-custody attestable.

security v3.33.0
View Plugin

Installation

This skill is included in the penetration-tester plugin:

/plugin install penetration-tester@claude-code-plugins-plus

Click to copy

Instructions

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:

  1. 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.
  2. 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.).
  3. 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 IPv4 range MEDIUM an inclusion overlaps a recorded reserved range; verify the engagement context (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+; PyYAML for YAML inputs, or use JSON without PyYAML
  • 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 "${CLAUDE_PLUGIN_ROOT}/skills/defining-pentest-scope/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 block handoff. Resolve malformed entries and conflicts before using scanner inputs. The third-party IP list is illustrative; this checker has no approval override for its blocking range findings. Reserved IPv4 ranges are advisory MEDIUM findings whose meaning depends on the engagement.

Step 4 — Hand off the allowlist


python3 "${CLAUDE_PLUGIN_ROOT}/skills/defining-pentest-scope/scripts/define_scope.py" --roe engagements/acme-2026-q2/roe.yaml \
    --emit-allowlist /tmp/allowed-ips.txt \
    --emit-targets /tmp/normalized-targets.json

A nonzero exit blocks handoff: do not consume files from that run. Blocking validation produces no new allowlist or normalized targets and preserves any previous files; those previous files are not approval of the failed scope. --min-severity only filters displayed findings, never the blocking exit code.

After a successful structural check, the allowlist is one IP/CIDR per line for:

  • 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 "${CLAUDE_PLUGIN_ROOT}/skills/defining-pentest-scope/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 "${CLAUDE_PLUGIN_ROOT}/skills/defining-pentest-scope/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 "${CLAUDE_PLUGIN_ROOT}/skills/defining-pentest-scope/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.

Findings use the shared skill_id, title, severity, target, detail, remediation and optional evidence/enrichment fields. Successful outputs report offline structural checks, not legal permission or network ownership.

Error Handling

  • Missing/unreadable/invalid ROE or extension: structured nonzero result.
  • Scalar scope sections, malformed exclusions, conflicting literal entries, and all-address ranges (::/0, 0.0.0.0/0) block handoff.
  • URL scopes require literal origins and simple path segments. Credentialed, query/fragment, encoded and dot-segment forms are not accepted. A hostname inclusion covers that literal name's URLs; a URL inclusion is origin/path restricted. DNS is not resolved, so enforce resolved IPs and redirects at scan time against independently confirmed authorization.
  • Output I/O failure returns nonzero. Do not consume artifacts from a failed invocation, including an invocation whose output write failed.

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

Ready to use penetration-tester?