
A vulnerability scan is an automated inventory of known issues. A pentest is a human trying to chain them.
ZAP or a commercial scanner will find missing headers and textbook XSS. It will not sit with two accounts and walk the invoice id unless you pay for that work and scope it.
The usual mistake is using the words interchangeably in a board slide, then skipping the authenticated test.
This page is when each one is the right buy, and what you still write in CI either way.
ZAP 2.17.0 published on 15 December 2025. zap-baseline.py is a one-minute spider and a passive pass. A green badge that never swapped aliceSid for bobSid is the failure.
How to wire the baseline sits on OWASP ZAP in CI. How to scope the human hours sits on scope then close. The control you still write sits on the secure coding checklist. A missing owner check sits on IDOR.
Two products, two tickets
A scheduled scan matches banners, headers, cookie flags, and known CVE signatures. It is cheap. It should run often. It does not know Alice from Bob. It does not read a refund flow. It does not decide whether a 200 on GET /orders/8412 leaked a tenant.
A hired review is a person with two accounts, a written window, and time to follow a business rule. It is expensive. It should run when you ship a money path, a new tenant model, or a new authn scheme. It is not a weekly badge.
NIST SP 800-115, dated September 2008 on the CSRC page still lists both as different techniques. There is no 2026 replacement that merges the two names. Use both. Do not merge the names.
TRIPWIRE zap-baseline.py on STAGING_ORIGIN rules.tsv FAILs cookie + 500 body green means headers still hold never invented aliceSid / bobSid PERSON two fixtures, refund and export finding names the request you patch, they retest that id CI replay stays 404
What a scheduled scan actually reports
As of 22 August 2026, ZAP Baseline Scan page. The script spiders for one minute by default, waits for passive alerts, then prints PASS, WARN, IGNORE, and FAIL. The page says it does not perform actual attacks. That is the tripwire. Point it at staging you operate. Do not dump customer cookies into a shared runner log.
An infrastructure scanner, Nessus or an ASV job, reports missing patches, default services, and weak TLS ciphers. That is useful. It is still a signature list. Bob reading Alice’s export does not appear there. A PATCH that accepts role is outside its signatures. The eleventh login from one key still returning 200 is a fixture test, not a CVE.
Treat three scan flavors as one product family. ZAP baseline is a web tripwire on headers, cookies, and error bodies. An ASV job is a PCI scan of external hosts. An infra scanner is a CVE and service list inside the VPC. None of those three invents a second session. File them under “scan” in the tracker. Give them a weekly or monthly cadence. Do not give them a pentest label in Jira just because the vendor PDF said “ethical hacking.”
False positives are the other cost. A scanner that flags every missing X-Frame-Options on a JSON API will bury the one 500 that leaked a stack. That is why rules.tsv exists. FAIL the three ids this page named. IGNORE the alert you already ticketed as SEC-188. Review leftover WARN once a sprint, then promote or ignore. A pile of untriaged WARN is not a risk register.
| Signal | Scanner | Person |
|---|---|---|
| Missing Secure cookie | Yes, every week | Will mention, not why you hired them |
| Known CVE on nginx | Yes, if the banner matches | Only if it changes the app risk |
| Bob reads Alice | No, one session | Yes, that is the hour |
| Refund without pay | No | Yes, they watch the flow |
| Extra PATCH field | Rarely | Yes, they send role |
| False positive pile | Common | They drop it or prove it |
gowenfawr said the same split on Security Stack Exchange in November 2013, answer 45585: a scan will notice FTP opened to the world. It will not give insight into the application you wrote. the8472 already carries this page. The action is the same: keep the weekly job, stop calling it a review.
What you hire a person to read
You hire a person after the tripwire is boring. Headers are set. Cookies are flagged. The obvious CVEs are patched. Then you pay someone to watch checkout, password reset, export, and admin impersonation with two subjects you minted.
They should file a finding that names the route, the subject, the request against STAGING_ORIGIN, and the fix. How you turn that into a ticket and a retest sits on the scope page. This page only cares that you did not pay them to re-run zap-baseline.py.
Ask for hours on the flows you cannot unit-test in an afternoon: refund, gift card, partner webhook, admin impersonation, CSV export. A person who spends the week confirming Strict-Transport-Security is present has sold you a scan. Send that week back. Keep the hours for the flow.
OWASP API Security Top 10 2023 is still the current API list on 22 August 2026. project page. There is no 2025 API edition. API1 is still object-level authorization. A spider that never swaps the session scores that green. The person is the one who uses bobSid on Alice’s orderId.
PCI keeps the words apart
PCI SSC post dated 11 June 2024. PCI DSS v4.0.1 is a limited revision of v4.0. No requirements added or deleted. v4.0 retired on 31 December 2024. Future-dated items became mandatory on 31 March 2025. The public split every QSA still enforces is enough: an ASV scan is one artifact. A human application review is another. A clean scan report does not retire the review.
If a vendor sells you “PCI pentest” and the deliverable is a vulnerability export with a cover sheet, ask which artifact the QSA will accept. Then ask to see a finding that is not a CVE. If they cannot show one from a prior redacted report, you are buying a scan.
Write the two artifacts into the compliance folder with different names. scan-asv-2026-03.pdf is the ASV job. review-checkout-2026-09.pdf is the human writeup plus the retest addendum. A single file named pentest-q1.pdf that is only plugin ids is how the8472’s piggy bank gets into the audit zip. Keep the filenames honest even if a salesperson will not.
Run the tripwire you already own
Commit rules.tsv next to the app. FAIL the alerts you mean. IGNORE the ones you have already ticketed. The ZAP page has the Docker line and the workflow. This page only names the policy: staging you own, no production customers, exit 1 on FAIL.
# rules.tsv identifiers stay 10010, 10011, 90022
10010 FAIL (Cookie No HttpOnly Flag)
10011 FAIL (Cookie Without Secure Flag)
90022 FAIL (Application Error Disclosure)
10035 IGNORE (Strict-Transport-Security, ticketed SEC-188)
The numbers are ZAP alert ids. baseline docs. 10010, 10011, and 90022 are the three this page FAILs. If you use a different scanner, keep the same idea: fail closed on a missing cookie flag and on a 500 that talks. Do not fail the pipeline on a pile of INFO noise you have not triaged.
The proof that the tripwire still sees a miss:
# On staging you operate. Strip one FAIL header, expect exit 1.
docker run --rm -t \
-v "$(pwd):/zap/wrk:rw" \
ghcr.io/zaproxy/zaproxy:stable \
zap-baseline.py -t "$STAGING_ORIGIN" -c rules.tsv
# Restore X-Content-Type-Options. Expect exit 0.
If the job reports 0 URLs, the spider never reached the app. That is a missed target, not a secure app. Fix compose, then read the report. Full details and the GitHub Action sit on the ZAP guide.
When the invoice is worth it
Buy the person when you cannot write the test yourself yet, or when a second pair of eyes on a money flow is cheaper than a public bug. Do not buy the person to discover HttpOnly is missing. The tripwire already does that.
Write roe.yml first. Mint alice@shop.example and bob@shop.example. Name STAGING_ORIGIN. Hold a slice of pay for retest. The scope page is the loop. The types page is the evidence by surface. This page is only the split.
// test/object-replay.spec.js the scan will not write this
const request = require("supertest");
const app = require("../src/app");
test("Bob cannot read Alice order", async () => {
const aliceSid = process.env.ALICE_SID;
const bobSid = process.env.BOB_SID;
const created = await request(app)
.post("/orders")
.set("Cookie", aliceSid)
.send({ sku: "SKU-1", qty: 1 });
const replay = await request(app)
.get(`/orders/${created.body.id}`)
.set("Cookie", bobSid);
expect(replay.status).toBe(404);
});
Identifiers stay aliceSid, bobSid, STAGING_ORIGIN, rules.tsv, and zap-baseline.py. If a questionnaire asks “do you pentest,” answer with two sentences: weekly baseline on staging, human review on the last money-flow change, dated. Do not tick one box for both.
A cheap questionnaire reply that stays honest:
# security-questionnaire.answers.yml
vulnerability_scanning:
tool: zap-baseline.py
cadence: every_week
target: STAGING_ORIGIN
policy: rules.tsv
last_green: "2026-08-18"
human_review:
last_engagement: checkout-web-2026q3
scope_file: roe.yml
last_retest: "2026-09-22"
not_the_same_as: vulnerability_scanning
If procurement only allows one line, put the human review on that line and attach the weekly job as a separate exhibit. Never the reverse. A tripwire exhibit cannot carry a review question.
Questions we keep getting
Does a green ZAP job replace a hired review?
No. It replaces a forgotten cookie flag and a leaked 500. It does not replace two accounts or a person reading refund. Call it a tripwire on the ticket.
Can an ASV scan satisfy a pentest line on a form?
Not honestly. An ASV scan is a scan. If the form has two lines, fill both. If it has one line and means a person, do not attach Nessus.
Should I run an active full scan on production?
No. Keep zap-baseline.py on staging. Schedule any active job on a throwaway dataset. Production customer traffic stays out of that schedule.



