
OWASP API Security Top 10 2023 is still the current API list on 22 August 2026. I opened the project page. There is no 2025 API edition.A “mobile review” that never names a MASVS control is the failure. You buy a surface by the proof you will keep.
How you write the window and close the ticket sits on scope then close. The scanner split sits on tripwire versus person. The owner check sits on IDOR. The HTML sink sits on XSS.
Types are scopes, not a catalog
Black, gray, and white describe how much you tell the reviewer on day one. Web, API, and mobile describe what they are allowed to touch. Buy the surface. Then pick how much source and admin you will share. Do not buy “IoT plus wireless plus physical” because a brochure had eight rows.
White box means you hand architecture, source, and admin on the staging origin. Gray means two user sessions and a map of the money flow. Black means a URL and a time box. Gray is the usual product-team buy: you already know the app, you want a person to abuse the rules you wrote. Black spends the first days on discovery you could have pasted into roe.yml.
OWASP WSTG stable is still v4.2, dated 3 December 2020. I opened the WSTG project page. Version 5.0 is in development. Name v4.2 in a web statement of work. Name the API Top 10 2023 for an API. Name MASVS 2.1.0 for a mobile binary. A method without a version is a slogan.
WEB cookie session + owner on the page
XSS sink named, encoded or rejected
reset flow read, not only spidered
API bobSid on Alice orderId is 404
PATCH with role does not stick
extra field never hits UPDATE
MOBILE MASVS-STORAGE, MASVS-AUTH named
device bind, not a copy of the API
MASTG test id on the finding
REJECT plugin dump, no request, no subject
Web: session, sink, and owner
A web buy is a browser-facing origin you operate. Demand three proofs. First, a cookie-authenticated route treated the other tenant as 404 or 403. Second, a field that lands in HTML was encoded or rejected, not reflected raw. Third, a state change on POST was refused when the browser said the request was foreign, or the session cookie was not the only check.
WSTG-v42 identifiers belong on the findings if you named v4.2 in the order. You do not need every WSTG row. You need the ones that match your pages: login, reset, checkout, export, admin. A finding that only says “add a WAF” is not web evidence. A finding that names GET /orders/8412 with bob@shop.example is.
# evidence.web.yml required keys. Empty key means send it back.
surface: web
method: WSTG-v42
origin: https://staging.shop.example
subjects:
- alice@shop.example
- bob@shop.example
must_include:
- owner_check_on_html_page
- xss_sink_named_and_encoded
- reset_or_checkout_flow_read
reject_if:
- only_header_alerts
- no_second_subject
The XSS page is the sink. The checklist is the rest of the floor. This file is what you paste into the order so the vendor cannot deliver a header scan and call it a web review.
Ask them to read password reset the way crovers asked: is the flow a one-time token bound to the account, or a guessable path. Ask them to look at a “hidden” admin link that is only display:none. Those two hours beat a week of directory busting you could have run yourself. Put both bullets in the order under must_include.
API: object authz and extra fields
An API buy is JSON routes, not the marketing site. API1:2023 is still Broken Object Level Authorization. API3:2023 is property-level authorization, the extra field and the extra response key. API5:2023 is function-level authorization, the admin verb a user token can still call. I am citing the 2023 list I opened. I am not inventing a 2025 list.
Demand a replay: bobSid on Alice’s orderId returns 404 or 403 and no row. Demand a PATCH that adds role or ownerId and expect 400. Demand a user token on an admin route and expect 403. If the credential is a bearer, say so and mint two tokens. The review still has to swap the subject.
# evidence.api.yml required keys for an API buy
surface: api
method: API-Top-10-2023
origin: https://staging.shop.example
subjects:
- alice@shop.example
- bob@shop.example
must_include:
- API1_bob_on_alice_object
- API3_patch_role_rejected
- API5_user_token_on_admin_denied
reject_if:
- only_openapi_fuzz
- single_session
OpenAPI fuzzing is a cousin of the tripwire. It is useful. It is not API1. A fuzzer that never logs in as Bob will not write the first test below. Put fuzz in CI. Put the person on the object and the extra field.
// test/api-evidence.spec.js the proof you keep after the invoice
const request = require("supertest");
const app = require("../src/app");
test("API1: bobSid cannot read Alice orderId", async () => {
const created = await request(app)
.post("/orders")
.set("Cookie", process.env.ALICE_SID)
.send({ sku: "SKU-1", qty: 1 });
const replay = await request(app)
.get(`/orders/${created.body.id}`)
.set("Cookie", process.env.BOB_SID);
expect(replay.status).toBe(404);
});
test("API3: PATCH role is 400", async () => {
const res = await request(app)
.patch("/orders/" + process.env.ALICE_ORDER)
.set("Cookie", process.env.ALICE_SID)
.send({ qty: 2, role: "admin" });
expect(res.status).toBe(400);
});
Identifiers stay aliceSid via ALICE_SID, bobSid via BOB_SID, orderId, and role. If the vendor’s writeup uses other names, map them in the ticket. A UUID in the path is not the proof. Sjoerd’s line on Security Stack Exchange, answer 272470, dated 4 October 2023, is the one this site already quotes on IDOR: unguessable identifiers are not a replacement for access control. The replay is still required.
Mobile: MASVS controls you can name
A mobile buy is the iOS or Android binary plus the way it stores secrets and talks to your API. It is not a second copy of the API review unless you also buy the API. I opened the MASVS v2.1.0 release. The tag date is 18 January 2024. It is still the current standard on 22 August 2026. v2.1.0 added MASVS-PRIVACY. There are no L1/L2 levels in v2. If a vendor still sells “MASVS L2,” they are quoting v1.
MASTG v2.0.0 is the first stable v2 testing guide. I opened the GitHub releases list. cpholguera published it on 30 June 2026. Findings should carry a MASVS control and, when they tested a concrete case, a MASTG-TEST-**** id. “We decompiled the APK” is not a control.
# evidence.mobile.yml
surface: mobile
standard: MASVS-2.1.0
guide: MASTG-2.0.0
platforms:
- ios
- android
must_name:
- MASVS-STORAGE
- MASVS-AUTH
- MASVS-CRYPTO
- MASVS-NETWORK
reject_if:
- only_api_host_scan
- cites_MASVS_L2
- no_MASTG_test_id
The API still needs its own buy if the app is a thin client. Do not let a mobile invoice retire API1. Two products, two proofs.
What you send back unread
crovers hired people for the hard part after the scan. You can do the scan. Send back:
- A PDF that is only plugin ids, CVEs, or ZAP WARNs.
- A finding with no route, no subject, and no request against an origin you named.
- A “mobile” report that never mentions MASVS 2.1.0.
- A “web” report that never used a second user.
- A black-box week that rediscovered your staging URL and stopped.
- A recommendation to install a WAF as the only fix.
Hold a slice of pay until the resubmit has the keys in evidence.web.yml, evidence.api.yml, or evidence.mobile.yml. The scope page is the retest loop. This page is the rejection list.
The statement of work that names proof
Put the surface, the method version, the two subjects, and the reject list in the order. Put STAGING_ORIGIN in the order. Put “no third-party host without a written permit” in the order. That last line is the May 2026 dannyw comment on the scope page. Do not paste attack recipes into the SOW. Paste the proof you will accept.
# sow.checkout-2026q3.yml
buy: [web, api]
not_buying: [mobile, physical, wireless]
web:
method: WSTG-v42
evidence: evidence.web.yml
api:
method: API-Top-10-2023
evidence: evidence.api.yml
subjects:
- alice@shop.example
- bob@shop.example
origin: https://staging.shop.example
deliverable:
each_finding_has: [route, subject, request_proof, fix]
retest: paid_slice_held
reject: scanner_export_as_final
If you later need mobile, add it as a second order with MASVS 2.1.0, not as a free extra week. Network, wireless, and physical are different owners and different legal. They do not belong on a product-app invoice unless those owners signed.
Internal network work needs a different window, different source IPs, and often a jump host the reviewer may use. Wireless needs a building and a written permit from facilities. Physical needs HR and cameras. None of those proofs look like evidence.web.yml. If a brochure bundled them, split the order or decline the extras. A product team that ships a web checkout does not owe a badge-cloning story.
Social engineering is also a different owner: people, not the app. If you want a phishing drill, that is legal plus HR plus a Simulated Events form when the mail flows through a cloud you do not own. It does not belong in the same invoice as API1. Keep the people-test out of sow.checkout-2026q3.yml unless those owners signed.
| Buy | Method you name | Proof you keep |
|---|---|---|
| Web | WSTG v4.2 | Second user on a page, named sink |
| API | API Top 10 2023 | 404 on Bob, 400 on role |
| Mobile | MASVS 2.1.0 + MASTG 2.0.0 | Named control, test id |
| Knowledge | Gray unless you say | Two sessions on day one |
Questions we keep getting
Is black box more “real”?
It is more expensive discovery. Gray with two minted users finds the owner miss faster. Buy black only when you want to know what a stranger sees from a URL and a clock.
Does a mobile buy include the API?
Only if the order says so and you pay for API1 proof. The binary can be clean while Bob still reads Alice on the server. Keep two invoices or one invoice with two evidence files.
Can I accept a ZAP report as the web evidence?
No. ZAP is the tripwire. crovers can run that too. Web evidence is the second user, the sink, and the flow. The tripwire page is the other product.



