Engagement Setup
The work you do before scanning is what keeps the engagement clean, repeatable, and defensible. Skip it and you end up scanning out-of-scope hosts, losing evidence, or unable to reconstruct what you ran on day 9.
1. Pin down scope and rules of engagement
Get these in writing before anything else. They decide which commands you’re allowed to run.
- In-scope assets — root domains, IP ranges/CIDRs, ASNs, specific URLs.
- Out-of-scope assets — explicit exclusions (shared SaaS, third-party CDNs, partner domains). These become your blacklist (see below).
- Allowed activity — passive only? Active scanning? Exploitation? Brute force? Phishing?
- Rate / timing limits — some clients cap requests-per-second or forbid scanning during business hours.
- Blackout windows & emergency contact — who to call when something falls over, and when not to test.
- Credentials & test accounts — for authenticated testing.
A wildcard like *.example.com means “enumerate and prove the subdomains”; a
bare example.com usually means just that host. Confirm which one the client
means — it changes the size of the engagement.
2. Stand up a tracked workspace
I treat the engagement as a git repo from the first minute. Every scan output, every scope change, and every note ends up version-controlled — it’s both the audit trail and the backup.
mkdir ~/engagements/acme && cd ~/engagements/acme
git init
mkdir -p recon hosts report tmp
printf '/tmp\n' > .gitignore
git add .gitignore && git commit -m "init workspace"
A directory convention that scales (this is basically the layout Arsenic enforces):
acme/
├── scope-domains.txt # in-scope root domains, one per line
├── scope-ips.txt # in-scope IPs / CIDRs
├── recon/ # org-wide recon output (domains, ips, discovery)
├── hosts/<host>/recon/ # per-host scan output
├── report/
│ ├── findings/ # one folder per finding
│ └── static/ # screenshots & evidence
└── tmp/ # scratch (git-ignored)
Commit early and often. A habit I borrowed straight from the Arsenic scripts:
after each meaningful scan, git add the new output and commit with a message
describing what ran. If you’re collaborating, push between steps so teammates
don’t re-scan the same hosts.
3. Define the scope files
The whole pipeline is driven by two seed files. Everything you discover later gets validated back against these plus a blacklist.
# Seed roots — the things you were explicitly told are in scope
printf 'example.com\nexample.net\n' >> scope-domains.txt
printf '203.0.113.0/24\n198.51.100.10\n' >> scope-ips.txt
Keep a blacklist of root domains that show up in results but aren’t yours to test — shared infrastructure that certificate transparency and reverse DNS will constantly surface. Arsenic ships a sensible default; the usual offenders:
1e100.net akamaitechnologies.com amazonaws.com
azurewebsites.net cloudfront.net cloudapp.net
googleusercontent.com readthedocs.io sites.hubspot.net
Every time you generate a new candidate list of domains/IPs, run it through this blacklist before adding it to scope. This one habit prevents the most common engagement mistake: scanning someone else’s CDN.
Ingesting scope from a CSV / bug-bounty program
Real scope rarely arrives as a clean list. For a HackerOne-style CSV, I normalize
it with mlr (Miller) and jq:
curl -s https://hackerone.com/teams/acme/assets/download_csv.csv \
| mlr --icsv --ojson cat | jq | tee acme-scope.json
# Pull eligible, non-wildcard identifiers into the domain scope
jq -r '.[]
| select(.eligible_for_submission == "true")
| select(.max_severity != "none")
| .identifier' acme-scope.json \
| grep -v '\*' \
| sort -u >> scope-domains.txt
Handle wildcard entries (*.acme.com) separately — strip the *. and feed the
parent to subdomain enumeration in the Discovery phase.
4. Build your toolbox
Install the toolchain once and keep it on $PATH. Full install commands are in
the Toolbox Reference; the essentials:
# ProjectDiscovery suite (Go)
go install github.com/projectdiscovery/subfinder/v2/cmd/subfinder@latest
go install github.com/projectdiscovery/dnsx/cmd/dnsx@latest
go install github.com/projectdiscovery/naabu/v2/cmd/naabu@latest
go install github.com/projectdiscovery/httpx/cmd/httpx@latest
go install github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest
# Content discovery & fuzzing
go install github.com/ffuf/ffuf/v2@latest
# feroxbuster, gobuster — package manager or release binaries
# Classics
sudo apt install -y nmap amass exploitdb jq miller # searchsploit ships with exploitdb
# Wordlists
git clone https://github.com/danielmiessler/SecLists /opt/SecLists
Let nmap run unprivileged
Most useful nmap scans need raw sockets. Rather than sudo on every run, grant
the binary the capabilities once:
sudo setcap cap_net_raw,cap_net_admin,cap_net_bind_service+eip "$(command -v nmap)"
With setup done, move on to Discovery to turn your seed scope into a full asset inventory.