how to run an OWASP ZAP scan in CI
Run OWASP ZAP dynamic security scans inside CI with the official zaproxy GitHub Action or the zaproxy Docker image. Use when adding DAST to a pipeline: the passive baseline scan on every pull request, the active full scan on a schedule against staging, or the API scan driven by an OpenAPI spec. Not for SAST tools like gosec or semgrep, not for one-off local scans, and not for scanning production.
tl;dr
run the passive baseline scan on every pull request and the active full scan on a schedule against staging. the fastest working setup is the official action in a github workflow: zaproxy/action-baseline pointed at a running build of your app. baseline scans are passive and safe for every PR; full scans send attack payloads, so they only ever run against a staging target.
how to run an OWASP ZAP scan in CIthat query comes from an agent whose vectle search returned weak results. this skill is the missing answer: a concrete CI setup for ZAP with the exact scan types, failure behavior, and report wiring.
steps
- start your app so ZAP has a live target. ZAP is a dynamic scanner, it only works against a running server. in CI that usually means starting the app in a service step first, then waiting for its health endpoint. expected output: your health endpoint answers 200 before the scan step begins.
- add the baseline scan to the workflow. this runs on every pull request. it spiders the app and does passive analysis, no attack payloads.
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.12.0
with:
target: '[app-url]'
rules_file_name: '.zap/rules.tsv'
fail_action: trueexpected output: the action writes report_html.html, report_json.json, and report_sarif.sarif into the workspace. INFO and WARN alerts do not fail the build by default; with fail_action: true any alert at your configured threshold fails it.
- control pass/fail with a rules file. create
.zap/rules.tsvto decide which alert IDs fail the build and which are ignored:
# format: alert-id, action, optional note
10021 IGNORE # X-Content-Type-Options handled by the CDN
10038 WARN # CSP header missing
10054 FAIL # cookie SameSite missing
40018 FAIL # SQL injection
40012 FAIL # cross site scriptingexpected output: only alerts marked FAIL break the build. tune this file as false positives appear, otherwise CI becomes a spam gate and people start ignoring it.
- run the full scan on a schedule, not on PRs. the full scan adds the ajax spider and active attacks, which take much longer and can send destructive payloads. run it weekly against staging:
- name: ZAP Full Scan
uses: zaproxy/action-full-scan@v0.10.0
with:
target: '[staging-url]'
rules_file_name: '.zap/rules.tsv'expected output: a longer run (tens of minutes on a large app) with active-attack findings. schedule it with a cron trigger rather than on every push.
- for APIs, scan from the OpenAPI spec instead of spidering. use the api scan script with your spec file:
docker run --rm -v [workdir]:/zap/wrk:rw ghcr.io/zaproxy/zaproxy:stable \
zap-api-scan.py -t [openapi-spec-url] -f openapi -r zap-api-report.htmlexpected output: every endpoint in the spec gets probed, without relying on the spider to discover them.
- upload the reports as build artifacts. add an upload step that always runs, even when the scan fails, so the evidence is inspectable:
- name: Upload ZAP Report
if: always()
uses: actions/upload-artifact@v4
with:
name: zap-report
path: report_html.htmlexpected output: a downloadable zap-report artifact on every run, pass or fail.
when to use this skill
- adding dynamic application security testing (DAST) to a CI pipeline
- choosing between the ZAP baseline, full, and api scans
- making ZAP break the build on real findings without failing on noise
- scanning a REST API from an OpenAPI spec in CI
when not to use it
- static analysis of source code: that is SAST (gosec, semgrep, codeql), not ZAP
- one-off local scans from your laptop: run ZAP desktop or the docker image directly, CI wiring is overkill
- scanning production with the full scan: active attacks can corrupt data, use staging
- testing endpoints behind login: ZAP needs auth configured first, this skill covers the CI wiring only
tool and version compatibility
- OWASP ZAP 2.17 or newer. official docker image:
ghcr.io/zaproxy/zaproxy:stable(weekly tags also exist) zaproxy/action-baselinev0.12 or newer,zaproxy/action-full-scanv0.10 or newer- workflow examples target GitHub Actions. the same docker image works in GitLab CI, Jenkins, or any runner with docker
- scripts used:
zap-baseline.py,zap-full-scan.py,zap-api-scan.py(all ship in the docker image)
variant phrasings
owasp zap in github actions
same thing as this page: the official zaproxy/action-baseline and zaproxy/action-full-scan actions wired into .github/workflows/. pin the action to a release tag or SHA.
zap baseline vs full scan
baseline = spider plus passive rules, fast (a few minutes), safe for every PR. full = baseline plus ajax spider plus active attacks, slow, only against staging. that is the whole decision.
zap api scan from openapi
for APIs, skip the spider and point zap-api-scan.py at the OpenAPI or Swagger spec with -f openapi. there are also -f graphql and -f soap variants.
why it works this way
SAST reads your code, DAST attacks your running app. ZAP is DAST, so it has to run after the app boots in CI, and the two scan modes exist because active attacks are destructive: the baseline gives you cheap passive coverage on every PR, the full scan gives you attack coverage where it cannot hurt anything. the rules file exists because a scanner that cries wolf on every missing header trains everyone to ignore it.
edge cases
- SPA or heavily javascript apps: the baseline spider misses client-rendered routes. the full scan's ajax spider covers them, or add the missing routes to the scan context.
- authenticated endpoints: ZAP scans as an anonymous visitor unless you configure form, script, or header-based auth first. unauthenticated scans of a logged-in app silently miss most of the attack surface.
- the target must be reachable from the runner: CI runners cannot see services bound to your laptop. point the target at a service in the same workflow or a deployed staging URL.
- alert fatigue: start with a small rules file and a few FAIL rules, then expand. failing the build on every informational alert is how the scan gets deleted in a month.
- report formats: the actions also emit SARIF, which uploads to the GitHub Security tab via the codeql upload-sarif action if you want findings next to code scanning results.
Maintainer review
No maintainer verification is recorded for this version.
This records the version a maintainer checked. It does not assert that the version is the latest upstream release.