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: >4 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".5---67# DAST Security Scan89Two-tier dynamic security scanning: Nuclei for fast template-based scanning (1-5 min), ZAP for deep active scanning via Docker (5-30 min).1011Complements `threat-modeling` (design-time STRIDE analysis) with runtime vulnerability verification.1213## Phase 1: Target Verification1415Before scanning, verify the target is reachable and gather information.16171. **Determine target URL:**18 - If user provided a URL → use it19 - Otherwise → check common localhost ports: `curl -s -o /dev/null -w "%{http_code}" http://localhost:PORT` for ports 3000, 8080, 5173, 4200, 8000, 500020 - If no server found → STOP: "No running server detected. Start your dev server and provide the URL."21222. **Check for OpenAPI/Swagger spec** (enables API-specific scanning):23 - **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`.24 - **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).25 - If found → note the path + content-type for ZAP API scan mode in Tier 2.26273. **Check for authenticated endpoints:**28 - Try a few API paths and look for 401/403 responses29 - Check for OpenAPI security schemes in the spec30 - If auth detected → ask user:3132 ```33 Authenticated endpoints detected. How should I authenticate?34 A) Bearer token — paste your token35 B) Username/password + login endpoint URL36 C) Cookie value — paste the Cookie header37 D) Skip authenticated endpoints38 ```3940 For option B: hit the login endpoint, extract token/cookie from response, use for scanning.41424. Report: "Target: http://localhost:3000 (reachable). OpenAPI spec: found at /openapi.json. Auth: Bearer token configured."4344## Phase 2: Nuclei Scan (Tier 1)4546Fast template-based scanning. Always runs first.47481. **Run Nuclei with smart template selection:**4950```bash51nuclei -u <TARGET_URL> -severity critical,high,medium -as -json-export /tmp/nuclei-results.json52```5354Flags:55- `-severity critical,high,medium` — skip low/info noise56- `-as` — auto-smart: detect tech stack, select relevant templates57- `-json-export` — machine-readable output5859If authenticated, add headers:60```bash61nuclei -u <TARGET_URL> -severity critical,high,medium -as -json-export /tmp/nuclei-results.json -H "Authorization: Bearer <TOKEN>"62```63or:64```bash65nuclei -u <TARGET_URL> -severity critical,high,medium -as -json-export /tmp/nuclei-results.json -H "Cookie: <COOKIE_VALUE>"66```67682. **Parse JSONL output.** Each line is a JSON object with:69 - `template-id`: which template matched (e.g., `cves/2024/CVE-2024-1234`)70 - `info.severity`: critical, high, medium71 - `info.name`: human-readable vulnerability name72 - `info.description`: what the vulnerability is73 - `matched-at`: the URL that triggered the match74 - `matcher-name`: specific matcher within the template75763. **Map findings to source code** where possible:77 - Extract URL paths from `matched-at`78 - Grep for route handlers matching those paths (e.g., `app.post('/api/users/search'` or `@app.route('/api/users/search')`)79 - Present with source file reference when found80814. **Present findings:**8283```84NUCLEI SCAN RESULTS (Tier 1 — template-based, 1-5 min)8586[CRITICAL] Remote Code Execution — GET /api/debug/eval87 Template: nuclei:cves/2024/CVE-2024-XXXX88 Evidence: Response contains command output89 Source: src/routes/debug.ts:12 (route handler for /api/debug/eval)90 Fix: Remove debug endpoint from production, or add authentication + input sanitization9192[HIGH] SQL Injection — POST /api/users/search93 Template: nuclei:vulnerabilities/sqli-error-based94 Evidence: Parameter "query" reflects error: "You have an error in your SQL syntax"95 Source: src/routes/users.ts:45 (route handler for /api/users/search)96 Fix: Use parameterized queries instead of string concatenation9798[MEDIUM] Missing CSRF Token — POST /api/settings/update99 Template: nuclei:vulnerabilities/csrf-detection100 Evidence: No CSRF token in form submission101 Fix: Add CSRF middleware to the route102```103104**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."105106## Phase 3: ZAP Scan (Tier 2, Opt-In)107108Deep scanning with crawling, spidering, and active injection testing. Requires Docker.1091101. **Check Docker availability:**111 ```bash112 command -v docker && docker info113 ```114 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."1151162. **Detect OS for Docker networking:**117 - macOS: use `host.docker.internal` instead of `localhost` in target URL118 - Linux: use `--network host` and `localhost` directly119 ```bash120 if [[ "$(uname)" == "Darwin" ]]; then121 ZAP_TARGET="${TARGET_URL//localhost/host.docker.internal}"122 else123 ZAP_TARGET="$TARGET_URL"124 fi125 ```1261273. **Run ZAP scan:**128129 Passive/baseline scan (user chose "passive only"):130 ```bash131 docker run --rm --network host -v /tmp:/zap/wrk:rw zaproxy/zap-stable zap-baseline.py -t <ZAP_TARGET> -J zap-report.json132 ```133134 Full active scan (user chose "full active scan"):135 ```bash136 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 15137 ```138 `-m 15` limits active scan to 15 minutes.139140 API scan (if OpenAPI spec was found in Phase 1):141 ```bash142 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.json143 ```144145 Exit codes: 0 = clean, 1 = error, 2 = warnings (findings).1461474. **Parse ZAP JSON report.** Key fields:148 - `site[].alerts[]`: array of findings149 - Each alert: `pluginid`, `alert` (name), `riskdesc` (severity), `desc` (description), `solution`, `uri` (affected URLs), `evidence`1501515. **Deduplicate against Nuclei results.** Match by:152 - Affected URL path153 - Vulnerability category (XSS, SQLi, CSRF, etc.)154 - Skip ZAP findings already reported by Nuclei1551566. **Present ZAP-only findings:**157158```159ZAP SCAN RESULTS (Tier 2 — active scanning, new findings only)160161[HIGH] Cross-Site Scripting (Reflected) — GET /search?q=<script>162 Alert: 40012163 Evidence: Response contains unescaped user input in HTML context164 Source: src/routes/search.ts:23 (search query handler)165 Fix: Escape HTML output using framework's built-in XSS protection166167[MEDIUM] Cookie Without Secure Flag168 Alert: 10011169 Evidence: Set-Cookie header missing Secure attribute170 Fix: Set secure: true in cookie configuration171```172173## Phase 4: Summary174175Present a consolidated summary:176177```178SCAN SUMMARY179180Target: http://localhost:3000181Scans: Nuclei (Tier 1) + ZAP baseline (Tier 2)182183Findings by severity:184 Critical: 1185 High: 3186 Medium: 5187 Total: 9188189Recommended fix priority:190 1. [CRITICAL] Remote Code Execution — remove debug endpoint191 2. [HIGH] SQL Injection — parameterize queries in users route192 3. [HIGH] XSS — escape output in search route193 ...194195Next steps:196 - Fix critical and high findings first197 - Re-run scan after fixes to verify remediation198 - Consider threat-modeling skill for design-level security analysis199```200201## Integration with Other Skills202203- **After `threat-modeling`**: Run DAST to validate that identified threats are mitigated204- **Before `security-review`**: DAST findings focus the code review on concrete vulnerabilities205- **With `find-bugs`**: DAST covers runtime issues that static analysis misses206207## Limitations208209- Active scanning (ZAP full scan) can modify data — always warn before running210- Cannot test business logic flaws (IDOR, broken access control) — use `threat-modeling` for those211- Nuclei is template-based — may miss novel vulnerability patterns212- ZAP active scans can be slow (10-30 min) on large applications213- Docker required for ZAP tier; Nuclei works without Docker214- macOS Docker networking requires `host.docker.internal` workaround