DAST (Dynamic Application Security Testing) is the security testing discipline that gets skipped most often in DevSecOps programs. SAST is easy to add to a pipeline — point a tool at your source code and get results. DAST requires a running application, an authenticated session, a crawl strategy, and a way to interpret results without drowning in false positives.
That’s why teams skip it. But DAST finds a class of vulnerabilities that SAST misses: runtime behavior, authentication flaws, server configuration issues, and injection vulnerabilities that only manifest at runtime.
OWASP ZAP (Zed Attack Proxy) is the most widely used open-source DAST tool. It’s mature, actively maintained, and has CI/CD integration support that makes automation practical.
Two Modes: Baseline vs Full Active Scan
ZAP offers several scan modes. For CI/CD, two are most relevant:
Baseline Scan — Passive only. ZAP spiders the application and flags issues it can observe without sending attack payloads. Fast (minutes), low false-positive rate, safe to run against any environment. Good for PR pipelines.
Full Scan — Active scan. ZAP sends attack payloads to identify injection, XSS, CSRF, and other active vulnerabilities. Slower (minutes to hours), higher false-positive rate, should run against a dedicated test environment — never production.
Setting Up ZAP in Docker
ZAP’s Docker images make CI integration straightforward without managing a local ZAP installation.
# Baseline scan against a running app
docker run --rm \
-v $(pwd):/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable zap-baseline.py \
-t https://staging.example.com \
-r zap-report.html \
-J zap-report.json \
-I # don't fail on warnings, only on errors
# Full active scan
docker run --rm \
-v $(pwd):/zap/wrk:rw \
ghcr.io/zaproxy/zaproxy:stable zap-full-scan.py \
-t https://staging.example.com \
-r zap-full-report.html \
-z "-config scanner.attackStrength=LOW"
GitHub Actions Integration
name: DAST — Baseline Scan
on:
pull_request:
schedule:
- cron: '0 2 * * 1' # weekly full scan on Mondays
jobs:
zap-baseline:
runs-on: ubuntu-latest
steps:
- name: Start application (example — replace with your setup)
run: docker compose up -d app
- name: Wait for app to be ready
run: |
timeout 60 bash -c 'until curl -sf http://localhost:8080/health; do sleep 2; done'
- name: ZAP Baseline Scan
uses: zaproxy/action-baseline@v0.12.0
with:
target: 'http://localhost:8080'
rules_file_name: '.zap/rules.tsv'
cmd_options: '-I'
- name: Upload ZAP report
uses: actions/upload-artifact@v4
if: always()
with:
name: zap-report
path: report_html.html
Handling Authentication
The hardest part of DAST automation is authentication. ZAP needs to log in as a user to test authenticated endpoints.
Script-based authentication (form login):
# zap-auth.py — ZAP automation script
import requests
# ZAP API
zap_url = "http://localhost:8080"
# Set up form-based auth
requests.post(f"{zap_url}/JSON/authentication/action/setAuthenticationMethod/", params={
"contextId": "1",
"authMethodName": "formBasedAuthentication",
"authMethodConfigParams": (
"loginUrl=https://app.example.com/login"
"&loginRequestData=username={username}&password={password}"
)
})
JWT/Bearer token via automation framework:
ZAP’s newer Automation Framework (YAML-based) handles this more cleanly:
# zap-automation.yaml
env:
contexts:
- name: app-context
urls:
- https://staging.example.com
authentication:
method: browser
parameters:
loginPageUrl: https://staging.example.com/login
loginPageWait: 5
verification:
method: response
loggedInRegex: '"authenticated":true'
jobs:
- type: spider
parameters:
maxDuration: 5
- type: activeScan
parameters:
maxScanDurationInMins: 30
- type: report
parameters:
reportFile: zap-report.html
reportTitle: DAST Report
Tuning to Reduce False Positives
A raw ZAP scan against most applications produces noise. The rules configuration file (.zap/rules.tsv) lets you tune which alerts fail the build:
# .zap/rules.tsv — format: rule_id\tOK/WARN/FAIL\toptional_reason
10202 WARN Absence of Anti-CSRF Tokens (handled at framework level)
10038 IGNORE Content Security Policy Header Not Set (handled by CDN)
40012 FAIL Cross Site Scripting (Reflected)
40014 FAIL Cross Site Scripting (Persistent)
90022 FAIL Application Error Disclosure
The principle: fail on vulnerabilities that represent real, exploitable risk in your context. Warn or ignore on issues that are mitigated elsewhere or don’t apply to your architecture.
What ZAP Finds That SAST Misses
DAST is complementary to SAST, not a replacement. Things ZAP reliably finds:
- Reflected and stored XSS — requires a running application to confirm
- Missing security headers — Content-Security-Policy, X-Frame-Options, HSTS
- Server information disclosure — error messages, stack traces, version banners
- Insecure cookie attributes — missing
Secure,HttpOnly,SameSite - CSRF vulnerabilities — state-changing requests without token validation
- Open redirects — redirect parameters that can be hijacked
These are runtime behaviors that no amount of static code analysis will reliably surface.
Practical Recommendations
- Start with baseline scans on every PR — no authentication needed, fast, low noise
- Run full scans weekly against a staging environment — with authentication, dedicated environment
- Define your rules file from day one — set severity thresholds appropriate to your context
- Treat ZAP findings like any other bug — create tickets, track remediation, don’t just re-scan
- Use the Automation Framework for complex setups — it’s more maintainable than shell scripts