Designs an end-to-end DAST cadence for teams adopting dynamic scanning: ZAP passive baseline (PR-blocking) then ZAP full active scan (nightly on staging) then optional Burp Pro deep scan (per-release). Handles the baseline-finding ratchet for legacy apps so pre-existing findings do not immediately block PRs, plus per-tool per-run deduplication and CI workflow YAML. Use when the team is setting up DAST from scratch or restructuring scan cadence, not when tools are already running and you need to merge their output (cross-tool aggregation of existing independent runs is a separate concern).
70
88%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
This skill is a build-an-X workflow - the per-team DAST cadence and aggregation strategy. It layers ZAP passive baseline, ZAP full active scan, and an optional paid deep scan across PR / nightly / per-release windows, with a baseline ratchet so pre-existing findings don't block PRs.
Three scan types stack:
| Layer | Scan type | Cadence | Target | Risk |
|---|---|---|---|---|
| 1 | Passive baseline (ZAP baseline) | Per-PR (blocking) | Staging | Safe - passive only |
| 2 | Active full scan (ZAP full / NightVision) | Nightly | Staging | Active probes - pollute staging data |
| 3 | Deep paid scan (Burp Pro / Enterprise) | Per-release / weekly | Staging | Active + extension-driven |
PR-blocking layer is intentionally narrow - only fail on findings that didn't exist before. This requires baseline ratchet (Step 3).
The first scan against a legacy app surfaces 100s of pre-existing findings; if they all block PRs, the team disables DAST. The ratchet pattern:
baseline-findings.json# pr-gate.py
import json
def diff_findings(current, baseline):
baseline_keys = {(f['file'], f['rule_id']) for f in baseline}
new = [f for f in current if (f['file'], f['rule_id']) not in baseline_keys]
return new
with open('current.json') as f:
current = json.load(f)
with open('baseline.json') as f:
baseline = json.load(f)
new_findings = diff_findings(current, baseline)
if any(f['severity'] in ['critical', 'high'] for f in new_findings):
print(f"FAIL: {len(new_findings)} new finding(s) on PR; not in baseline")
exit(1)ZAP baseline natively supports this via the -c config.tsv rule
file; mirror the pattern for the cross-tool aggregation layer.
Consecutive PR-runs catch the same vulnerability multiple times;
each PR comment shows duplicate noise. Dedupe by (rule_id, url, parameter) tuple:
def dedupe_findings(findings):
seen = set()
deduped = []
for f in findings:
key = (f['rule_id'], f['url'], f.get('parameter', ''))
if key not in seen:
seen.add(key)
deduped.append(f)
return dedupedCross-tool dedup is handled at the aggregation layer; this skill's dedup is per-tool per-run.
Three workflows implement the layering:
| Workflow file | Trigger | Job |
|---|---|---|
.github/workflows/dast.yml | pull_request | ZAP baseline + dast-pr-gate.py (Step 2) |
.github/workflows/dast-nightly.yml | cron: 0 2 * * * | ZAP full scan + NightVision API scan |
.github/workflows/dast-release.yml | release: created | Burp deep scan (self-hosted) |
Full workflow YAML for all three: references/ci-workflows.md.
When a new finding appears in a PR-blocking scan, the team has 4 options:
.zap/rules.tsv
with # Reason: ... Re-review-date: ...Each option requires reviewer + reason + Re-review-date in commit message or PR comment. No silent suppression.
Post-scan, measure coverage to detect blind spots:
# How many endpoints did the scan cover?
jq '.spider_results.urls | length' report.json
# How many endpoints did the OpenAPI spec define?
jq '.paths | length' openapi.yaml
# Coverage ratioIf coverage < 80% of API surface, the spider missed routes; investigate auth flows, JS-heavy SPAs, route-discovery gaps.
Once 2+ tools run, aggregate each tool's JSON output:
zap-baseline.py -t $URL -J zap.json
nightvision scan results $SCAN_ID --output json > nightvision.json
# Burp Enterprise: download via API
curl ... -o burp.json
# Aggregate all three + emit unified verdictThe aggregation layer handles cross-tool dedup, severity normalization, waiver enforcement.
| Anti-pattern | Why it fails | Fix |
|---|---|---|
| Run full active scans on every PR | Scan time blows out CI; staging data corrupted | Baseline-only on PR; full nightly (Step 4) |
| Skip baseline ratchet | Legacy findings block every PR | Baseline + diff (Step 2) |
| Ignore coverage measurement | Missing endpoints unscanned silently | Step 6 weekly check |
| One scan per app, never re-baseline | Baseline grows stale; misses regressions in old code | Quarterly re-baseline + waiver review |
| Run ZAP + Burp + NightVision without dedup | Same finding shows 3x | Aggregate via triager (Step 7) |
For each app's DAST coverage:
Re-review-date + reviewer (Step 5)zap-baseline, burp-headless,
nightvision-dast.zap-baseline,
burp-headless,
nightvision-dast - sister tools