DAST Security Scan
Two-tier dynamic security scanning: Nuclei for fast template-based scanning (1-5 min), ZAP for deep active scanning via Docker (5-30 min).
Complements threat-modeling (design-time STRIDE analysis) with runtime vulnerability verification.
Phase 1: Target Verification
Before scanning, verify the target is reachable and gather information.
Determine target URL:
- If user provided a URL → use it
- Otherwise → check common localhost ports:
curl -s -o /dev/null -w "%{http_code}" http://localhost:PORT for ports 3000, 8080, 5173, 4200, 8000, 5000
- If no server found → STOP: "No running server detected. Start your dev server and provide the URL."
Check for OpenAPI/Swagger spec (enables API-specific scanning):
- Step 1 — infer likely paths from context. Detect the framework from response headers (
Server, X-Powered-By), HTML at /, or repo files (package.json, pyproject.toml, go.mod, Cargo.toml). Enumerate the paths conventional for that framework. Examples of current conventions to draw from (NOT an exhaustive list — frameworks add new ones over time, so supplement with anything you know for the detected stack): FastAPI (/openapi.json, /docs/openapi.json, /redoc), Swagger UI (/swagger.json, /api-docs, /swagger/v1/swagger.json), NestJS (/api, /api-json), Express + swagger-ui-express (/api-docs, /api-docs.json), Spring (/v3/api-docs, /v2/api-docs), Django REST Framework (/openapi, /schema), Laravel + L5-Swagger (/api/documentation, /docs), ASP.NET (/swagger/v1/swagger.json), Stoplight/Redocly/Scalar UIs (/openapi.yaml, /openapi.yml). If the framework is unknown, start with the generic defaults: /openapi.json, /swagger.json, /api-docs, /docs/openapi.json, /api/openapi.json.
- Step 2 — probe in order. Test each candidate with
curl -s -o /dev/null -w "%{http_code}". Stop at first HTTP 200. Cap at 10 probe attempts total — if none hit by then, report "no OpenAPI spec found at conventional paths" and move on (do NOT crawl the app to find one).
- If found → note the path + content-type for ZAP API scan mode in Tier 2.
Check for authenticated endpoints:
- Try a few API paths and look for 401/403 responses
- Check for OpenAPI security schemes in the spec
- If auth detected → ask user:
Authenticated endpoints detected. How should I authenticate?
A) Bearer token — paste your token
B) Username/password + login endpoint URL
C) Cookie value — paste the Cookie header
D) Skip authenticated endpoints
For option B: hit the login endpoint, extract token/cookie from response, use for scanning.
Report: "Target: http://localhost:3000 (reachable). OpenAPI spec: found at /openapi.json. Auth: Bearer token configured."
Phase 2: Nuclei Scan (Tier 1)
Fast template-based scanning. Always runs first.
- Run Nuclei with smart template selection:
nuclei -u <TARGET_URL> -severity critical,high,medium -as -json-export /tmp/nuclei-results.json
Flags:
-severity critical,high,medium — skip low/info noise
-as — auto-smart: detect tech stack, select relevant templates
-json-export — machine-readable output
If authenticated, add headers:
nuclei -u <TARGET_URL> -severity critical,high,medium -as -json-export /tmp/nuclei-results.json -H "Authorization: Bearer <TOKEN>"
or:
nuclei -u <TARGET_URL> -severity critical,high,medium -as -json-export /tmp/nuclei-results.json -H "Cookie: <COOKIE_VALUE>"
Parse JSONL output. Each line is a JSON object with:
template-id: which template matched (e.g., cves/2024/CVE-2024-1234)
info.severity: critical, high, medium
info.name: human-readable vulnerability name
info.description: what the vulnerability is
matched-at: the URL that triggered the match
matcher-name: specific matcher within the template
Map findings to source code where possible:
- Extract URL paths from
matched-at
- Grep for route handlers matching those paths (e.g.,
app.post('/api/users/search' or @app.route('/api/users/search'))
- Present with source file reference when found
Present findings:
NUCLEI SCAN RESULTS (Tier 1 — template-based, 1-5 min)
[CRITICAL] Remote Code Execution — GET /api/debug/eval
Template: nuclei:cves/2024/CVE-2024-XXXX
Evidence: Response contains command output
Source: src/routes/debug.ts:12 (route handler for /api/debug/eval)
Fix: Remove debug endpoint from production, or add authentication + input sanitization
[HIGH] SQL Injection — POST /api/users/search
Template: nuclei:vulnerabilities/sqli-error-based
Evidence: Parameter "query" reflects error: "You have an error in your SQL syntax"
Source: src/routes/users.ts:45 (route handler for /api/users/search)
Fix: Use parameterized queries instead of string concatenation
[MEDIUM] Missing CSRF Token — POST /api/settings/update
Template: nuclei:vulnerabilities/csrf-detection
Evidence: No CSRF token in form submission
Fix: Add CSRF middleware to the route
STOP — Present Nuclei findings. Ask: "Found N vulnerabilities. Want a deeper scan with ZAP? Options: passive only (5 min) or full active scan (15-30 min). Note: active scanning may modify data — only run against ephemeral/dev environments."
Phase 3: ZAP Scan (Tier 2, Opt-In)
Deep scanning with crawling, spidering, and active injection testing. Requires Docker.
Check Docker availability:
command -v docker && docker info
If Docker is not available → STOP: "Docker is required for ZAP scanning. Install Docker (https://docs.docker.com/get-docker/) or run tools/dast.sh to set up. Nuclei results above are still valid."
Detect OS for Docker networking:
- macOS: use
host.docker.internal instead of localhost in target URL
- Linux: use
--network host and localhost directly
if [[ "$(uname)" == "Darwin" ]]; then
ZAP_TARGET="${TARGET_URL//localhost/host.docker.internal}"
else
ZAP_TARGET="$TARGET_URL"
fi
Run ZAP scan:
Passive/baseline scan (user chose "passive only"):
docker run --rm --network host -v /tmp:/zap/wrk:rw zaproxy/zap-stable zap-baseline.py -t <ZAP_TARGET> -J zap-report.json
Full active scan (user chose "full active scan"):
docker run --rm --network host -v /tmp:/zap/wrk:rw zaproxy/zap-stable zap-full-scan.py -t <ZAP_TARGET> -J zap-report.json -m 15
-m 15 limits active scan to 15 minutes.
API scan (if OpenAPI spec was found in Phase 1):
docker run --rm --network host -v /tmp:/zap/wrk:rw zaproxy/zap-stable zap-api-scan.py -t <ZAP_TARGET>/openapi.json -f openapi -J zap-report.json
Exit codes: 0 = clean, 1 = error, 2 = warnings (findings).
Parse ZAP JSON report. Key fields:
site[].alerts[]: array of findings
- Each alert:
pluginid, alert (name), riskdesc (severity), desc (description), solution, uri (affected URLs), evidence
Deduplicate against Nuclei results. Match by:
- Affected URL path
- Vulnerability category (XSS, SQLi, CSRF, etc.)
- Skip ZAP findings already reported by Nuclei
Present ZAP-only findings:
ZAP SCAN RESULTS (Tier 2 — active scanning, new findings only)
[HIGH] Cross-Site Scripting (Reflected) — GET /search?q=<script>
Alert: 40012
Evidence: Response contains unescaped user input in HTML context
Source: src/routes/search.ts:23 (search query handler)
Fix: Escape HTML output using framework's built-in XSS protection
[MEDIUM] Cookie Without Secure Flag
Alert: 10011
Evidence: Set-Cookie header missing Secure attribute
Fix: Set secure: true in cookie configuration
Phase 4: Summary
Present a consolidated summary:
SCAN SUMMARY
Target: http://localhost:3000
Scans: Nuclei (Tier 1) + ZAP baseline (Tier 2)
Findings by severity:
Critical: 1
High: 3
Medium: 5
Total: 9
Recommended fix priority:
1. [CRITICAL] Remote Code Execution — remove debug endpoint
2. [HIGH] SQL Injection — parameterize queries in users route
3. [HIGH] XSS — escape output in search route
...
Next steps:
- Fix critical and high findings first
- Re-run scan after fixes to verify remediation
- Consider threat-modeling skill for design-level security analysis
Integration with Other Skills
- After
threat-modeling: Run DAST to validate that identified threats are mitigated
- Before
security-review: DAST findings focus the code review on concrete vulnerabilities
- With
find-bugs: DAST covers runtime issues that static analysis misses
Limitations
- Active scanning (ZAP full scan) can modify data — always warn before running
- Cannot test business logic flaws (IDOR, broken access control) — use
threat-modeling for those
- Nuclei is template-based — may miss novel vulnerability patterns
- ZAP active scans can be slow (10-30 min) on large applications
- Docker required for ZAP tier; Nuclei works without Docker
- macOS Docker networking requires
host.docker.internal workaround
1---2name: dast-scan3description: Dynamic Application Security Testing with two tiers: Nuclei (fast, template-based) and ZAP (deep, active scanning via Docker). Use when asked to scan for vulnerabilities, run a security scan, DAST scan, pen test the app, check for XSS/SQLi/CSRF, scan for security issues on a running app, or after deploying a local dev server. Complements threat-modeling (design-time) with runtime verification. Triggers on: vulnerability scan, security scan, DAST, pen test, penetration test, XSS, SQL injection, CSRF, "is this secure" (for running apps), "scan my app", "check for vulnerabilities", "find security issues".4---56# DAST Security Scan78Two-tier dynamic security scanning: Nuclei for fast template-based scanning (1-5 min), ZAP for deep active scanning via Docker (5-30 min).910Complements `threat-modeling` (design-time STRIDE analysis) with runtime vulnerability verification.1112## Phase 1: Target Verification1314Before scanning, verify the target is reachable and gather information.15161. **Determine target URL:**17 - If user provided a URL → use it18 - Otherwise → check common localhost ports: `curl -s -o /dev/null -w "%{http_code}" http://localhost:PORT` for ports 3000, 8080, 5173, 4200, 8000, 500019 - If no server found → STOP: "No running server detected. Start your dev server and provide the URL."20212. **Check for OpenAPI/Swagger spec** (enables API-specific scanning):22 - **Step 1 — infer likely paths from context.** Detect the framework from response headers (`Server`, `X-Powered-By`), HTML at `/`, or repo files (`package.json`, `pyproject.toml`, `go.mod`, `Cargo.toml`). Enumerate the paths conventional for that framework. Examples of current conventions to draw from (NOT an exhaustive list — frameworks add new ones over time, so supplement with anything you know for the detected stack): FastAPI (`/openapi.json`, `/docs/openapi.json`, `/redoc`), Swagger UI (`/swagger.json`, `/api-docs`, `/swagger/v1/swagger.json`), NestJS (`/api`, `/api-json`), Express + swagger-ui-express (`/api-docs`, `/api-docs.json`), Spring (`/v3/api-docs`, `/v2/api-docs`), Django REST Framework (`/openapi`, `/schema`), Laravel + L5-Swagger (`/api/documentation`, `/docs`), ASP.NET (`/swagger/v1/swagger.json`), Stoplight/Redocly/Scalar UIs (`/openapi.yaml`, `/openapi.yml`). If the framework is unknown, start with the generic defaults: `/openapi.json`, `/swagger.json`, `/api-docs`, `/docs/openapi.json`, `/api/openapi.json`.23 - **Step 2 — probe in order.** Test each candidate with `curl -s -o /dev/null -w "%{http_code}"`. Stop at first HTTP 200. Cap at 10 probe attempts total — if none hit by then, report "no OpenAPI spec found at conventional paths" and move on (do NOT crawl the app to find one).24 - If found → note the path + content-type for ZAP API scan mode in Tier 2.25263. **Check for authenticated endpoints:**27 - Try a few API paths and look for 401/403 responses28 - Check for OpenAPI security schemes in the spec29 - If auth detected → ask user:3031 ```32 Authenticated endpoints detected. How should I authenticate?33 A) Bearer token — paste your token34 B) Username/password + login endpoint URL35 C) Cookie value — paste the Cookie header36 D) Skip authenticated endpoints37 ```3839 For option B: hit the login endpoint, extract token/cookie from response, use for scanning.40414. Report: "Target: http://localhost:3000 (reachable). OpenAPI spec: found at /openapi.json. Auth: Bearer token configured."4243## Phase 2: Nuclei Scan (Tier 1)4445Fast template-based scanning. Always runs first.46471. **Run Nuclei with smart template selection:**4849```bash50nuclei -u <TARGET_URL> -severity critical,high,medium -as -json-export /tmp/nuclei-results.json51```5253Flags:54- `-severity critical,high,medium` — skip low/info noise55- `-as` — auto-smart: detect tech stack, select relevant templates56- `-json-export` — machine-readable output5758If authenticated, add headers:59```bash60nuclei -u <TARGET_URL> -severity critical,high,medium -as -json-export /tmp/nuclei-results.json -H "Authorization: Bearer <TOKEN>"61```62or:63```bash64nuclei -u <TARGET_URL> -severity critical,high,medium -as -json-export /tmp/nuclei-results.json -H "Cookie: <COOKIE_VALUE>"65```66672. **Parse JSONL output.** Each line is a JSON object with:68 - `template-id`: which template matched (e.g., `cves/2024/CVE-2024-1234`)69 - `info.severity`: critical, high, medium70 - `info.name`: human-readable vulnerability name71 - `info.description`: what the vulnerability is72 - `matched-at`: the URL that triggered the match73 - `matcher-name`: specific matcher within the template74753. **Map findings to source code** where possible:76 - Extract URL paths from `matched-at`77 - Grep for route handlers matching those paths (e.g., `app.post('/api/users/search'` or `@app.route('/api/users/search')`)78 - Present with source file reference when found79804. **Present findings:**8182```83NUCLEI SCAN RESULTS (Tier 1 — template-based, 1-5 min)8485[CRITICAL] Remote Code Execution — GET /api/debug/eval86 Template: nuclei:cves/2024/CVE-2024-XXXX87 Evidence: Response contains command output88 Source: src/routes/debug.ts:12 (route handler for /api/debug/eval)89 Fix: Remove debug endpoint from production, or add authentication + input sanitization9091[HIGH] SQL Injection — POST /api/users/search92 Template: nuclei:vulnerabilities/sqli-error-based93 Evidence: Parameter "query" reflects error: "You have an error in your SQL syntax"94 Source: src/routes/users.ts:45 (route handler for /api/users/search)95 Fix: Use parameterized queries instead of string concatenation9697[MEDIUM] Missing CSRF Token — POST /api/settings/update98 Template: nuclei:vulnerabilities/csrf-detection99 Evidence: No CSRF token in form submission100 Fix: Add CSRF middleware to the route101```102103**STOP** — Present Nuclei findings. Ask: "Found N vulnerabilities. Want a deeper scan with ZAP? Options: passive only (~5 min) or full active scan (~15-30 min). Note: active scanning may modify data — only run against ephemeral/dev environments."104105## Phase 3: ZAP Scan (Tier 2, Opt-In)106107Deep scanning with crawling, spidering, and active injection testing. Requires Docker.1081091. **Check Docker availability:**110 ```bash111 command -v docker && docker info112 ```113 If Docker is not available → STOP: "Docker is required for ZAP scanning. Install Docker (https://docs.docker.com/get-docker/) or run `tools/dast.sh` to set up. Nuclei results above are still valid."1141152. **Detect OS for Docker networking:**116 - macOS: use `host.docker.internal` instead of `localhost` in target URL117 - Linux: use `--network host` and `localhost` directly118 ```bash119 if [[ "$(uname)" == "Darwin" ]]; then120 ZAP_TARGET="${TARGET_URL//localhost/host.docker.internal}"121 else122 ZAP_TARGET="$TARGET_URL"123 fi124 ```1251263. **Run ZAP scan:**127128 Passive/baseline scan (user chose "passive only"):129 ```bash130 docker run --rm --network host -v /tmp:/zap/wrk:rw zaproxy/zap-stable zap-baseline.py -t <ZAP_TARGET> -J zap-report.json131 ```132133 Full active scan (user chose "full active scan"):134 ```bash135 docker run --rm --network host -v /tmp:/zap/wrk:rw zaproxy/zap-stable zap-full-scan.py -t <ZAP_TARGET> -J zap-report.json -m 15136 ```137 `-m 15` limits active scan to 15 minutes.138139 API scan (if OpenAPI spec was found in Phase 1):140 ```bash141 docker run --rm --network host -v /tmp:/zap/wrk:rw zaproxy/zap-stable zap-api-scan.py -t <ZAP_TARGET>/openapi.json -f openapi -J zap-report.json142 ```143144 Exit codes: 0 = clean, 1 = error, 2 = warnings (findings).1451464. **Parse ZAP JSON report.** Key fields:147 - `site[].alerts[]`: array of findings148 - Each alert: `pluginid`, `alert` (name), `riskdesc` (severity), `desc` (description), `solution`, `uri` (affected URLs), `evidence`1491505. **Deduplicate against Nuclei results.** Match by:151 - Affected URL path152 - Vulnerability category (XSS, SQLi, CSRF, etc.)153 - Skip ZAP findings already reported by Nuclei1541556. **Present ZAP-only findings:**156157```158ZAP SCAN RESULTS (Tier 2 — active scanning, new findings only)159160[HIGH] Cross-Site Scripting (Reflected) — GET /search?q=<script>161 Alert: 40012162 Evidence: Response contains unescaped user input in HTML context163 Source: src/routes/search.ts:23 (search query handler)164 Fix: Escape HTML output using framework's built-in XSS protection165166[MEDIUM] Cookie Without Secure Flag167 Alert: 10011168 Evidence: Set-Cookie header missing Secure attribute169 Fix: Set secure: true in cookie configuration170```171172## Phase 4: Summary173174Present a consolidated summary:175176```177SCAN SUMMARY178179Target: http://localhost:3000180Scans: Nuclei (Tier 1) + ZAP baseline (Tier 2)181182Findings by severity:183 Critical: 1184 High: 3185 Medium: 5186 Total: 9187188Recommended fix priority:189 1. [CRITICAL] Remote Code Execution — remove debug endpoint190 2. [HIGH] SQL Injection — parameterize queries in users route191 3. [HIGH] XSS — escape output in search route192 ...193194Next steps:195 - Fix critical and high findings first196 - Re-run scan after fixes to verify remediation197 - Consider threat-modeling skill for design-level security analysis198```199200## Integration with Other Skills201202- **After `threat-modeling`**: Run DAST to validate that identified threats are mitigated203- **Before `security-review`**: DAST findings focus the code review on concrete vulnerabilities204- **With `find-bugs`**: DAST covers runtime issues that static analysis misses205206## Limitations207208- Active scanning (ZAP full scan) can modify data — always warn before running209- Cannot test business logic flaws (IDOR, broken access control) — use `threat-modeling` for those210- Nuclei is template-based — may miss novel vulnerability patterns211- ZAP active scans can be slow (10-30 min) on large applications212- Docker required for ZAP tier; Nuclei works without Docker213- macOS Docker networking requires `host.docker.internal` workaround