intermediate By Mathias Paulenko

Penetration Test Scope Template

A template for defining the boundaries, targets, rules, and deliverables for a penetration testing engagement.

Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.

Overview

A Penetration Test Scope Template defines what will be tested, what will not be tested, how the testing will be conducted, and what the organization expects to receive. A clear scope protects the organization from unintended disruption, prevents legal issues for testers, and ensures the engagement delivers useful value.

When to Use

  • Hiring an external security firm for a penetration test.
  • Running an internal red-team or purple-team exercise.
  • Meeting compliance requirements for annual testing.
  • After a major architecture change or product launch.
  • Scoping a bug bounty or crowdsourced testing program.

Prerequisites

  • An inventory of systems, applications, and network ranges.
  • Legal and compliance approval for testing.
  • A contact list for emergency escalation.
  • An understanding of the testing methodology, such as OWASP or PTES.

Solution

Template

1. Engagement Details

FieldDescriptionValue
OrganizationEntity being testedAcme Corp
Engagement typeBlack box, gray box, or white boxGray box
Start dateWhen testing begins2026-07-01
End dateWhen testing ends2026-07-15
Testing windowAllowed hours08:00 - 18:00 UTC
Emergency contact24/7 contact for critical findingssecurity@example.com
Report due dateWhen findings are delivered2026-07-22

2. Targets In Scope

TargetTypeEnvironmentURL / IP RangeNotes
app.example.comWeb applicationProduction203.0.113.10Public-facing
api.example.comAPIProduction203.0.113.11OAuth2 protected
k8s clusterCloud infrastructureStaging10.0.0.0/16Read-only credentials provided
Admin portalWeb applicationProductionadmin.example.comMFA enabled

3. Out-of-Scope Items

ItemReason
Third-party SaaS providersOutside organizational control
Physical securityNot included in this engagement
Social engineeringExcluded per legal request
Denial-of-service attacksRisk to production uptime
Employee personal devicesPrivacy and legal boundaries
Production database writesCould corrupt customer data

4. Rules of Engagement

RuleDescription
Authorized testingOnly listed targets may be tested
CommunicationCritical findings reported immediately
Data handlingNo customer data exfiltration unless approved
ToolingCommercial and open-source tools allowed; no auto-exploitation on production
EvidenceScreenshots and logs required for all findings
ConfidentialityResults stored encrypted and shared only with named recipients
Clean-upTester must remove any persistence or accounts created during testing

5. Testing Methodology

PhaseActivitiesDeliverable
ReconnaissanceCollect public information and map targetsTarget inventory
ScanningVulnerability and configuration scanningScan output
ExploitationAttempt to validate vulnerabilitiesExploitation evidence
Post-exploitationAssess impact and lateral movementImpact analysis
ReportingDocument findings, risk, and remediationFinal report
RetestVerify fixes after remediationRetest report

6. Success Criteria

CriterionTarget
Coverage100% of in-scope targets tested
Critical findingsReported within 24 hours of discovery
Report qualityIncludes risk rating, evidence, and remediation steps
RetestAll high and critical findings remediated and retested
DebriefExecutive and technical sessions delivered

Explanation

The scope template aligns the organization and testers before any traffic is sent. It reduces legal risk, prevents production outages, and ensures the findings are relevant. Rules of engagement are especially important because they separate authorized testing from criminal activity under computer fraud laws.

Variants

  • Web application penetration test: Focuses on OWASP Top 10 testing for a single app.
  • Cloud penetration test: Targets AWS, Azure, or GCP configurations and IAM.
  • Red team exercise: Broader scope with stealth objectives and longer duration.
  • Bug bounty scope: Public-facing targets with safe harbor language and reward rules.
  • Internal network test: Assumes an insider or compromised endpoint perspective.

What works

  • Get written authorization before any testing begins.
  • Include both technical and business owners in scope definition.
  • Define emergency contacts and escalation paths.
  • Exclude third-party systems unless explicit permission is obtained.
  • Require proof-of-concept evidence for every finding.
  • Schedule retesting to validate remediation.
  • Store findings securely and limit distribution.

Common Mistakes

  • Defining a scope that is too narrow to find real risks.
  • Forgetting to include APIs, microservices, and mobile backends.
  • Not providing test credentials for authenticated testing.
  • Allowing testing on production without a rollback plan.
  • Skipping retest and assuming fixes are complete.
  • Not informing SOC or NOC that testing will occur.

Advanced Solutions

Automated reconnaissance with Nmap and Nuclei

Run automated scanning as a first pass before manual exploitation to identify low-hanging fruit:

#!/bin/bash
set -euo pipefail

