ZAP Finding Triage — Operator Runbook
This runbook covers what to do when the OWASP ZAP baseline scan fires on a PR or weekly schedule run.
When does ZAP run?
| Trigger | Target | Who sees it |
|---|---|---|
| PR opened/pushed (Antlers surface only) | Branch preview URL on Cloudflare Pages | PR comment posted automatically |
| Weekly schedule — Monday 09:07 UTC | https://raxx-app.pages.dev + https://raxx-api-staging.herokuapp.com/ |
GitHub Actions step summary + auto-filed issues |
Manual workflow_dispatch |
Override URL (staging only) | Step summary |
Run location: .github/workflows/security-zap.yml
Finding severity map
ZAP uses a numeric risk code:
| riskcode | ZAP label | Our action |
|---|---|---|
| 3 | HIGH | Auto-file GitHub issue, severity:high + area:security; block PR (advisory) |
| 2 | MEDIUM | Log in artifact; post in PR comment; no auto-issue; triage within 1 week |
| 1 | LOW | Log in artifact only; no action required |
| 0 | INFO | Log in artifact only |
Step 1 — Find the artifacts
- Go to the Actions run that fired (link is in the auto-filed issue body or PR comment).
- Scroll to the bottom of the run page → Artifacts.
- Download
zap-antlers-report-<run_id>orzap-api-report-<run_id>. - Open
report_html.htmlfor a human-readable summary, orreport_json.jsonfor machine-parseable details.
Step 2 — Triage a HIGH alert
For each HIGH alert in the report:
-
Confirm the URL is reachable. ZAP sometimes fires on redirect chains or error pages. Check whether the finding URL is a real application endpoint.
-
Check the rule ID against the
.zap/rules.tsvIGNORE list. If the rule should be ignored and isn't yet, add it with a comment and open a PR. -
Confirm exploitability in the Raxx threat model: - Is the affected route user-accessible or internal-only? - Does it handle user-supplied input? - Is there a realistic attack vector given current auth model (CF Access + header-based API auth)?
-
Act on the finding: - Exploitable → assign the auto-filed issue to a developer; add
ready-for-devlabel; link from Epic #81 if it's a systemic issue. - False positive → close the auto-filed issue with a comment explaining why; add an IGNORE entry in.zap/rules.tsvwith rationale. - Needs more context → leaveneeds-grooming, tagcard-groomerfor a grooming pass.
Step 3 — Fix and verify
After a code fix is merged:
- Trigger a manual ZAP run via
workflow_dispatchwith the staging URL to confirm the finding is resolved. - Close the GitHub issue once the manual run is clean.
- If you added a temporary IGNORE in
.zap/rules.tsvto suppress the finding while the fix was in flight, remove the IGNORE entry in the same PR as the fix.
Suppressing a false positive
Edit .zap/rules.tsv — add a line:
<rule-id>\tIGNORE
Always add a comment on the line above explaining why the rule is a
false positive for Raxx. Open a PR; the security_response.md process applies.
Known false positives are documented in .zap/rules.tsv with full rationale.
DO NOT scan production
The scan is configured to target staging URLs only. Never add production
hostnames (app.raxx.app, raxx-console-prod.herokuapp.com, etc.) to the
workflow. Scanning prod can:
- Trigger Cloudflare WAF rate-limiting or temporary IP bans.
- Generate synthetic load during market hours.
- Create noise in production logs that obscures real traffic.
If a finding must be verified against prod behavior: reproduce it in a local environment or against staging with production-equivalent config.
Contacts and escalation
- Finding triaged → sre-agent handles infra-side; feature-developer handles code-side.
- Finding confirmed HIGH exploitable → escalate to Epic #81, tag
severity:high. - Finding confirmed CRITICAL (rare for ZAP baseline) → follow
docs/agents/security_response.mdCRITICAL path (block merge, immediate fix).
Related
.github/workflows/security-zap.yml— workflow definition.zap/rules.tsv— rule overrides + rationale.github/scripts/zap_file_issues.py— auto-issue filing scriptdocs/agents/security_response.md— full severity → action table- Epic #81 — SDLC, Tooling, and Security Hardening