iwantcoding.com
🔥 Daily 👥 Rooms 🏆 Top Log in Sign up

DAST

How to add a defensible DAST stage to CI: ZAP baseline scan, allowlist auth, treating findings like compiler errors. Authorised use only.

OWASP — DAST in CI

EXAMPLE
# SCOPE: authorised DAST against owned staging environments only.
# Do not scan systems you do not own or have written permission to test.

# ===== Where DAST sits =====
# SAST (source) -> SCA (deps) -> Build -> DAST (running app on staging)
# DAST exercises the running app: auth, sessions, headers, common injections.

# ===== ZAP baseline scan (read-only, safe defaults) =====
# .github/workflows/dast.yml
name: dast
on:
  workflow_dispatch:
  schedule:
    - cron: '0 4 * * 1'   # weekly, 04:00 Monday
jobs:
  zap:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      issues: write
    steps:
      - uses: actions/checkout@v4
      - name: ZAP baseline
        uses: zaproxy/action-baseline@v0.13.0
        with:
          target: ${{ secrets.STAGING_BASE_URL }}
          rules_file_name: '.zap/rules.tsv'
          cmd_options: '-a -j -m 5 -T 10'

# .zap/rules.tsv  (false-positive allowlist; one rule per line)
# Format:  PLUGIN_ID\tTHRESHOLD\tNEW_RISK
10038\tIGNORE\t\t# CSP missing on /healthz (not user-facing)
10063\tIGNORE\t\t# Permissions-Policy intentionally unset on docs

# ===== Authenticated scan (after baseline is green) =====
# ZAP automation framework, scanned with a session token
# .zap/automation.yaml
env:
  contexts:
    - name: app
      urls: [${{ secrets.STAGING_BASE_URL }}]
      authentication:
        method: script
        parameters:
          scriptName: login.zst
          scriptEngine: ECMAScript
        loggedInIndicator: 'Sign out'
      users:
        - name: scan-bot
          credentials:
            username: ${{ secrets.SCAN_USER }}
            password: ${{ secrets.SCAN_PASS }}
jobs:
  - type: spider
    parameters: { context: app, user: scan-bot, maxDuration: 5 }
  - type: activeScan
    parameters: { context: app, user: scan-bot, policy: 'Default Policy' }
  - type: report
    parameters: { template: 'sarif-json', reportDir: reports/ }

# ===== Make findings part of the loop =====
# 1. Upload SARIF to GitHub code scanning
# 2. Auto-open issues for HIGH findings, labelled 'security/dast'
# 3. Allowlist with a comment + an expiry date
# 4. Track median-time-to-fix as a metric

# ===== Verification: hardened response headers =====
# These are usually the first wins:
#   Content-Security-Policy: default-src 'self'; ...
#   X-Content-Type-Options: nosniff
#   X-Frame-Options: DENY (or CSP frame-ancestors)
#   Referrer-Policy: strict-origin-when-cross-origin
#   Permissions-Policy: camera=(), microphone=(), geolocation=()
#   Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

# ===== Patterns to internalise =====
# - DAST runs against staging that mirrors prod; do not point at prod
# - Use a dedicated scan-user with limited data and audit tags
# - Baseline first (read-only) before active scans
# - Manage false positives with timed allowlists, not 'IGNORE forever'
# - Treat HIGH findings like a failing test: fix or expire

# ===== Pitfalls =====
# - Pointing the scanner at prod and tipping over user-facing endpoints
# - No auth -> only the public surface is exercised; deep bugs hide
# - Allowlists with no owner or expiry -> findings rot
# - Scan-user with prod-like permissions -> blast radius if compromised
# - Treating DAST as a one-off audit instead of weekly hygiene

Why it matters

DAST is hygiene, not a stunt. Weekly baseline on staging, authenticated scan once that is green, SARIF into the same dashboard as SAST. Allowlist with expiry. The unfixable findings shrink each quarter, and the fixable ones land like bug fixes.

Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.

Example

Example
// Dynamic scan against staging: ZAP baseline scan, Burp Suite Pro.
// Run weekly; treat findings as tickets.
Try it Yourself »

Discussion

Loading…