name: python-pentest description: Python best practices for pentest scripts — requests sessions, error handling, output, CLI args user-invocable: false
Python Best Practices for Pentest Scripts
Follow these conventions when generating or reviewing Python test scripts in this project.
HTTP Requests
Use requests.Session as a context manager for connection pooling and consistent config:
import requests
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
with requests.Session() as session:
session.proxies = {"http": proxy, "https": proxy}
session.verify = False # Required for Burp interception
session.headers.update({"Authorization": f"Bearer {token}"})
resp = session.get(url)
Retry on transient failures
Mount a retry adapter for robustness against flaky targets:
from urllib3.util import Retry
from requests.adapters import HTTPAdapter
retries = Retry(total=3, backoff_factor=0.3, status_forcelist=[502, 503, 504])
session.mount("https://", HTTPAdapter(max_retries=retries))
session.mount("http://", HTTPAdapter(max_retries=retries))
Timeouts
Always set timeouts. Never let a request hang indefinitely:
resp = session.get(url, timeout=(5, 30)) # (connect, read) in seconds
Error handling
Catch specific exceptions, not bare except. Log and continue — pentest scripts should be resilient:
from requests.exceptions import ConnectionError, Timeout, HTTPError
try:
resp = session.get(url, timeout=(5, 30))
resp.raise_for_status()
except Timeout:
print(f"[TIMEOUT] {url}")
except ConnectionError:
print(f"[CONN_ERR] {url}")
except HTTPError as e:
print(f"[HTTP {e.response.status_code}] {url}")
CLI Arguments
Use argparse (not Click) in generated standalone scripts — they must run independently without project dependencies:
import argparse
parser = argparse.ArgumentParser(description="IDOR test for /api/users/{id}")
parser.add_argument("--base-url", required=True, help="Target base URL")
parser.add_argument("--auth-token", required=True, help="Bearer token")
parser.add_argument("--proxy", default="http://127.0.0.1:8080", help="Proxy URL (default: Burp)")
parser.add_argument("--timeout", type=int, default=30, help="Request timeout in seconds")
parser.add_argument("--delay", type=float, default=0, help="Delay between requests in seconds")
args = parser.parse_args()
Required args for every script: --base-url, --auth-token, --proxy.
File Output
Always write results to scripts/output/results/ as JSON. Use pathlib for paths:
from pathlib import Path
from datetime import datetime
import json
RESULTS_DIR = Path(__file__).resolve().parent / "results"
def save_results(results: list[dict], prefix: str) -> Path:
RESULTS_DIR.mkdir(parents=True, exist_ok=True)
outfile = RESULTS_DIR / f"{prefix}_{datetime.now():%Y%m%d_%H%M%S}.json"
outfile.write_text(json.dumps(results, indent=2))
return outfile
Script Structure
Every generated script should follow this order:
- Shebang + docstring
- Imports (stdlib, then third-party)
- Constants (
RESULTS_DIR) - Helper functions
main()with argparseif __name__ == "__main__": main()
Type Hints
Use type hints for function signatures. Use modern syntax (list[dict] not List[Dict]):
def test_endpoint(session: requests.Session, url: str, ids: range) -> list[dict]:
...
Logging vs Print
Use print() with status prefixes for real-time progress in pentest scripts. Structured logging is overkill for standalone test scripts:
print(f"[+] Found: {url} returned {resp.status_code}")
print(f"[-] Failed: {url} — {e}")
print(f"[*] Testing ID range {start}-{end}")
print(f"[!] Possible vulnerability: {url}")
Security Rules
- Never hardcode secrets — tokens, URLs, and passwords come from CLI args only
- Never import from the project — generated scripts must be fully standalone
verify=Falseis intentional — required for Burp proxy interception, suppress the warning once at the top- Default proxy to Burp —
http://127.0.0.1:8080 - Respect rate limits — support
--delayarg for throttling