Your country

Tools that support it use your country for local currency, number formats, units and paper size. Your choice is saved only in this browser.

Type a name or a two-letter code. Use the up and down arrow keys to move through the countries, Enter to choose one and Escape to close.

Web Security (OWASP Top 10:2025)  Module 15 – Security testing and OWASP ZAP in CI

ZAP API scans, authenticated scans and triage

Run a ZAP API scan from an OpenAPI description, write an Automation Framework plan, scan signed-in areas with a safe test user, and triage each alert.

  • Advanced
  • 40 minutes
  • Examples run with Node.js 24.21.0, quickjs 0.32.0, Python 3.14.8, Pyodide 314.0.7 and sql.js 1.14.2
  • By MySmartCoPilot
Free intro, Pro: 7 of 40 minutes free
Free: An active scan finds what a payload can show, and cannot know whose data is whose. With a pass: the other 8 sections and 4 interview answers, about 33 minutes.

What you will learn

  • Run an API scan from an OpenAPI description with ZAP's script or an Automation Framework plan
  • Write an Automation Framework plan with an environment, bounded jobs, a report and an exit status, and run it in CI
  • Configure an authenticated scan with a dedicated test user, a safely handled session token and excluded risky paths
  • Classify alerts as true or false positives, map each to a CWE and a Top 10 category, and fix it with a regression test
  • Limit scans to a copy you own, with disposable data, bounded minutes and threads, and a reviewed suppression file

Before you start

What you’ll learn · Before you start

What you will learn

  • Run an API scan from an OpenAPI description with ZAP's script or an Automation Framework plan
  • Write an Automation Framework plan with an environment, bounded jobs, a report and an exit status, and run it in CI
  • Configure an authenticated scan with a dedicated test user, a safely handled session token and excluded risky paths
  • Classify alerts as true or false positives, map each to a CWE and a Top 10 category, and fix it with a regression test
  • Limit scans to a copy you own, with disposable data, bounded minutes and threads, and a reviewed suppression file

Before you start

On this page

An active scan of the training shop’s vulnerable build raised three High alerts: a SQL injection on one parameter, and cross-site scripting on another, reported twice. The same plan on the fixed build raised none. It also said nothing about the worst flaw in the shop, one customer reading another’s order, which a test with two users finds at once.

You will set up ZAP API scans from a description, write an Automation Framework plan that fails a build on the right alerts, run authenticated scans with a test user that can do no harm, and triage each alert into a fix and a test.

An active scan finds what a payload can show, and cannot know whose data is whose

ZAP API scans read a description of an API in OpenAPI, SOAP or GraphQL form, import its endpoints and run an active scan tuned to APIs. Unlike the baseline, an active scan attacks: it sends requests built to provoke a flaw to every parameter it knows. It runs only against a copy you own, with disposable data, and every run needs bounds on its time and its threads. ZAP API scans take a rules file of the same format and end with the same exit codes as the baseline. Authenticated scans reach the signed-in part of an application, where most of the data is, and they do so only when ZAP carries a session. Authenticated scans are also where an active scan can do the most harm, because the attacks reach endpoints that create, change and delete data.

Turning the alerts of a scan into decisions: each alert is reproduced, classified, mapped to a CWE, fixed and given a regression test. is what comes after the scan: each alert is reproduced, classified as a true or false positive, mapped to a CWE and a Top 10 category, fixed, and given a regression test. The program summarises three recorded active scans of ShopNest, the training shop, by risk, to show how much there is to triage. They ran a plan with ZAP’s default policy and a visit to the search page, so they include the cross-site scripting rules that the API scan script leaves out:

