Row one of an OWASP Top 10 review is access control: sign in as user A, request user B’s record by its id, and record what comes back. That is how to test OWASP Top 10 vulnerabilities on a small app: 10 rows, one falsifiable test each, the evidence, and a reason for any row that does not apply.
How to test OWASP Top 10 vulnerabilities: the review in one table
An OWASP Top 10 review of a small web app is 10 rows: for each category, one or two tests a person can run and fail, the pass condition, the evidence kept, and either a result or a written reason the category does not apply. Two ordinary accounts and one admin account are enough to run it.
This review is one control in web app security: one step of a web application security audit, and a shared vocabulary for the controls in the application security checklist. Reviewing the application against the OWASP Top 10 and recording findings, fixes and evidence by category is deliverable 3.7 of the Production Hardening Sprint.
The rows follow the OWASP Top 10:2025 list in its published order. That order also answers which category is the most severe: the edition’s introduction says A01 Broken Access Control “maintains its position at #1 as the most serious application security risk”. Each row tests one class of OWASP Top 10 vulnerabilities, not one bug.
| Category (2025) | The test | Pass condition | Evidence to keep | Applies? | Go deeper (testing guide) | Where the fix lives |
|---|---|---|---|---|---|---|
| A01 Broken Access Control | As user A, request user B’s record by id; call an admin action as a member; call each API route with no session | Refused every time, in whatever shape the stack refuses | The request, what came back, both account ids, the date | Always, for any app with accounts | WSTG-ATHZ-04 Testing for Insecure Direct Object References | Authorization rules |
| A02 Security Misconfiguration | On the production build, force an error and read it; read the response headers; look for default accounts, public buckets, debug mode | No stack trace, headers set, nothing public that should be private | Header dump, the error response, a settings screenshot | Always | WSTG-CONF-02 Test Application Platform Configuration | Headers and platform defaults |
| A03 Software Supply Chain Failures | Run the package manager’s audit from the committed lockfile; check the framework version against its advisories | No reachable advisory left without a fix plan; CI installs from the lockfile | The audit output, the framework version, the advisory checked | Always | No dedicated test; nearest is WSTG-INFO-08 Fingerprint Web Application Framework | Dependency audit |
| A04 Cryptographic Failures | The six checks in the cryptographic section below | All six pass | Header output, the password column’s format, search results | Always, if the app stores passwords, personal data or keys | WSTG-CRYP-04 Testing for Weak Encryption | Encryption and password hashing |
| A05 Injection | Search for string-built queries and raw HTML rendering; send a single quote and a harmless tag through each input | No database error; the tag shows as plain text | The search results, the responses | Always, if the app takes input | WSTG-INPV-05 Testing for SQL Injection | Query building and output escaping |
| A06 Insecure Design | Can the client set a price or plan; can a coupon be replayed; can step three come first; is the costliest action limited | Each answer proven with a request | A one-page threat model with the answers | Always | WSTG-BUSL-06 Testing for the Circumvention of Work Flows | Threat model and limits |
| A07 Authentication Failures | Known and unknown email on login and reset; repeated wrong passwords; a reset link used twice; the old session after a password change | Same response for both emails; attempts throttled; link single-use | The responses side by side | Unless a managed provider runs every flow; then record its settings | WSTG-ATHN-03 Testing for Weak Lock Out Mechanism | Login, reset and sessions |
| A08 Software or Data Integrity Failures | Find deserialization of untrusted data; post an unsigned webhook; check fork pull requests against deploy secrets | Unsigned webhook rejected; no fork reaches secrets | The search, the handler’s verification line, workflow permissions | The deserialization part often not, with the search as proof | WSTG-BUSL-03 Test Integrity Checks | Webhook signatures and CI |
| A09 Security Logging and Alerting Failures | Cause failed logins, a denial and an admin action, then find each in the logs | Each event found with who, what, when and where; an alert reaches someone | The log lines | Always | WSTG-BUSL-07 Test Defenses Against Application Misuse | Security logging |
| A10 Mishandling of Exceptional Conditions | Send a malformed body, a wrong type, an oversized payload and a missing field to each write endpoint | A client error with a safe message; no half-written record | The requests, the responses, a read-back | Always | WSTG-ERRH-01 Testing for Improper Error Handling | Error handling and validation |
The go-deeper column names one test from OWASP’s Web Security Testing Guide per row, by its id and title, for when a row needs more than one test can give it. Treat each row as a test case: these OWASP Top 10 test cases are the smallest set that still leaves evidence, and the guide holds the rest. The evidence column is the part a reviewer reads, and evidence for each OWASP category means a request, what came back, the account used and a date. Kept that way, the table doubles as an OWASP Top 10 checklist with the results filled in, and when a customer sends an application security checklist that cites OWASP, it answers them row by row.
How to run an OWASP Top 10 review, in five steps:
- 01 Write the edition and the review date at the top of the record.
- 02 List the app's surfaces: routes, server actions, storage buckets, webhooks, admin screens and any AI feature.
- 03 Run each category's tests against staging with two ordinary accounts and one admin account.
- 04 Record the request, the response and the date for every row.
- 05 Mark a category not applicable only with a written reason tied to this app.
A reason names something about the app: no XML parser anywhere in the dependency tree is a reason, while using a framework is not. On a multi-tenant SaaS, the OWASP security checklist gains one test in row one: change the tenant id in a write and expect a refusal. Checking the OWASP Top 10 against production is a different job with different risks, so every test here runs on staging with test accounts.
Testing OWASP Top 10 vulnerabilities manually, with a browser and those three accounts, is enough for a small app. Among OWASP Top 10 testing tools, a scanner adds coverage for misconfiguration and known-vulnerable components and proves nothing about access control, because it cannot know that user A must not read user B’s record; what a scanner does and does not cover is in a website security check. This is security testing against the OWASP Top 10 at the scale of one app and one reviewer. It is not an OWASP Top 10 pentest checklist in the attacker’s sense: every test runs against an app you own and expects a refusal.
What is OWASP Top 10 in simple terms?
The OWASP Top 10, in simple terms, is an awareness document that ranks 10 classes of web application risk from contributed data and a community survey. It is not a standard to certify against, a test plan or a certificate, so a review needs its own tests and its own evidence.
OWASP’s own words for what the OWASP Top 10 is: “a standard awareness document for developers and web application security” that represents “a broad consensus about the most critical security risks to web applications”. The 2025 edition’s introduction says eight of the ten categories come from contributed data and “the other two categories are from the Top 10 community survey”. The latest OWASP Top 10 for web application security risks is the 2025 edition, which the project page calls “the most current released version”. It ranks risks and names the weaknesses in each; it prescribes no test and grants no certificate, which is why the table above exists (my reading of both pages). Web application security best practices from OWASP live in other projects, such as its Cheat Sheet Series. The 2004 edition was published by the Open Web Application Security Project under the title “The Ten Most Critical Web Application Security Vulnerabilities”, and the organization behind the OWASP Top 10 now calls itself the Open Worldwide Application Security Project; what OWASP is covers the rest of that story.
What goes wrong without it
Picture three requests a small team gets, and what each looks like when nobody has walked the categories:
| What the reviewer asked | What the team had | What follows |
|---|---|---|
| A customer’s security questionnaire asks for a dated OWASP Top 10 review | A scanner report that never signed in as two different users | The access-control answer rests on a tool that never tested access control |
| A diligence reviewer asks for the evidence behind the access-control row | A pass with no request, no response and no date | The reviewer has to treat the row as untested |
| A fix lands for one SQL injection in one handler | No walk through the rest of the category | The same string-built query stays in the other handlers |
Two counts from my own audits sit behind the access-control and components rows. In my June and July 2026 audits, 11 of the 21 third-party apps had unauthenticated endpoints doing privileged work. Across the 26 apps I audited in that period, 9 ran a framework version with a publicly known, reachable RCE or auth bypass, and the fix was usually a one-line version bump. Those apps are a selected set, not a random sample, so read the counts as places to look first, not as a rate for AI-built apps in general. How the same apps landed category by category is in where AI-built apps land on the OWASP Top 10.
How to prevent OWASP Top 10 vulnerabilities is the fix pages’ job, not the review’s: every category below names the page that owns its fix. The twelve checks a builder runs before launch, with their own OWASP mapping, are in the vibe coding security checklist.
How to test each category
Each of the 10 categories gets the same treatment: what it covers in one sentence, a test with an expected result, the evidence to keep, the condition under which it does not apply, and the page that owns the fix. None of the tests needs an attack tool.
Each scope sentence below is quoted from the category’s 2025 page; the tests are mine, written for an app you own and run against staging.
Broken access control
The 2025 page says access control “enforces policy such that users cannot act outside of their intended permissions.” Four tests cover it: as user A, request user B’s object by its id; as an ordinary member, call an admin action directly; with no session at all, call each API route and read each table the client bundle names; and on a multi-tenant app, change tenant_id in a write. The expected result for all four is a refusal.
A refusal has a shape, and the shape depends on the stack. A server route that checks access answers with the error status the app chose, often an unauthorized, forbidden or not-found response. On Supabase, a row-level security using clause that filters a row out “raises nothing, matches zero rows”, so a blocked read comes back empty and a blocked update or delete changes nothing, with no error; a blocked insert is a with check violation and does raise one (42501). So the evidence here is often that no data came back and nothing changed, confirmed by reading the target row back as its owner, the way Supabase’s row-level security guide pairs every denied write with a check that the row is intact. Keep the request, what came back and the two account ids. This row is never not applicable for an app with accounts.
The finding this test catches has its own name, IDOR. Insecure direct object references merged into A5:2017 Broken Access Control, and IDOR sits in A01 of the OWASP Top 10 in 2021 and in 2025, with CWE-639 on both lists.
SSRF moved: the OWASP Top 10 gave SSRF its own category, A10:2021, and the 2025 introduction says Server-Side Request Forgery “has been rolled into” Broken Access Control. To test it, give any feature that fetches a URL (an image import, a link preview, a webhook tester) an internal address you control in staging, and expect a refusal. The other server-side classes are in race conditions and the other vulnerability classes, and the fix for this row belongs to broken access control.
Security misconfiguration
The 2025 page defines it as a system, application or cloud service “set up incorrectly from a security perspective, creating vulnerabilities.” Security Misconfiguration is A02 in the 2025 OWASP Top 10, up from fifth place in 2021. Run these tests on a production build deployed to staging, because development modes are verbose on purpose: Express’s own guide notes that verbose error logging, fine while developing, “can become a security concern in a production environment.”
Request a page that does not exist and force a server error, then read both bodies for a stack trace. Read the response headers against the header list, starting with Content Security Policy without unsafe-inline. Look for default credentials, storage buckets anyone can list, a database port open to the internet, directory listing, debug mode, public source maps, and a .env or .git path that answers over HTTP. Then check the hosting platform’s own settings against what insecure defaults are. The OWASP Top 10 has no infrastructure category of its own; misconfigured servers, storage and cloud services are tested here, in my reading of the A02 scope.
In the OWASP Top 10, XXE (XML External Entities) was its own category, A4:2017, moved into misconfiguration in 2021, and CWE-611 is one of the two notable CWEs of A02:2025. The test is to find any XML parsing in the dependency tree (a SAML login, an SVG or DOCX upload, an RSS import) and confirm the parser’s options disable external entities. With no XML parser anywhere, the XXE part of this row is not applicable and says why. Evidence: the header dump, the error response and a screenshot of the platform settings.
Software supply chain and vulnerable components
The 2025 name is Software Supply Chain Failures: “breakdowns or other compromises in the process of building, distributing, or updating software.” It is the old OWASP Top 10 category for using components with known vulnerabilities, A9 in 2013 and 2017, renamed Vulnerable and Outdated Components as A06:2021 and widened in 2025 to build systems and distribution.
Audit the dependencies from the lockfile that is actually committed, and read the result for reachability, not for the count; the npm audit command covers reading the output. Check the framework’s version against its own security advisories: that single check is the finding behind the 9-of-26 count above. Confirm a lockfile is committed and that CI installs from it, and look at which packages ran install scripts, the route npm malware packages take. Keep the audit output, the framework version, the advisory you checked and the date.
Cryptographic failures: what an encryption failure looks like in a web app, and what to change
Cryptographic failures in a web app come down to 6 checks: plain HTTP or no HSTS, passwords under a fast hash, sensitive fields stored in plain text, a key in the repository, a predictable random source for tokens, and reversible encryption where a hash belongs. Each has a one-line way to see it.
A cryptographic failure, meaning in one sentence: data that cryptography should have protected was left bare, protected with a weak algorithm, or protected with a key someone else can get. The 2025 page’s own list of cryptography issues is “failures related to the lack of cryptography, insufficiently strong cryptography, leaking of cryptographic keys, and related errors.” In the OWASP Top 10, Cryptographic Failures is A04:2025, down from A02:2021; the 2017 edition covered the same ground as A3 Sensitive Data Exposure, and 2021 renamed it for the cause instead of the symptom.
| What is wrong | How to see it | What to change to |
|---|---|---|
| Pages or APIs served over plain HTTP, or no HSTS | Request the http:// address and see whether it redirects; read the response headers for Strict-Transport-Security | HTTPS everywhere, plus the HSTS header, which tells browsers to use HTTPS for the host from then on |
| Passwords under a fast hash (MD5, SHA-1, unsalted SHA-256) | Find the hashing call in the signup code and read a test user’s stored password format | A slow password hashing function: Argon2id first, then scrypt, bcrypt for legacy systems, PBKDF2 where FIPS-140 is required |
| Secrets or personal data stored in plain text | Read a test user’s row in the database console | Field encryption, or the platform’s encryption at rest where that is the expected control |
| A hard-coded key or IV in the repository | Search the code and its history for key names and literal default values | Keys only from a secret store, with no fallback value, rotated if they were ever committed |
Math.random() used for tokens or codes | Search for Math.random( near token, code or id generation | crypto.getRandomValues() in the browser, crypto.randomBytes() in Node |
| Reversible encryption of something that only needs comparing | Look for encrypt and decrypt calls on passwords or API keys | A hash, and for passwords a password hashing function |
The last column answers the best way to prevent a cryptographic failure: each fix is a setting or a library call, not new cryptography. For passwords, the OWASP Password Storage Cheat Sheet gives the order above and the reason: “Fast hashing algorithms such as SHA‑256 are not suitable for password storage because they allow attackers to perform large numbers of guesses quickly.” Password length and composition rules are a separate question: NIST minimum password length. For stored fields, the choice between field encryption and at-rest encryption is in database encryption.
The key row needs the code and the deployed settings read together. In my June and July 2026 audits, a medical app’s login-token signing key had two known values in the repository: the code fell back to a literal default when the variable was unset, and the production stack set the variable to the file path of a container secret, which the backend never read, so the operative key was the path string itself. The report records one caveat: a strong key set outside the repository would bypass both. The lesson I take from it is that neither the code nor the settings screen alone shows which key is live, so the evidence for this row is both, side by side.
The random-source row earns its place from the 2025 page’s own account of cryptographic vulnerabilities: three of the most common CWEs in this category “involved the use of a weak pseudo-random number generator”. CWE-328 is mapped to the Cryptographic Failures category of the OWASP Top 10: it is on the A04:2025 list, under its older name Reversible One-Way Hash, and on the A02:2021 list. MITRE’s CWE-328 entry now calls it Use of Weak Hash: a digest weak enough that someone can find the input, a second input with the same hash, or two inputs that collide. Cryptography vulnerabilities inside a library itself reach you through the components row, as advisories. An encryption failed message on a phone or in an email client is a different topic, a device or mail setting, not this web app category.
MD5 vs SHA-1, and whether either is secure
MD5 and SHA-1 are both broken for collision resistance, so neither is secure for new security work, and comparing the two is the wrong question. Use SHA-256 or stronger for integrity, and a password hashing function for passwords, where even SHA-256 is wrong because it is fast. MD5 still detects accidental corruption.
MD5 was broken in stages that RFC 6151 dates: pseudo-collisions for its compression function in 1993, a collision pair for that function in 1996, the first paper showing two collision pairs for MD5 itself in 2004, and later attacks that “can find MD5 collision in about one minute on a standard notebook PC”. That is why MD5 is insecure: anyone can produce two inputs with the same MD5 hash, and the RFC calls it “no longer acceptable where collision resistance is required such as digital signatures.” The 2025 OWASP page lists MD5 and SHA1 as “deprecated hash functions”.
SHA-1 is no more secure for new work. NIST’s retirement of SHA-1 says collision attacks “have been used to undermine SHA-1 in recent years” and that SHA-1 “should be phased out by Dec. 31, 2030”; NIST’s hash functions page adds that it deprecated SHA-1 in 2011 and disallowed it for digital signatures at the end of 2013. Between MD5, SHA-1 and SHA-256, pick SHA-256 for integrity; NIST’s advice to anyone relying on SHA-1 is to “migrate to SHA-2 or SHA-3 as soon as possible.”
Speed is no reason to pick either. For passwords, speed is the defect: the password cheat sheet says slow hashes are the point, “unlike algorithms such as MD5 and SHA-1, which were designed to be fast”. MD5 keeps one honest use: RFC 6151 says that where an MD5 checksum is used inline with a protocol “solely to protect against errors, an MD5 checksum is still an acceptable use.” Label it in the code as a corruption check, never as a security control.
SHA-256 in JavaScript, checksums and SRI hashes
In JS, SHA-256 needs no library and no checksum generator site. The browser has crypto.subtle.digest(), available only in secure contexts per SubtleCrypto.digest on MDN, and Node has createHash('sha256') in Node’s crypto module. A file checksum is sha256sum (GNU coreutils) or shasum -a 256, where plain shasum defaults to SHA-1.
// Browser (secure contexts only)
await crypto.subtle.digest('SHA-256', new TextEncoder().encode(text))
// Node.js: import { createHash } from 'node:crypto'
createHash('sha256').update(text).digest('hex')
# Shell: a file checksum, then an SRI value for a script
sha256sum app.js
cat app.js | openssl dgst -sha384 -binary | openssl base64 -A
No SHA integrity generator site is needed either. A Subresource Integrity value is the algorithm name, a dash and the base64 digest; MDN’s Subresource Integrity guide allows sha256, sha384 and sha512 and gives the OpenSSL command above. SHA-256 itself is not retired: NIST’s hash functions page lists it among the approved SHA-2 algorithms of FIPS 180-4, with 128 bits of collision resistance against under 80 for SHA-1. Pasting a secret into an online hash generator is the failure this category is about, since the value has left your control.
Injection, including SQL injection and XSS
SQL injection is classified under Injection, A03 in the 2021 edition, and cross-site scripting joined the same category that year. Both are tested without an attack tool: look for string-built queries and unescaped rendering, then confirm a single quote causes no database error and a tag shows as plain text.
In the OWASP Top 10, injection is A05:2025, down from third place in 2021, and the 2025 page defines it as “an application flaw that allows untrusted user input to be sent to an interpreter (e.g. a browser, database, the command line) and causes the interpreter to execute parts of that input as commands.” The 2021 OWASP Top 10 classification name for SQL injection is A03:2021 Injection; in 2017 it was A1:2017 Injection, and XSS had its own OWASP Top 10 entry, A7:2017, until 2021 folded it into Injection. Cross-site scripting stays in the OWASP Top 10 under Injection in 2025, which the page describes as “high frequency/low impact” next to SQL injection’s “low frequency/high impact”.
The tests stay defensive. Search the code for string concatenation or template literals inside query calls, and for raw-query helpers such as DB::raw, RawSQL or a Supabase .or() or .filter() built from user input. Send a single quote through each search and filter field: the expected answer is a normal response, results or a validation message, never a database error. For XSS, save <b>test</b> in every user-controlled field that is shown back later and confirm it displays as the literal text, then search for dangerouslySetInnerHTML, v-html and innerHTML. Keep the search results and the responses. The fixes are SQL injection prevention and how to mitigate cross-site scripting.
Insecure design
Insecure design is a missing control, not a bug in one. The 2025 page puts it plainly: “An insecure design cannot be fixed by a perfect implementation as needed security controls were never created to defend against specific attacks.” In the OWASP Top 10, insecure design is A06:2025; it first appeared as A04:2021. Business logic flaws belong to this OWASP Top 10 row, since the page says the category “includes flaws in the business logic of an application”.
The tests are questions with a yes or no and a request as proof. Can a price, a quantity or a plan be set from the client? Will a coupon, a trial or a referral work twice? Can a multi-step flow be entered at step three? Is there any limit on the most expensive action (rate limiting in API endpoints)? The evidence for this row is a one-page threat modeling note with each answer and its proof, and no app gets to mark this row not applicable.
Authentication failures
The 2025 page: “When an attacker is able to trick a system into recognizing an invalid or incorrect user as legitimate, this vulnerability is present.” Broken authentication in the OWASP Top 10 was A2:2017 Broken Authentication, became A07:2021 Identification and Authentication Failures, and is A07:2025 Authentication Failures.
Try a known and an unknown email on login and on password reset and compare the answers; the page asks for “the same messages for all outcomes”. Send repeated wrong passwords and expect a throttle or a lockout. Use a reset link twice, and once after a long wait. Change the password and replay the old session, remembering that a stateless access token stays valid until it expires, so wait out its lifetime before calling that a failure; that difference is part of JWT security. Check that admin accounts need a second factor. Keep the responses side by side. The fixes live in authentication best practices and the JWT page. When a managed provider runs every flow, the row records the provider’s settings instead of a pass.
Software or data integrity failures
The 2025 scope: integrity failures “relate to code and infrastructure that does not protect against invalid or untrusted code or data being treated as trusted and valid.” In the OWASP Top 10 2021, the category that includes insecure deserialization is A08:2021 Software and Data Integrity Failures, which absorbed A8:2017 Insecure Deserialization; 2025 renamed it Software or Data Integrity Failures.
Find any place the app turns untrusted data into objects (pickle.loads, Java deserialization, yaml.load, eval on JSON); a TypeScript app often has none, and the row says so with the search as proof. Post an unsigned request to each webhook endpoint and expect a rejection. Check that third-party scripts on pages that take payments carry an integrity attribute. Check whether a pull request from a fork can reach deploy secrets, which is covered in CI CD security best practices, and whether any auto-update channel checks a signature. Evidence: the search, the webhook handler’s verification line and the workflow permissions.
Logging and alerting failures
The 2025 page: “Without logging and monitoring, attacks and breaches cannot be detected, and without alerting it is very difficult to respond quickly and effectively during a security incident.” Cause five failed logins, one access-control denial and one admin action, then search the logs for each straight away, looking for who, what, when and from where. Straight away matters: Vercel keeps runtime logs for 1 hour on Hobby and 1 day on Pro, so a search made the next day can come back empty even when logging works. Confirm no password, token or card number appears in those lines, and that an alert reaches a person. The log lines are the evidence, and how to prevent insufficient logging and monitoring owns the fix.
Codes without a year mislead most in this part of the list. OWASP Top 10 A5 was Broken Access Control in 2017 and Security Misconfiguration in 2021; OWASP Top 10 A6 was Security Misconfiguration in 2017 and Vulnerable and Outdated Components in 2021; in 2025 they are Injection and Insecure Design. The crosswalk below settles any code.
Mishandling of exceptional conditions
The 2025 page: it “happens when programs fail to prevent, detect, and respond to unusual and unpredictable situations, which leads to crashes, unexpected behavior, and sometimes vulnerabilities.” Send a malformed body, a wrong type, an oversized payload and a missing required field to each write endpoint. Expect a client-error status with a safe message, never a 500 error with a stack trace and never a half-written record; the two fixes come from API error handling best practices and input validation in a web app.
In the OWASP Top 10, input validation had its own entry in the 2004 list, A1 Unvalidated Input, and has none in the 2013, 2017, 2021 or 2025 lists. Validating data input is now the control behind this OWASP Top 10 category and behind injection: CWE-20 Improper Input Validation is mapped to A05:2025, and the A10 page says exceptional conditions “can be caused by missing, poor, or incomplete input validation”. Denial of service was A9 in the 2004 OWASP Top 10 and is not a current category; the 2025 introduction treats it as a symptom, so resource exhaustion is tested under insecure design and rate limits. Buffer overflow was A5 in the 2004 OWASP Top 10, a memory-safety flaw that application code in JavaScript, Python or C# rarely meets in my reading (the 2004 list itself called Java environments “immune to these attacks (except for overflows in the JVM itself)”), and it still matters in native dependencies, which the components row covers.
Which OWASP Top 10 category a finding falls under
A finding’s OWASP category depends on the edition, so a code without its year is ambiguous: the same code has meant different things in different editions. The crosswalk maps 20 common findings to their 2017, 2021 and current codes, and names the ones that were never on the list.
A report line that cites A3 alone could mean Sensitive Data Exposure (2017), Injection (2021) or Software Supply Chain Failures (2025). The 2021 codes come from the 2021 edition and its category lists, and the 2017 codes from the 2017 category pages and release notes.
| Finding | 2017 | 2021 | 2025 | Where to test it |
|---|---|---|---|---|
| SQL injection | A1 Injection | A03 Injection | A05 Injection | Injection |
| Cross-site scripting (XSS) | A7 XSS | A03 Injection | A05 Injection | Injection |
| Cross-site request forgery (CSRF) | none (A8 in 2013, dropped) | A01 Broken Access Control | A01 Broken Access Control | Broken access control |
| XML external entities (XXE) | A4 XXE | A05 Security Misconfiguration | A02 Security Misconfiguration | Security misconfiguration |
| Server-side request forgery (SSRF) | none | A10 SSRF | A01 Broken Access Control | Broken access control |
| Insecure direct object reference (IDOR) | A5 Broken Access Control | A01 Broken Access Control | A01 Broken Access Control | Broken access control |
| Open redirect | none (A10 in 2013, dropped) | A01 Broken Access Control | A01 Broken Access Control | Broken access control |
| Path traversal | A5 Broken Access Control | A01 Broken Access Control | A01 Broken Access Control | Broken access control |
| Insecure deserialization | A8 Insecure Deserialization | A08 Software and Data Integrity Failures | A08 Software or Data Integrity Failures | Software or data integrity failures |
| Weak hash (CWE-328) | A3 Sensitive Data Exposure | A02 Cryptographic Failures | A04 Cryptographic Failures | Cryptographic failures |
| Sensitive data exposure (the 2017 name) | A3 Sensitive Data Exposure | A02 Cryptographic Failures | A04 Cryptographic Failures | Cryptographic failures |
| User enumeration | A2 Broken Authentication (my mapping) | A07 Identification and Authentication Failures | A07 Authentication Failures | Authentication failures |
| No limit on login attempts | A2 Broken Authentication | A07 (CWE-307) | A07 (CWE-307) | Authentication failures |
| Information disclosure in error messages | A6 Security Misconfiguration | A04 Insecure Design (CWE-209) | A10 Mishandling of Exceptional Conditions (CWE-209) | Mishandling of exceptional conditions |
| Default credentials | A6 Security Misconfiguration | A05 Security Misconfiguration | A07 Authentication Failures (CWE-1392) | Security misconfiguration (run the test there; file the finding under A07 in a 2025 record) |
| Outdated framework with a known flaw | A9 Using Components with Known Vulnerabilities | A06 Vulnerable and Outdated Components | A03 Software Supply Chain Failures | Software supply chain and vulnerable components |
| Unsigned webhook accepted | none | A08 (CWE-345) | A08 (CWE-345) | Software or data integrity failures |
| Security events not logged | A10 Insufficient Logging & Monitoring | A09 Security Logging and Monitoring Failures | A09 Security Logging and Alerting Failures | Logging and alerting failures |
| Business logic abuse | none | A04 Insecure Design (CWE-840) | A06 Insecure Design | Insecure design |
| Prompt injection | not on the web list | not on the web list | not on the web list; LLM01:2025 in the LLM list | See the LLM links below |
Where the 2017 column says none, no 2017 category covers the finding, in my reading of that edition’s ten; the 2021 and 2025 codes follow each category’s own CWE list or text. CSRF left the OWASP Top 10 in 2017: it was A8 in 2013, the 2017 release notes dropped it because “many frameworks include CSRF defenses, it was found in only 5% of applications”, and since 2021 its CWE-352 sits under A01. The test and the fix are both a matter of CSRF mitigation. Open redirect took a similar road in the OWASP Top 10 2021: A10 in 2013 as Unvalidated Redirects and Forwards, dropped in 2017, then mapped to A01 through CWE-601. Information disclosure in the OWASP Top 10 crosses three categories: an error message that leaks internals was a misconfiguration in 2017, an insecure-design CWE in 2021 and an exceptional-conditions CWE in 2025.
The OWASP Top 10 2025 vs 2021, in three lines from the edition’s introduction, which counts “two new categories and one consolidation”:
- Server-Side Request Forgery, A10:2021, was rolled into A01 Broken Access Control.
- Software Supply Chain Failures (A03:2025) expands A06:2021 to the whole chain of dependencies, build systems and distribution, and Mishandling of Exceptional Conditions (A10:2025) is new.
- Security Misconfiguration moved from fifth to second, Cryptographic Failures fell from second to fourth, and Authentication Failures and Security Logging and Alerting Failures took new names.
Between the OWASP Top 10 2021 and 2017, the moves were merges and renames: Sensitive Data Exposure became Cryptographic Failures, XSS joined Injection, XXE joined Security Misconfiguration, Insecure Deserialization joined the new Software and Data Integrity Failures category, and Insecure Design and SSRF arrived as the other two new categories.
The OWASP Top 10 vs the SANS Top 25 is a comparison of different units. The OWASP Top 10 ranks ten risk categories for web applications, while the CWE Top 25, run by MITRE’s CWE program and published as the CWE/SANS Top 25 in its 2009 to 2011 editions, ranks specific weaknesses across all software from CVE Records, 39,080 of them in its 2025 dataset. The bridge is the CWE id: each Top 10 category lists the CWEs it contains, 248 of them across the 2025 edition, so a CWE is the precise name and the category is its group.
The Top 10 is the web list. The API list is a separate document, mapped in the API security checklist for AI-built apps, and so is mobile, in mobile app security best practices. Prompt injection is on neither: the 2025 Injection page sends it to the LLM list as LLM01:2025, which the OWASP LLM Top 10 and what is prompt injection cover.
The same review on Django, Laravel, Node.js, ASP.NET Core and Java
A framework closes some rows by default and none of them all. The table uses each framework’s own security documentation; the Next.js with Supabase row is there because it is a common stack for apps made with AI builders (my reading).
| Framework | On by default, in its own docs | Still needs the test |
|---|---|---|
| Django | Template auto-escaping against most XSS; protection against most CSRF attacks when enabled and used where appropriate; querysets built with query parameterization; X-Frame-Options middleware | Raw SQL, extra() and RawSQL; login throttling, which Django does not do; access control, design, logging, dependencies |
| Laravel | The request forgery middleware in the web middleware group; PDO parameter binding in the query builder | DB::raw and other raw expressions; routes outside the web group; access control, design, logging, dependencies |
| Express on Node.js | Little: it sends an X-Powered-By header by default, and Helmet sets security headers once you add it | Input validation, headers, CSRF, access control, design, logging, dependencies |
| ASP.NET Core | Razor encodes output from variables; CSRF middleware on by default in apps built with WebApplication.CreateBuilder | JSON endpoints, which that middleware does not reject cross-origin; Html.Raw; access control, design, logging, dependencies |
| Java | Depends on the framework; not stated in the cheat sheet index | The Java Security, Injection Prevention in Java and Deserialization cheat sheets as the test list |
| Next.js with Supabase | React DOM escapes values embedded in JSX; most Supabase filter methods take values rather than raw syntax | dangerouslySetInnerHTML; .filter(), .not() and .or() values, which Supabase says must be sanitized; row-level security on every table |
Django’s security documentation points readers to the OWASP Top 10 itself and says that while Django has tools for some of the issues, “other issues must be accounted for in the design of your project.” For Laravel, the OWASP Top 10 rows the docs close are CSRF, through the middleware described in Laravel’s CSRF protection, and SQL injection through query binding. On Node.js with Express, the OWASP Top 10 review starts from almost nothing on by default; Express’s security best practices say the responsibility for handling user input “is yours”. ASP.NET Core security names features for four OWASP Top 10 problems: XSS, SQL injection, CSRF and open redirects. For Java, OWASP’s cheat sheets are the nearest thing to a framework page: the OWASP Cheat Sheet Series has Java Security and Injection Prevention in Java as sheets of their own.
The rows no framework closes are access control, insecure design, logging and the supply chain, in my reading of the documents above. The tests do not change with the stack; only the search patterns and the shape of a refusal do.
How to verify it
An OWASP review is verified by 5 checks on the record itself: every row has a result or a justified not-applicable, every pass has evidence someone can re-run, every fail has a retest, the edition and date are stated, and a second person reproduces three rows.
- 01 Every one of the ten rows has a result: pass, fail with a finding id, or not applicable with a reason a stranger would accept.
- 02 Every pass has evidence someone else can re-run: the request, what came back, the date and the account used.
- 03 Every fail has a fix, a retest and the retest's evidence.
- 04 The edition and the review date are on page one, and every finding carries its code with the year.
- 05 A second person re-runs three rows picked at random and gets the same results.
A review with ten passes and no evidence fails this section. The record is also not an OWASP Top 10 penetration test: a review records a test per category, while a penetration test probes chosen surfaces the way an attacker would, which web application penetration testing covers, and a pentest scoped to the OWASP Top 10 is still that other job. Neither one is a certificate. The reviewer’s view of the code itself is a separate job: the secure code review checklist.
In the Production Hardening Sprint, deliverable 3.7 is verified this way: deliver category-level results and supporting test evidence, with justified non-applicable cases identified.
Where the sprint does this
Deliverable 3.7 of the Production Hardening Sprint reviews the application against the OWASP Top 10 and records findings, fixes and evidence by category. The targeted security tests cover the five priority attack surfaces documented in the report, and the package also includes an OWASP review and a controls checklist with evidence. The production readiness report, deliverable 13.1, accounts for all 123 IDs, keeps failures visible until resolved and explains genuine non-applicable items. Formal third-party certifications and independent audit opinions are separate from the sprint deliverables. The full list of deliverables is in the published scope.
Common questions about an OWASP review
What is the OWASP Top 10 used for?
The OWASP Top 10 is used as what OWASP calls it, an awareness document, and in practice as a shared vocabulary for findings and a baseline that customers and reviewers ask about. It tells a team which classes of web risk to look at first; it does not say how to test them, which is why a review adds its own tests and evidence.
What are the latest vulnerabilities?
The Top 10 ranks classes of weakness, not individual flaws, so it does not track this week’s vulnerabilities. The recent ones that touch your app come from your dependency audit and your framework’s security advisories, which is the supply chain row of the review.
What are the top 10 principles of OWASP?
The OWASP list of things to build in, rather than risks to avoid, is the OWASP Top 10 Proactive Controls, written for developers first, with 2024 as its latest numbered edition. Its stated aim is “to raise awareness about application security by describing the most important areas of concern that software developers must be aware of.”
The list itself is at the OWASP Top 10 Proactive Controls.
What is an OWASP vulnerability?
An OWASP vulnerability is not a formal type: it means a weakness that falls under one of the Top 10 categories. The precise name is its CWE id, which each category page lists, such as CWE-89 for SQL injection under A05:2025.
Is OWASP Top 10 a legal requirement?
Not in itself: OWASP describes the Top 10 as an awareness document for developers, not as a law or a certification. Whether a contract, a customer questionnaire or another standard you follow requires it depends on that document, and this is general information, not legal advice.
The checks in this guide show you where the app is open. The sprint below closes those gaps, tests the result and writes the evidence down.
Built it with AI. Now it has to hold up for real customers.
The Production Hardening Sprint takes the app you already have and builds the production foundation underneath it. Authentication and access rules, payments that stay consistent, error handling, monitoring, backups, automated tests and a documented handover. Our engineers work inside your existing codebase for ten working days. All 123 deliverables are included, and you get the evidence for each one.
See the Production Hardening Sprint →
$2,500 fixed price · 10 working days · One codebase