DevSecOps & Supply Chain Security

DAST & Runtime Testing

18 min Lesson 5 of 28

DAST & Runtime Testing

Static analysis (SAST) reads source code without executing it. Dynamic Application Security Testing (DAST) does the opposite: it attacks a running application exactly the way a real attacker would — sending malformed inputs, probing endpoints, following redirects, and inspecting responses. This lesson covers how to integrate DAST into a staging-based pipeline, how to fuzz APIs at scale, and how to interpret results without drowning your team in false positives.

Why DAST Exists Alongside SAST

SAST misses an entire class of vulnerabilities that only appear at runtime: deserialization gadget chains, server-side request forgery triggered by a library that fetches user-supplied URLs, authentication bypass through token-validation logic, and race conditions in concurrent request handlers. DAST finds these because it interacts with the actual process, memory state, and network stack. At big-tech scale, both are mandatory — they catch orthogonal defect classes, and the union of their findings is far larger than either alone.

Key principle: DAST requires a running target. That target must be staging-equivalent to production — same auth configuration, same database driver, same feature flags — otherwise scan results do not transfer to real risk.

Core DAST Tools in a Modern Pipeline

The two tools you will encounter most often in CI/CD pipelines are OWASP ZAP (Zed Attack Proxy) and Nuclei. ZAP is a full intercepting proxy with an active-scan engine and a rich automation framework. Nuclei is a template-driven scanner built for speed and GitOps-friendly workflows — templates live in a repository alongside application code, versioned and reviewed like any other dependency.

For API-first services, RESTler (from Microsoft Research) handles OpenAPI-spec-driven stateful fuzzing — it reads your openapi.yaml and generates sequences of calls that explore multi-step state machines. ffuf is used for targeted parameter and path brute-forcing when you need surgical precision against a specific endpoint.

Staging-Based Pipeline Integration

The standard pattern is: deploy to staging → run smoke tests → run DAST → gate promotion to production on the DAST exit code. Never run active DAST against production. Active scanning sends malformed, oversized, and repeated payloads that corrupt data, spike error rates, exhaust connection pools, and trigger alerting storms. The staging environment must have its own isolated database seeded with synthetic data that is safe to destroy.

# .github/workflows/dast.yml — active ZAP scan after staging deploy dast-scan: needs: deploy-staging runs-on: ubuntu-latest steps: - name: Checkout (for ZAP rules file) uses: actions/checkout@v4 - name: ZAP Full Scan uses: zaproxy/action-full-scan@v0.10.0 with: target: 'https://staging.example.com' rules_file_name: '.zap/rules.tsv' # suppress known-safe alert IDs cmd_options: '-a -j -l WARN' fail_action: true # non-zero exit on HIGH findings - name: Upload ZAP Report uses: actions/upload-artifact@v4 if: always() with: name: zap-report path: report_json.json

The rules.tsv file is how you avoid alert fatigue. Each line is an alert ID and an action: IGNORE, WARN, or FAIL. On a new service, run ZAP in passive-only mode first, review every finding, then promote the legitimate ones to FAIL and silence the noise with IGNORE.

# .zap/rules.tsv — one rule per line: <alertId> TAB <action> TAB <comment> 10020 IGNORE X-Frame-Options managed by CDN layer, not app 10038 IGNORE CSP delivered by reverse proxy 10096 WARN Timestamp Disclosure — low risk, monitor only 40018 FAIL SQL Injection — zero tolerance 40012 FAIL Reflected XSS — zero tolerance 90022 WARN Application Error Disclosure — investigate but do not block

API Fuzzing Basics

Modern backends are mostly APIs, not HTML pages. ZAP's crawler is designed for HTML — for REST and GraphQL APIs you need spec-driven fuzzing. The workflow: export your OpenAPI spec from CI (many frameworks auto-generate it), feed it to the fuzzer, and let it generate boundary-value inputs for every parameter.

DAST pipeline: spec-driven API fuzzing flow OpenAPI Spec Export Fuzzer (RESTler / ZAP) Boundary Value Payloads Malformed Auth Sequences Staging API Target Response analysis & finding triage
Spec-driven API fuzzing: the OpenAPI contract drives payload generation; findings flow back for triage.

