Dynamic Application Security Testing attacks a deployed app from the outside like a real attacker — finding auth, header, and injection issues SAST cannot see. Run an automated OWASP ZAP baseline scan against staging in CI.
DAST runs against a live application, sending crafted requests and observing responses — exactly how an external attacker probes you. Because it exercises the real running system, it catches issues static analysis is blind to: missing security headers, broken authentication flows, server misconfiguration, and injection that only manifests at runtime. Its cost is that it needs a deployed environment and finds fewer, but more real, issues.
SAST DAST
reads the source attacks the running app
per-PR, seconds needs a deployed env, minutes
exact file:line "this URL is vulnerable" (no line)
false positives common findings are real (it did it)
misses runtime/config/authn FINDS runtime/config/authn:
- missing HSTS/CSP headers
- auth bypass, session flaws
- server misconfig, verbose errors
- reflected injection
Run both. They see different halves of the system.OWASP ZAP is the standard open-source DAST tool. Its "baseline" scan is passive and fast — it spiders the app and flags issues without launching active attacks, which makes it safe to run on every deploy to staging. Point it at a freshly deployed environment and let it fail the build on new alerts.
Scope required — Only run DAST against systems you own or are authorized to test — your own staging/ephemeral environment. Never point an active scanner at third-party or production systems without written authorization.# job: dast — after deploying to an ephemeral/staging URL
dast:
runs-on: ubuntu-latest
steps:
- name: ZAP baseline scan (passive, safe for CI)
uses: zaproxy/action-baseline@v0.12.0
with:
target: ${{ env.STAGING_URL }}
# -a: include alpha passive rules; -I: don't fail on warnings
cmd_options: "-a"
fail_action: true # fail the build on new alertsMost of an app lives behind authentication, and an unauthenticated scan only sees the front door. Feed ZAP a session (a token or a login script) so it can test the authenticated surface where the real risk is. Also seed it with your API spec so it tests every endpoint, not just what a spider can crawl to.
# authenticated + API-aware scanning
dast:
steps:
- name: Import OpenAPI so ZAP knows every endpoint
run: |
zap-api-scan.py -t ${{ env.STAGING_URL }}/openapi.json \
-f openapi \
-z "-config replacer.full_list(0).replacement='Bearer ${{ secrets.STAGING_TEST_TOKEN }}'"
# use a dedicated low-value test account in staging, never prod creds.
# now ZAP tests authenticated endpoints + every documented route.Like SAST, DAST needs tuning to stay trustworthy. Maintain a baseline of accepted alerts so known, triaged items do not re-fail every run, and file real findings as tickets linked to the threat model. Keep active (attacking) scans out of the fast PR path — run them on a schedule or against ephemeral environments where a heavier scan is safe.
# .zap/rules.tsv — mark triaged findings so they don't re-break the build
# ruleId action url-regex comment
10096 IGNORE .* timestamp disclosure - accepted, low risk
10021 FAIL .* missing X-Content-Type-Options - must fix
# cadence:
# baseline (passive) scan -> every deploy to staging (fast, safe)
# full active scan -> nightly against an ephemeral env
# every real finding -> ticket, linked back to the threat model id.