Testland
Browse all skills & agents

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

  • The repo deploys a web app + needs pre-prod DAST gate.
  • A CI workflow needs a non-intrusive scan that's safe to point at a deployed staging environment.
  • The team uses OWASP ZAP (de facto OSS DAST) over commercial alternatives.
  • Pair with burp-headless for deeper paid-tool coverage on critical flows.

Step 1 - Install

ZAP baseline runs from the official Docker image - no host install needed. Per zap-base:

docker pull ghcr.io/zaproxy/zaproxy:stable

Image variants:

TagUse
:stableProduction-grade; pin in CI
:weeklyLatest features; for evaluation
:bareHeadless 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.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.

Step 3 - Common flags

Per zap-base:

FlagUse
-t URLTarget URL (required)
-r FILEHTML report output
-w FILEMarkdown report
-x FILEXML report
-J FILEJSON report (for dast-finding-triager)
-c FILEConfig file: rule INFO/IGNORE/FAIL behavior
-P PORTSpecify ZAP listen port
-jUse Ajax spider in addition to traditional spider (JS-heavy apps)
-m MINSSpider duration in minutes (default 1)
-dShow debug messages
-IDon't return failure on warning (gate softly)
-n CONTEXT_FILEAuthenticated 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:

  • Auth method (form / script / HTTP / OAuth)
  • Login URL + creds (referenced env vars; never hardcode)
  • Session-management strategy
  • Logged-in / logged-out indicators

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

Step 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.json

zap-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 responses

Three suppression layers:

MechanismExampleWhen 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 XMLWhole URL trees out of scope
-I flagzap-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.json

The 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-patternWhy it failsFix
Run zap-full-scan.py against productionActive payloads pollute / damage dataStaging-only (Step 5)
Skip -J JSON outputCan't feed dast-finding-triager; report is HTML-onlyAlways pass -J (Step 7)
Ignore Ajax spider for SPAsMissing routes; coverage gapAdd -j flag (Step 3)
Hardcode credentials in context fileSecrets leak in repoReference env vars (Step 4)
Suppress without # Re-review-datePermanent debtRequired template (Step 6)

Limitations

  • Baseline is passive only; full-scan needed for active probes but is not safe in production.
  • Spider duration default 1 min may miss deep-routes apps; increase via -m (Step 3).
  • ZAP scans single-target only; for multi-app fleet, run per-app
    • aggregate via dast-finding-triager.
  • Authentication setup via context file is fragile - apps with complex login flows often need custom ZAP scripts.

References