TARGETS_FILE="targets.txt"
OUTPUT_DIR="pentest-recon-$(date +%Y%m%d)"
mkdir -p "$OUTPUT_DIR"

# Port scan all targets
echo "=== Port Scanning ==="
while IFS= read -r target; do
  echo "Scanning $target..."
  nmap -sV -sC -oA "$OUTPUT_DIR/nmap-$target" "$target" >> "$OUTPUT_DIR/nmap-summary.txt" 2>&1
done < "$TARGETS_FILE"

# Run Nuclei for known vulnerability templates
echo "=== Nuclei Vulnerability Scan ==="
nuclei -l "$TARGETS_FILE" \
  -t cves/ \
  -t exposures/ \
  -t misconfiguration/ \
  -severity critical,high,medium \
  -o "$OUTPUT_DIR/nuclei-findings.txt" \
  -json-export "$OUTPUT_DIR/nuclei-findings.json"

# Check for SSL/TLS issues
echo "=== SSL/TLS Assessment ==="
while IFS= read -r target; do
  testssl --severity HIGH --quiet "$target" >> "$OUTPUT_DIR/testssl-$target.txt" 2>&1
done < "$TARGETS_FILE"

# Summarize findings
echo "=== Summary ==="
echo "Nmap scans: $(ls "$OUTPUT_DIR"/nmap-*.xml 2>/dev/null | wc -l)"
echo "Nuclei findings: $(wc -l < "$OUTPUT_DIR/nuclei-findings.txt" 2>/dev/null || echo 0)"
echo "Results stored in $OUTPUT_DIR/"

OWASP ZAP automated scan for web applications

Run authenticated scans against web targets using ZAP’s API:

import time
from zapv2 import ZAPv2
from typing import List, Dict

class ZAPScanner:
    def __init__(self, zap_proxy: str = "http://127.0.0.1:8080", api_key: str = ""):
        self.zap = ZAPv2(proxies={"http": zap_proxy, "https": zap_proxy}, apikey=api_key)

    def scan_target(self, target_url: str, context_name: str = None) -> Dict:
        """Run a full ZAP scan against a target URL."""
        results = {"target": target_url, "alerts": []}

        # Step 1: Spider the target
        print(f"Spidering {target_url}...")
        scan_id = self.zap.spider.scan(target_url)
        while int(self.zap.spider.status(scan_id)) < 100:
            time.sleep(2)
        print(f"Spider complete. Found {len(self.zap.core.urls())} URLs.")

        # Step 2: Active scan
        print(f"Active scanning {target_url}...")
        ascan_id = self.zap.ascan.scan(target_url)
        while int(self.zap.ascan.status(ascan_id)) < 100:
            time.sleep(5)
        print("Active scan complete.")

        # Step 3: Collect alerts
        alerts = self.zap.core.alerts(baseurl=target_url)
        for alert in alerts:
            results["alerts"].append({
                "name": alert.get("name"),
                "risk": alert.get("risk"),
                "confidence": alert.get("confidence"),
                "url": alert.get("url"),
                "param": alert.get("param"),
                "solution": alert.get("solution"),
            })

        # Summary by risk level
        risk_counts = {}
        for a in results["alerts"]:
            risk = a["risk"]
            risk_counts[risk] = risk_counts.get(risk, 0) + 1

        results["summary"] = risk_counts
        return results

    def generate_report(self, results: List[Dict], output_file: str) -> None:
        """Generate a markdown report from scan results."""
        with open(output_file, "w") as f:
            f.write("# ZAP Automated Scan Report\n\n")
            f.write(f"**Date:** {time.strftime('%Y-%m-%d %H:%M UTC')}\n\n")

            for result in results:
                f.write(f"## {result['target']}\n\n")
                f.write(f"**Alerts by risk:** {result['summary']}\n\n")

                for alert in result["alerts"]:
                    f.write(f"### {alert['name']} ({alert['risk']})\n\n")
                    f.write(f"- **URL:** {alert['url']}\n")
                    f.write(f"- **Confidence:** {alert['confidence']}\n")
                    f.write(f"- **Solution:** {alert['solution']}\n\n")

# Example usage
scanner = ZAPScanner(zap_proxy="http://127.0.0.1:8080")
results = scanner.scan_target("https://app.example.com")
scanner.generate_report([results], "zap-report.md")

Finding tracking with Jira integration

Automatically create tickets for penetration test findings:

from jira import JIRA
from dataclasses import dataclass
from typing import List

@dataclass
class PentestFinding:
    title: str
    severity: str  # Critical, High, Medium, Low
    description: str
    affected_component: str
    evidence: str
    remediation: str
    cvss_score: float