A practical Nuclei invocation against a staging API — running only the HTTP and technology-detection templates at medium-and-above severity — looks like this:

# Install Nuclei (Go binary) go install -v github.com/projectdiscovery/nuclei/v3/cmd/nuclei@latest # Run against staging — HTTP templates only, severity medium+ nuclei \ -target https://staging.example.com \ -tags http,tech \ -severity medium,high,critical \ -rate-limit 50 \ -bulk-size 10 \ -concurrency 5 \ -o nuclei-report.json \ -json # Update template library (pin in CI by committing templates to your repo) nuclei -update-templates
Pin your template version in CI. Running nuclei -update-templates unconditionally in a pipeline means a new template with a false positive can break your build unexpectedly. Commit the nuclei-templates/ directory (or a specific tag) to your repo and update it as a deliberate dependency upgrade.

Authenticated Scanning

Unauthenticated DAST finds publicly exposed issues but misses the entire authenticated attack surface — which is usually where the sensitive data lives. ZAP supports session handling scripts and JSON-based auth configuration. You inject a short-lived staging credential (never a production secret) via a CI secret, and ZAP replays it on every request.

For OAuth2/OIDC flows, the pattern is: obtain a token with a client-credentials or password grant against the staging IdP → write it to a ZAP context file → use ZAP's replacer add-on to inject the Authorization: Bearer header on every scan request.

Common DAST Finding Classes

  • Reflected and Stored XSS — inputs that are echoed back without encoding. ZAP injects known XSS polyglots into every parameter.
  • SQL Injection — ZAP and SQLMap both test this; ZAP is pipeline-friendly, SQLMap is more exhaustive for targeted investigation.
  • IDOR (Insecure Direct Object Reference) — swapping one user's resource ID for another. Automated scanners have limited coverage here; complement with manual testing or custom scripts.
  • Security Misconfigurations — missing HSTS, permissive CORS, server version disclosure, default credentials on admin interfaces.
  • Authentication Flaws — missing rate limiting on login endpoints, weak session token entropy, JWT with alg:none.
Production pitfall — scan against a live database: Active scanning will attempt SQL injection, which triggers real DB queries. If your staging database has foreign-key constraints pointing at shared infrastructure, or if staging shares a message queue with production, a scan payload can propagate downstream. Isolate staging at the network and data layer before enabling active scanning.

Interpreting Results and Reducing Noise

A first ZAP scan against a typical web application returns dozens to hundreds of findings at INFORMATIONAL or LOW severity — most of them informational banner disclosures or missing optional headers that your CDN already handles. The workflow to get signal out of noise:

  1. Run ZAP in passive mode first (no active payloads) to build a baseline without risk.
  2. Triage every finding against your actual architecture — many LOW findings are mitigated at the load-balancer or CDN layer.
  3. Encode the triage decisions into rules.tsv so they are applied automatically on every subsequent run.
  4. Set the pipeline gate at HIGH or CRITICAL only — MEDIUM findings go into the backlog with a sprint SLA rather than blocking deploys.
  5. Track finding count over time in your SIEM or a simple CSV artefact; a spike in new findings after a deploy is a meaningful signal even if individual findings are suppressed.

DAST in the Broader DevSecOps Loop

DAST findings feed back into SAST rules. When ZAP finds a reflected XSS in a template rendering function, you add a SAST rule (a Semgrep pattern or a CodeQL query) that flags every call to that function without the sanitization wrapper — catching future regressions before they reach staging at all. This feedback loop is where "shift left" gets its real leverage: runtime findings harden the static ruleset.

DevSecOps feedback loop: DAST findings harden SAST rules Developer Commits Code CI — SAST Semgrep / CodeQL Staging DAST / ZAP Production Deploy Gate DAST finding → new SAST rule (closes the loop)
DAST findings from staging feed back as new SAST rules, catching the same class of bug at commit time in future.

The goal is to make every production-equivalent security finding self-reinforcing: the first time you catch it at runtime, you automate catching it at compile time. Over a year of sustained effort, this compresses the finding-to-rule feedback loop from weeks to hours.