Three recorded active scans, by risk, and the exit value of each plan JavaScript · summary/active_summary.mjs
// The alerts of three active scans of ShopNest by ZAP 2.17.0, one line for each rule: the id, the name, the risk and the CWE, and for an alert that an
// attack raised, the endpoint and the parameter. The scans ran an Automation Framework plan whose last job, exitStatus, had errorLevel High and warnLevel
// Medium. The program works out the plan's exit value from the alerts, and prints the exit value that each recorded run ended with beside it.
const INFO = [
  ['10104', 'User Agent Fuzzer', 'Informational', 0],
  ['10111', 'Authentication Request Identified', 'Informational', -1],
  ['10112', 'Session Management Response Identified', 'Informational', -1],
];
const BUILDS = {
  vulnerable: {
    recorded: 1,
    alerts: [
      ['40018', 'SQL Injection', 'High', 89, 'GET /api/products', 'category'],
      ['40012', 'Cross Site Scripting (Reflected)', 'High', 79, 'GET /search', 'q'],
      ['40026', 'Cross Site Scripting (DOM Based)', 'High', 79, 'GET /search', 'q'],
      ['10038', 'Content Security Policy (CSP) Header Not Set', 'Medium', 693],
      ['10020', 'Missing Anti-clickjacking Header', 'Medium', 1021],
      ['10202', 'Absence of Anti-CSRF Tokens', 'Medium', 352],
      ['90003', 'Sub Resource Integrity Attribute Missing', 'Medium', 345],
      ['10010', 'Cookie No HttpOnly Flag', 'Low', 1004],
      ['10054', 'Cookie without SameSite Attribute', 'Low', 1275],
      ['10017', 'Cross-Domain JavaScript Source File Inclusion', 'Low', 829],
      ['10021', 'X-Content-Type-Options Header Missing', 'Low', 693],
      ['10037', 'Server Leaks Information via "X-Powered-By" HTTP Response Header Field(s)', 'Low', 497],
      ...INFO,
    ],
  },
  fixed: { recorded: 0, alerts: INFO },
  'fixed, CSP header removed': { recorded: 2, alerts: [['10038', 'Content Security Policy (CSP) Header Not Set', 'Medium', 693], ...INFO] },
};
const RANK = { Informational: 0, Low: 1, Medium: 2, High: 3 };
const AUTHORISATION = [284, 285, 639, 862, 863]; // CWEs about who may read or change what

const exitValue = (alerts) => {
  const top = Math.max(0, ...alerts.map((a) => RANK[a[2]]));
  return top >= RANK.High ? 1 : top >= RANK.Medium ? 2 : 0; // errors give 1, warnings give 2, a clean plan gives 0
};

console.log('build'.padEnd(27) + 'High  Medium  Low  Info  alerts  plan exit');
for (const [name, build] of Object.entries(BUILDS)) {
  const count = (risk) => build.alerts.filter((a) => a[2] === risk).length;
  const exit = exitValue(build.alerts);
  console.log(`${name.padEnd(27)}${String(count('High')).padStart(4)}${String(count('Medium')).padStart(8)}${String(count('Low')).padStart(5)}${String(count('Informational')).padStart(6)}${String(build.alerts.length).padStart(8)}  ${exit} (the run ended with ${build.recorded})`);
}
console.log('\nHigh alerts of the vulnerable build:');
for (const [id, name, , cwe, where, param] of BUILDS.vulnerable.alerts.filter((a) => a[2] === 'High')) console.log(`  ${id} ${name}, CWE-${cwe}: ${where}, parameter ${param}`);
const vulnerable = BUILDS.vulnerable.alerts;
console.log(`\nAlerts about who may read what (CWE ${AUTHORISATION.join(', ')}): ${vulnerable.filter((a) => AUTHORISATION.includes(a[3])).length} of ${vulnerable.length}`);

Output

build                      High  Medium  Low  Info  alerts  plan exit
vulnerable                    3       4    5     3      15  1 (the run ended with 1)
fixed                         0       0    0     3       3  0 (the run ended with 0)
fixed, CSP header removed     0       1    0     3       4  2 (the run ended with 2)

High alerts of the vulnerable build:
  40018 SQL Injection, CWE-89: GET /api/products, parameter category
  40012 Cross Site Scripting (Reflected), CWE-79: GET /search, parameter q
  40026 Cross Site Scripting (DOM Based), CWE-79: GET /search, parameter q

Alerts about who may read what (CWE 284, 285, 639, 862, 863): 0 of 15

Recorded with Node.js 24.21.0 on macOS 26 arm64. To run it yourself: mise exec node@24.21.0 -- node active_summary.mjs

The vulnerable build raised 15 alerts: three High, four Medium, five Low and three informational. The High alerts are a SQL injection on the category parameter of the products endpoint, and cross-site scripting on the q parameter of the search page, reported as reflected by one rule and as DOM based by another, because one echo of the query string trips both. The plan ended with exit value 1, the fixed build with 0, and the fixed build with one header taken away with 2. None of the 15 alerts is about who may read what. The order endpoint of the vulnerable build gives any signed-in user any customer’s order, and the scan has no way to know whose order was whose.

Key idea

A scan finds what a payload can show. Write tests for the rules that only your application knows.

Checkpoint

Check: What an active scan finds

  1. Question 1 of 2 In the first program, how many High alerts does the scan of the vulnerable build raise?

    Type a number.

    Show the answer: Question 1 of 2

    Answer: 3 alerts

    A SQL injection on the category parameter and cross-site scripting on the q parameter, reported by two rules: 1 + 2 = 3.

  2. Question 2 of 2 The vulnerable shop gives any signed-in user any customer's order, and none of the 15 alerts mentions it. Why?

    Choose one answer.

    Show the answer: Question 2 of 2

    Answer: Who may read which order is a rule of the application, and a scanner has no way to know it

    Write a test with two users and made-up orders for the rules that only your application knows. The scan and the test cover different questions.

