From qa-dast
Configures and runs OWASP ZAP baseline scanning - `zap-baseline.py` Docker-packaged spider + passive scan suitable for CI gating; supports `-t target_url` + `-r html_report` + `-c config_file` rule customization (INFO/IGNORE/FAIL warnings); pairs with `zap-full-scan.py` for active scanning; supports authenticated scans via `-n context_file` and Ajax spider via `-j` for JS-heavy SPAs. Use when the user runs OWASP ZAP for pre-prod web app DAST.
How this skill is triggered — by the user, by Claude, or both
Slash command
/qa-dast:zap-baselineThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Per [zaproxy.org/docs/docker/baseline-scan/][zap-base]:
Per zaproxy.org/docs/docker/baseline-scan/:
"The ZAP Baseline scan is a script that is available in the ZAP Docker images. It runs the ZAP spider against the specified target for (by default) 1 minute and then waits for the passive scanning to complete before reporting the results."
The baseline scan is passive only - non-intrusive, safe for
production. The companion zap-full-scan.py adds active scanning
(injection probes, XSS payloads) and is NOT safe for
production; reserve for staging.
burp-headless for
deeper paid-tool coverage on critical flows.ZAP baseline runs from the official Docker image - no host install needed. Per zap-base:
docker pull ghcr.io/zaproxy/zaproxy:stable
Image variants:
| Tag | Use |
|---|---|
:stable | Production-grade; pin in CI |
:weekly | Latest features; for evaluation |
:bare | Headless minimal; smallest |
Per zap-base (verbatim):
docker run -v $(pwd):/zap/wrk/:rw -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
-t https://www.example.com -r testreport.html
The -v $(pwd):/zap/wrk/:rw mount makes the report writable to the
host directory. The report file lands at ./testreport.html after
the scan.
Per zap-base:
| Flag | Use |
|---|---|
-t URL | Target URL (required) |
-r FILE | HTML report output |
-w FILE | Markdown report |
-x FILE | XML report |
-J FILE | JSON report (for dast-finding-triager) |
-c FILE | Config file: rule INFO/IGNORE/FAIL behavior |
-P PORT | Specify ZAP listen port |
-j | Use Ajax spider in addition to traditional spider (JS-heavy apps) |
-m MINS | Spider duration in minutes (default 1) |
-d | Show debug messages |
-I | Don't return failure on warning (gate softly) |
-n CONTEXT_FILE | Authenticated scan context file |
For apps requiring login, export a ZAP context file from the GUI (Sites → right-click → Export Context). The context file encodes:
Then:
docker run -v $(pwd):/zap/wrk/:rw \
-e ZAP_AUTH_USERNAME=$USER \
-e ZAP_AUTH_PASSWORD=$PASS \
-t ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t https://app.example.com -n /zap/wrk/context.xml -J report.json
zap-full-scan.py)For staging-only deeper analysis:
docker run -v $(pwd):/zap/wrk/:rw -t ghcr.io/zaproxy/zaproxy:stable \
zap-full-scan.py -t https://staging.example.com -J report.json
zap-full-scan.py triggers active payloads (SQLi probes, XSS,
SSRF). NEVER point at production - risk of data corruption +
generates audit-log noise.
Per zap-base, rule customization via -c config_file
where each line is <rule_id>\t<INFO|WARN|IGNORE|FAIL>\t<URL_pattern>:
Example zap-config.tsv:
10049 IGNORE * # Cookie No HttpOnly: legacy session cookie; tracked in JIRA-1234
40012 WARN https://app.example.com/admin/* # XSS: legacy admin pages; not exposed to users
10063 FAIL * # Permissions Policy: must be set on all responses
Three suppression layers:
| Mechanism | Example | When to use |
|---|---|---|
| Per-rule config TSV | <rule_id>\tIGNORE\t* | Rule disabled globally |
| Per-URL pattern | <rule_id>\tWARN\thttps://app/admin/* | Rule downgraded for known-old code path |
| Context exclusion | <exclude_from_context> in context XML | Whole URL trees out of scope |
-I flag | zap-baseline.py -I ... | Soft gate: warns but exits 0 |
Justification template (mandatory in config):
# Rule 10049 (Cookie No HttpOnly)
# Suppressed: 2026-05-15 by [email protected]
# Reason: legacy session cookie; tracked in JIRA-1234; expires 2026-09-15
# Re-review-date: 2026-09-15
10049 IGNORE *
Cadence: every quarter, audit the config TSV - every IGNORE entry should have an Re-review-date. Past-due entries are removed + re-evaluated.
dast-finding-triagerdocker run -v $(pwd):/zap/wrk/:rw -t ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t https://app.example.com -J zap-report.json
The JSON report feeds dast-finding-triager.
For SARIF output (GitHub Code Scanning), use a converter (zap-sarif
container action).
GitHub Actions:
jobs:
zap-baseline:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: zaproxy/[email protected]
with:
target: 'https://staging.example.com'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-J zap-report.json'
- uses: actions/upload-artifact@v4
if: always()
with: { name: zap-report, path: zap-report.json }
The official zaproxy/action-baseline action wraps the Docker
invocation. Auto-creates a GitHub Issue on failure.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Run zap-full-scan.py against production | Active payloads pollute / damage data | Staging-only (Step 5) |
Skip -J JSON output | Can't feed dast-finding-triager; report is HTML-only | Always pass -J (Step 7) |
| Ignore Ajax spider for SPAs | Missing routes; coverage gap | Add -j flag (Step 3) |
| Hardcode credentials in context file | Secrets leak in repo | Reference env vars (Step 4) |
Suppress without # Re-review-date | Permanent debt | Required template (Step 6) |
-m (Step 3).dast-finding-triager.burp-headless,
nightvision-dast - sister DAST toolsdast-baseline-runner -
build-an-X for layered DAST (baseline → full → optional Burp deep)dast-finding-triager -
unifier agentnpx claudepluginhub testland/qa --plugin qa-dastProvides CDSS development patterns for drug interaction checking, dose validation, clinical scoring (NEWS2, qSOFA), and alert classification integrated into EMR workflows.