zap-baseline
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) and Ajax spider via `-j` for JS-heavy SPAs. Passive-only; for active injection probes use `zap-full-scan.py` via zap-authenticated-scans. Accepts `-n context_file` for pre-configured auth contexts (see zap-authenticated-scans for setting up auth from scratch). Use when the user runs OWASP ZAP for pre-prod web app DAST.
zap-baseline
Overview
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.
When to use
Step 1 - Install
ZAP baseline runs from the official Docker image - no host install needed. Per zap-base:
docker pull ghcr.io/zaproxy/zaproxy:stableImage variants:
| Tag | Use |
|---|---|
:stable | Production-grade; pin in CI |
:weekly | Latest features; for evaluation |
:bare | Headless minimal; smallest |
Step 2 - First baseline scan
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.htmlThe -v $(pwd):/zap/wrk/:rw mount makes the report writable to the host directory. The report file lands at ./testreport.html after the scan.
Step 3 - Common flags
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 |
Step 4 - Authenticated scans
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.jsonStep 5 - Active scan companion (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.jsonzap-full-scan.py triggers active payloads (SQLi probes, XSS, SSRF). NEVER point at production - risk of data corruption + generates audit-log noise.
Step 6 - False-positive triage (MANDATORY)
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 responsesThree 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 alice@example.com
# 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.
Step 7 - Output formats for dast-finding-triager
docker run -v $(pwd):/zap/wrk/:rw -t ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t https://app.example.com -J zap-report.jsonThe JSON report feeds dast-finding-triager. For SARIF output (GitHub Code Scanning), use a converter (zap-sarif container action).
Step 8 - CI integration
GitHub Actions:
jobs:
zap-baseline:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: zaproxy/action-baseline@v0.13.0
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-patterns
| 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) |