OWASP ZAP for QA — Scan, Triage, and Stop

Run OWASP ZAP against a staging build, then keep only the findings a developer can act on.

This post is part of the Security Testing Basics series. If you missed the previous post, read Part 1 — A Security Testing Mindset for Everyday QA first.


Introduction — A Scanner Is a List, Not a Verdict

ZAP will talk to your application and hand you alerts. Some are real. Some are headers you already accepted as a risk. Some fire because the scanner was not logged in and saw the login page.

The work is the triage. The scan is the cheap part.

Where the Scan Is Allowed to Run

Run ZAP at a staging build you can break. An active scan sends malicious payloads. It will create data, lock accounts, and trip rate limits.

Do not point an active scan at production. A baseline or passive look at production is a separate, narrower decision, and it still needs the people who own the site.

Give the scanner a user. An anonymous crawl of an application that lives behind login tests the marketing page.

Baseline First

The Automation Framework is enough for a first pass. A baseline scan spiders a little and reports passive findings. It does not try to exploit.

docker run --rm -v "$(pwd):/zap/wrk:rw" -t ghcr.io/zaproxy/zaproxy:stable \
  zap-baseline.py -t https://staging.example.com -r zap-report.html

Read the HTML report with a developer who knows the app. You are looking for three things:

  • a finding you can reproduce with a browser or with curl
  • a URL and a parameter, not only a plugin name
  • a decision: fix, accept, or “the scanner was lost”

What to Throw Away

Drop an alert when:

  • it only happens on a third-party script you do not ship
  • the same header finding is already an accepted risk, written down
  • you cannot reproduce it while logged in as the user the scanner used
  • the URL is a logout, a health check, or a static asset

Keep an alert when a user can see another user’s data, run script in the application origin, or change state without the right role. Those match the checklist from part 1. They get a defect with steps, not a pasted plugin id.

Stop

One baseline against the areas that changed this sprint is a useful afternoon. A full active scan of every host, with every plugin, on every pull request, is how the report becomes unread.

Write down the hosts in scope. When the report for those hosts is triaged, stop. More plugins will not finish the job the first report already gave you.

Conclusion

ZAP is how you get a list you would not have written by hand. You still decide what is in the application and what is noise. The next part of this series is the access-control cases a scanner usually misses, because they need two users and an object id.

Action for this week: Run a baseline scan against staging, logged in. Reproduce one high-confidence alert by hand. File it with the request, or close it with the reason.


Next in the series: Part 3 — Access Control Cases a Scanner Will Miss.