In short
Broken access control refers to a class of security vulnerabilities in which an application knows who a user is but does not consistently check what this user is allowed to do. A logged-in customer reads other people's orders, an editor grants themselves admin rights, a script calls a function that should only be visible in the back end.
The category has led the OWASP Top 10 since 2021 and is again in first place as A01 in the 2025 edition.
The term is often confused with "poor login security". That is not the point. Authentication answers the question "Who are you?", authorisation the question "What are you allowed to do?". Broken access control is the failure at the second question. The application has correctly recognised the user and still lets them reach data or functions to which they are not entitled.
That is exactly why the category is so persistent: no framework can solve it for you. Shopware does not know that customer 4711 may not see the invoice of customer 4712. NestJS provides guards, but a guard that nobody attaches to a route protects nothing. The rules about who may do what come from your business domain, and only you can cast them in code.
Why broken access control has been number 1 since 2021
OWASP assigns a total of 40 CWEs to category A01:2025, i.e. 40 different vulnerability patterns from the MITRE catalogue. In the test data, the average incidence rate was 3.74 per cent, and up to 20.15 per cent in individual data sets. Behind this are 1,839,701 individual findings and 32,654 published CVEs, more than in any other category (source: OWASP Top 10:2025, A01).
At OWASP, the incidence rate means the proportion of tested applications in which at least one vulnerability of this category was found. 3.74 per cent sounds moderate. The number of findings shows the real picture: where access control breaks, it rarely breaks in one place but at dozens of endpoints at the same time, because the same missing pattern was copied throughout the code.
New in the 2025 edition is that server-side request forgery is no longer listed as a category of its own but is absorbed into A01 as CWE-918. The logic behind this: a server that fetches arbitrary URLs on request crosses an access boundary, just inwards instead of outwards. Other prominent CWEs in the category are CWE-200 (exposure of sensitive information), CWE-352 (CSRF) and CWE-639 (authorisation bypass through a user-controlled key).
The most important forms
Broken access control is not a single vulnerability but an umbrella term. The forms differ in which boundary is crossed: the one between two users, the one between roles, the one between browser and server or the one between application and internal network. The following overview classifies the six most common patterns.
| Form | CWE | What happens | Typical location |
|---|---|---|---|
| IDOR (insecure direct object reference) | CWE-639 | User changes an ID in the URL or body and receives other people's records | GET /orders/:id, invoice downloads, profile endpoints |
| Privilege escalation | CWE-269 | User with a low role obtains the rights of a higher role | User management, role assignment, admin APIs |
| Forced browsing | CWE-425 | Direct call of a page or route that is not shown in the menu | /admin, export routes, hidden reports |
| CSRF (cross-site request forgery) | CWE-352 | A third-party website triggers an action in the name of the logged-in user | Forms without a token, cookie-based sessions |
| SSRF (in A01 since 2025) | CWE-918 | Server fetches a URL specified by the client and reaches internal services | Image proxy, webhook tester, media import |
| Mass assignment | CWE-915 | Request body sets fields that the user should not be allowed to set | Update endpoints that accept entire objects |
IDOR: the classic of the category
IDOR is the most common pattern and at the same time the easiest to test. The route checks whether a valid token is present, but not whether the requested record belongs to the token holder. A tester increments the ID by one and sees what happens. MITRE describes CWE-639 as a case in which the authorisation function does not prevent "one user from gaining access to another user's data or record by modifying the key value" (CWE-639, MITRE).
Mass assignment: the silent escalation of rights
Mass assignment is IDOR in the other direction. The problem is not read access but write access to fields that the user should not even see. An update endpoint accepts a complete object, maps it to the database, and suddenly the field role or aclRoles is writable. The pattern is treacherous because the normal request looks completely harmless. Only an additional field in the JSON turns it into an escalation of rights.
How broken access control arises in shops and APIs
An online shop has at least three access boundaries: between customers, between customer and back end, and between back-end roles. Each of them must be checked at every endpoint that reads or writes data. Shopware's Store API is publicly accessible, and the sales channel key is in the client of every headless storefront. Whatever can be reached there without a context token can be reached by the world.
In the admin area, the problem shifts to the roles. Shopware comes with a fine-grained ACL system, but the question of whether a user who may edit users may also assign roles that go beyond their own is a business question. If it is not asked in the code, privilege escalation arises. The same applies to integrations and apps: anyone who may subscribe to events does not automatically get the right to read the data of these events.
In a NestJS back end, the gap usually arises between guard and service. The guard checks the JWT, the controller takes the ID from the URL, the service loads the record. Nowhere does anyone ask whether order.customerId matches request.user.id. The code is clean, the tests are green, and every logged-in user sees all orders.
A second typical place where it arises is front ends that only enforce permissions in the interface. The "Delete" button is hidden, but the DELETE endpoint behind it checks nothing. OWASP names exactly this scenario, the attempt to bypass via direct API calls, as one of three example attacks for A01. An attacker needs no tool for this other than the developer tools of their browser.
Real-world examples from 2021 and 2026
On 25 August 2026, Shopware closed a vulnerability with 6.7.13.1 and 6.6.10.23 that is listed as the successor to CVE-2026-48010: an authenticated admin user with the right to edit users could assign additional roles beyond their own rights via the field aclRoles in the update request. CVSS 6.5, pattern mass assignment plus privilege escalation (GHSA-4wpv-5fvv-c3xp).
Three weeks later, on 16 September 2026, a more serious case followed with 6.7.14.1 and 6.6.10.25. Users or integrations with the right to create webhooks received event data for which they had no read permission: customer data, orders and tokens for account recovery. CVSS 9.6, i.e. critical. As a workaround, Shopware recommended restricting the webhook right to trusted administrators and reviewing existing webhook registrations (GHSA-r432-q883-wgvf).
Both cases have one thing in common: the gap was not in the login but one level behind it, in the question of whether an already legitimised actor is allowed to carry out a particular operation. That is the core of broken access control, and it is the reason why the category keeps appearing even in mature products with a professional security process.
An example outside Shopware: in January 2021, the researchers at Pen Test Partners reported to Peloton that the endpoints /stats/workouts/details and the GraphQL interface released user IDs, location, age, gender and workout data without authentication. The first fix in February 2021 required a login, which meant that the data remained readable for all three million subscribers, even for profiles set to "private". Only after publication on 5 May 2021 was the authorisation really fixed (Pen Test Partners, 2021).
The Peloton case is instructive because it shows the most common misconception: adding authentication does not solve an authorisation problem. The question "May this user see this record?" was just as unanswered after the first fix as before.
Countermeasures that work
The most effective single measure is called deny by default: every route is blocked until someone explicitly releases it. In NestJS this means a globally registered auth guard from which public routes are exempted via a decorator, not the other way round. Anyone who forgets to mark a route then gets a 403 instead of a data leak. The OWASP documentation names this principle first, together with reusing a central mechanism instead of individual solutions per controller (OWASP Authorization Cheat Sheet).
The second measure is the server-side ownership check. A guard checks the role, but the question "Does this record belong to this user?" can only be answered with the loaded record. That is why the check belongs in the service or in the database query itself: the query filters on customerId = :currentUser from the outset, so that other people's records are not loaded in the first place. In this variant, a 404 for other people's IDs is not an error but the desired behaviour.
// NestJS: ownership check in the service, not just a role check in the guard
async findOrderForUser(orderId: string, userId: string) {
const order = await this.orders.findOne({
where: { id: orderId, customerId: userId },
});
if (!order) throw new NotFoundException(); // other people's IDs look as if they do not exist
return order;
}
The third building block is a role model that follows the principle of least privilege. RBAC is sufficient for most applications in mid-sized companies, as long as the roles are cut narrowly and the assignment of rights is itself subject to rights control. The Shopware case from August 2026 shows exactly this gap: the right "edit users" must not implicitly include the right "assign any roles".
For mass assignment: update endpoints accept DTOs with a whitelist, not entire entities. In NestJS, this is done by the ValidationPipe with whitelist: true and forbidNonWhitelisted: true, which not only ignores unknown fields but rejects the request. Fields such as role, aclRoles or isAdmin do not belong in any user DTO.
The fourth building block is tests that explicitly check authorisation. One integration test per resource that accesses user B's record with user A's token and expects a 403 or 404 takes a few minutes and catches the majority of IDOR cases. OWASP also recommends logging failed access checks and raising an alarm when they pile up: anyone who tries out ranges of IDs leaves a clear pattern.
Finally, SSRF requires separate treatment, because here it is not the user but the server that crosses the boundary. URLs supplied by the client are checked against an allowlist, internal address ranges and the metadata address of cloud providers are blocked, and DNS is resolved again at the time of the connection. Details can be found in the entry on server-side request forgery.
Anyone running a Shopware or NestJS system with many roles and integrations should have the rights model reviewed in a structured way once. In enterprise software development, this is part of the architecture work, not of the rework afterwards.
Distinction from related terms
Identification and authentication failures (A07:2025) concern the question of whether the application recognises a user correctly: weak passwords, missing multi-factor authentication, session tokens in the URL, no protection against credential stuffing. Broken access control, by contrast, presupposes that recognition has worked and describes what goes wrong afterwards. The Peloton case makes the boundary visible: after the first fix, A07 was solved, A01 was not.
Security misconfiguration (A02:2025) covers incorrectly set defaults, open debug modes, default passwords and missing hardening. The overlap with A01 is real: an admin panel that is publicly accessible because of missing configuration can fall under both categories. The rule of thumb: if the cause lies in a setting outside your own code, it is misconfiguration. If it lies in a missing check in the code or in the rights model, it is broken access control.
Injection (A05:2025) is a different mechanism: here, injected code is executed by the interpreter. However, injection can lead to access boundaries falling as a consequence, for example when an SQL injection reads the data of other tenants. OWASP classifies by cause, not by effect.
Frequently asked questions
Can broken access control be found by a scanner?
Only partly. Automatic scanners reliably find forced browsing and missing CSRF tokens. IDOR and privilege escalation require knowledge of which user is allowed to see which data, and no scanner has this knowledge. That is why two test accounts with different roles and a set of integration tests that deliberately request other people's IDs belong in every test pipeline. A penetration test with a manual component complements this but does not replace it.
Is a JWT guard in NestJS enough against broken access control?
No. A guard answers whether a valid token is present and, optionally, whether a role matches. The question of whether the requested record belongs to the user can only be answered by the service that knows the record. If the ownership check is missing in the service, the back end remains open to IDOR, however clean the authentication is. The documentation explicitly describes guards as a decision about access to the route on the basis of runtime conditions, not as a check of individual records (NestJS guards).
Why has SSRF counted as broken access control since 2025?
OWASP introduced SSRF as a category of its own, A10, in 2021 and integrated it into A01 in 2025, because the data basis no longer justified a separate category and the pattern fits in terms of content: the server crosses an access boundary that it should not cross, just in the direction of internal services instead of other users' data. In practice, this changes nothing about the countermeasures, but it brings SSRF into the focus of teams that have so far only checked user rights.