SEVERITY_TO_PRIORITY = {
    "Critical": "Highest",
    "High": "High",
    "Medium": "Medium",
    "Low": "Low",
}

class FindingToJira:
    def __init__(self, jira_url: str, api_token: str, project_key: str):
        self.jira = JIRA(server=jira_url, token_auth=api_token)
        self.project_key = project_key

    def create_tickets(self, findings: List[PentestFinding]) -> None:
        """Create Jira tickets for each penetration test finding."""
        for finding in findings:
            issue_dict = {
                "project": {"key": self.project_key},
                "summary": f"[Pentest] {finding.title} - {finding.affected_component}",
                "description": (
                    f"h3. Description\n{finding.description}\n\n"
                    f"h3. Affected Component\n{finding.affected_component}\n\n"
                    f"h3. Evidence\n{{code}}{finding.evidence}{{code}}\n\n"
                    f"h3. Remediation\n{finding.remediation}\n\n"
                    f"h3. CVSS Score\n{finding.cvss_score}"
                ),
                "issuetype": {"name": "Bug"},
                "priority": {"name": SEVERITY_TO_PRIORITY.get(finding.severity, "Medium")},
                "labels": ["pentest-finding", f"severity-{finding.severity.lower()}"],
            }
            issue = self.jira.create_issue(fields=issue_dict)
            print(f"Created {issue.key}: {finding.title} ({finding.severity})")

# Example usage
findings = [
    PentestFinding(
        title="SQL Injection in login endpoint",
        severity="Critical",
        description="The /api/login endpoint is vulnerable to SQL injection via the username parameter.",
        affected_component="api.example.com /api/login",
        evidence="' OR '1'='1' -- returned all user records",
        remediation="Use parameterized queries and input validation. See OWASP SQL Injection Prevention Cheat Sheet.",
        cvss_score=9.8,
    ),
    PentestFinding(
        title="Missing security headers",
        severity="Low",
        description="X-Frame-Options and Content-Security-Policy headers are not set.",
        affected_component="app.example.com",
        evidence="curl -I https://app.example.com shows missing headers",
        remediation="Add security headers in web server configuration or application middleware.",
        cvss_score=3.1,
    ),
]

jira_client = FindingToJira("https://company.atlassian.net", "api-token", "SEC")
jira_client.create_tickets(findings)

Business Risk Adjustment Matrix

Technical SeverityPublic-FacingAuthenticated Users OnlyInternal Only
CriticalCriticalCriticalHigh
HighHighHighMedium
MediumMediumMediumLow
LowLowLowInformational

2. **Provide testers with a test environment when possible.** Testing staging or a production replica reduces risk while still finding real vulnerabilities:

```bash
#!/bin/bash
# Clone production database to staging for testing
aws rds create-db-snapshot \
  --db-instance-identifier prod-db \
  --db-snapshot-identifier pentest-snapshot-$(date +%Y%m%d)

aws rds restore-db-instance-from-db-snapshot \
  --db-instance-identifier pentest-staging-db \
  --db-snapshot-identifier pentest-snapshot-$(date +%Y%m%d) \
  --db-subnet-group-name staging-subnet \
  --vpc-security-group-ids sg-pentest-access

Post-Test Cleanup Checklist

  • All test accounts removed from target systems
  • Any uploaded files or scripts deleted
  • Persistence mechanisms (cron jobs, services) removed
  • Test data purged from databases
  • SSH keys or credentials created during testing revoked
  • Any modified configurations restored to original state
  • Confirmation of cleanup provided in writing

## Additional FAQs

### How do we scope a penetration test for microservices?

List each microservice endpoint separately in the scope table. Include the API gateway, individual service endpoints, and any service-to-service communication paths. Provide Swagger/OpenAPI specs to testers for detailed coverage. Test both the gateway (external perspective) and individual services (internal perspective). Include authentication and authorization testing for each service's API.

### What is the difference between a vulnerability scan and a penetration test?

A vulnerability scan identifies potential weaknesses using automated tools. A penetration test goes further: a human validates findings, chains vulnerabilities together, attempts lateral movement, and assesses real-world impact. Vulnerability scans are routine and frequent; penetration tests are periodic and deeper. Both are needed for a complete security program.

Frequently Asked Questions

What is a gray box test?
A gray box test provides the tester with some internal knowledge, such as credentials, architecture diagrams, or source code, while still simulating an attacker with limited access.
Can we test production systems?
Production testing is allowed if explicitly included in the scope, during agreed windows, and with rollback plans. Many organizations prefer testing staging first.
What should a report include?
At minimum: executive summary, methodology, scope, risk-rated findings, evidence, impact, remediation steps, and retest results. Include timelines and CVSS scores where applicable.