Flashcards · 4 cards

Recall it, then check

Say the answer to yourself before you look: recalling it is what makes it stick.

  1. How does a ZAP active scan differ from the baseline scan?

    An active scan attacks: it sends requests built to provoke a flaw to every parameter it knows, so it runs only against a copy you own, with disposable data and bounds on time and threads. The baseline sends no attack. ZAP API scans import an OpenAPI, SOAP or GraphQL description and take a rules file of the same format, with the same exit codes.

  2. Why do authenticated scans matter, and why are they riskier?

    They reach the signed-in part of an application, where most of the data is, when ZAP carries a session. They are also where an active scan can do the most harm, because the attacks reach endpoints that create, change and delete data, so use a test user whose actions can do no damage, on a disposable copy.

  3. What is triage after a scan?

    Turning the alerts into decisions: each alert is reproduced, classified as a true or false positive, mapped to a CWE and to a Top 10 category, fixed, and given a regression test.

  4. What did the active scan of the vulnerable shop find, and what could it not know?

    15 alerts: three High (a SQL injection on the category parameter, and cross-site scripting on the q parameter of the search page, reported twice, as reflected and as DOM based), four Medium, five Low and three informational. None was about who may read what: the order endpoint gave any signed-in user any customer's order, which a test with two users finds at once. A scan finds what a payload can show; write tests for the rules that only your application knows.

An Automation Framework plan runs the scan the same way every time Pro: opens with a Pro, Premium or Ultimate pass

In this part:

  • A signed-in scan needs a test user that can do no harm
  • A scan cannot tell whose data is whose, so test that yourself
  • Triage turns a report into a short queue of fixes
  • Gate on new alerts, and keep suppressions honest
    • Apply a suppression file to the alerts of a scan
  • Practise: from the first scan to the gate
  • Interview questions
    • You are asked to run an authenticated active ZAP scan of the staging copy of an e-commerce API every night. What do you set up so that it finds problems and does no harm?
    • A first active scan of your service raises a hundred alerts and the team lead wants a plan. How do you turn them into work, and how do you decide what to fix first?
    • A penetration test finds that signed-in users can read each other's orders, and your nightly ZAP scans never reported it. What went wrong, and what do you change in the pipeline?
    • Your gate fails on new alerts of Medium risk and above. A team asks to suppress one that they call a false positive. What do you require, and who decides?
  • Key takeaways

2 diagrams · 4 runnable examples (JavaScript, JavaScript (plan_check.mjs), JavaScript (plans.mjs), JavaScript (reports.mjs), JavaScript (zap_report_triage.mjs), Python, YAML (zap-api-plan.yaml), YAML (zap-api.yml)) · 1 exercise with sample tests · 4 interview answers · about 47 minutes

Opening this part with your pass…

This part opens with any MySmartCoPilot pass.

  • Pro pass · 1 week₹79$2.99
  • 30 days₹179$5.99
  • 1 year₹1,699$45

One payment; a pass never renews. The price shown is the total you pay: no fees or charges are added at checkout.

It also opens every other in-depth part in Learn and every Pro tool. Sales are final, except where the law gives you a right to withdraw, for example in the EU and the UK (refund policy).

Terms in this lesson

accepted risk
A real weakness that a person with the authority has decided to carry for now. The decision is written down with a reason, an owner and an expiry, and the alert stays on the list. A High risk is never accepted by a configuration file.
Automation Framework
ZAP's way of running a scan from one YAML plan: an environment that says what the plan may touch, and jobs that run in order, such as a spider, an active scan, a report and an exit status. The plan ends with an exit value of 0, 1 or 2.
context
In ZAP, the set of addresses that belong to the application under test, defined by the starting addresses and by include and exclude patterns. Jobs that name a context stay inside it, so it is the first limit on where a scan may go.
report diff
A comparison of two scan reports that lists the alerts that are new, resolved or unchanged. Alerts are keyed by rule and parameter, not by page, because a crawl reaches different pages on each run. A gate that fails on new alerts leaves old ones to the plan.
triage
Turning the alerts of a scan into decisions: reproduce each one, classify it as a true positive, a false positive or an accepted risk, merge alerts that share a root cause, map it to a CWE, fix it with a regression test, and suppress only what remains false.

References

Related tools

Report a problem with this lesson

Quick answers and tool search

Type to search tools or to get a quick answer, for example 18% of 2500. Use the up and down arrow keys to move through the results, Enter to choose, and Escape to close.