DEV Community

Cover image for OWASP Top 10 for Shopware and NestJS Projects: What Actually Lands in Your Stack
Slawa
Slawa

Posted on Originally published at next-levels.de

OWASP Top 10 for Shopware and NestJS Projects: What Actually Lands in Your Stack

TL;DR: The OWASP Top 10 2025 is a risk ranking across 2.8 million tested applications, not a checklist for your project. In a Shopware or NestJS stack, four categories dominate: Broken Access Control, Security Misconfiguration, Software Supply Chain and Injection. Shopware's security releases of August and September 2026 cover six of the ten categories, and npm did not switch off its install scripts for no reason.

On 25 August 2026, Shopware shipped a security release with ten fixes. One of them, found by the researchers at Sansec, let an attacker without a customer account inject SQL through the Store API and work their way up to the admin account and code execution. All it took was an active sales channel key, and every headless storefront hands that key to its clients as part of normal operation (Sansec, 25 Aug 2026).

Hold that release against the OWASP Top 10 and, together with its successor of 16 September, it covers six of the ten categories. The OWASP Top 10 is the most-cited and most-misused document in application security: a team pins the ten items to the wall as a checklist, ticks ten boxes and is exactly as exposed as before. The list is a ranking of risk categories across millions of applications, an awareness document, as OWASP itself puts it.

So the question for your project is: which of these ten categories show up in your stack, in what form, and where in the code or configuration? For Shopware and NestJS that question has an answer, because both are mature, open systems with a public advisory history.

OWASP Top 10 in a Shopware and NestJS stack: the four categories that really land. Ten checkmarks are not security.

Ten checkmarks are not security.

What the OWASP Top 10 2025 is, and what it is not

The eighth edition was presented as a release candidate at OWASP Global AppSec in Washington on 6 November 2025 and finalised in January 2026 (OWASP Top 10:2025). The data set covers more than 2.8 million tested applications and 589 weakness types (CWEs). Eight of the ten categories come from that data, two from a community survey, because OWASP explicitly considers the data incomplete.

"Incidence rate" at OWASP means the share of applications in which at least one weakness of the category was found. Broken Access Control averages 3.74 percent, with 20.15 percent in individual data sets. That sounds small. Behind it stand 1.84 million individual findings.

Three shifts since 2021 define this edition. Software Supply Chain Failures is new at number 3 and replaces "Vulnerable and Outdated Components", expanded to compromised packages, tampered builds and insecure updates. In the community survey, exactly 50 percent of respondents put this category at number 1. Security Misconfiguration climbed from 5 to 2.

Server-Side Request Forgery was folded into Broken Access Control as a category of its own. New at number 10 is "Mishandling of Exceptional Conditions": stack traces in the response, resources not released after an exception, 24 CWEs that previously ran under "poor code quality".

OWASP 2025 Change since 2021 Where it lands in a Shopware/NestJS stack
A01 Broken Access Control stays at 1, absorbs SSRF Admin ACL, integrations, Store API routes; NestJS guards, ownership checks
A02 Security Misconfiguration from 5 to 2 APP_ENV, trusted hosts, CSP, CORS, debug endpoints
A03 Software Supply Chain Failures new (replaces A06:2021) Composer, npm, plugins, apps, CI pipelines
A04 Cryptographic Failures from 2 to 4 secrets in the repo, token lifetimes, TLS termination
A05 Injection from 3 to 5 raw SQL in plugins, Twig, TypeORM query builder
A06 Insecure Design from 4 to 6 checkout logic, discount rules, workflow states
A07 Authentication Failures renamed password reset, rate limiting, JWT handling
A08 Software or Data Integrity Failures unchanged webhooks without signature checks, unsigned deployments
A09 Security Logging & Alerting Failures renamed logs nobody alerts on, missing audit trails
A10 Mishandling of Exceptional Conditions new exception filters, stack traces, dangling transactions

Two stacks, one attack surface

Shopware 6 has three public doors, the storefront, the Store API and the Admin API, plus an extension system in which foreign code runs: plugins with full PHP access and apps with Twig-based app scripts in a sandbox. A NestJS backend defines its attack surface almost entirely through what you declare: controllers, DTOs, guards, pipes. The framework itself brings little attack surface, but it brings npm.

The Shopware releases 6.7.13.1 and 6.6.10.23 of 25 August (ten fixes, nine of them also in 6.6) and 6.7.14.1 and 6.6.10.25 of 16 September (five more) are a better map of the real attack surface than any abstract list, six categories in two releases (release notes 6.7.13.1, 6.7.14.1):

Shopware fix (Aug/Sep 2026) OWASP category Severity (CVSS)
App Script sandbox escape, arbitrary PHP and OS commands A03 Supply Chain / A05 Injection CVSS 9.6
Password reset poisoning via the Host header A02 Misconfiguration / A07 Authentication CVSS 9.3
SQL/DDL injection via custom entity field names (app manifest) A05 Injection CVSS 9.1
SQL injection in Store API aggregations, sales channel key sufficient A05 Injection CVSS 8.6
Path traversal via writable media.fileExtension, up to RCE A01 Access Control CVSS 8.0
SSRF via DNS rebinding in media import and app system A01 Access Control (SSRF) CVSS 6.3 / 3.1
Privilege escalation via ACL role mass assignment, profile update, Sync API A01 Access Control CVSS 6.5 to 8.1
Webhook permission bypasses event ACLs A01 Access Control CVSS 9.6
Unpublished product reviews readable A01 Access Control CVSS 5.3
Missing rate limiting on guest document download A07 Authentication CVSS 3.7
Newsletter double opt-in bypass A06 Insecure Design moderate

Shopware security releases August and September 2026: 15 fixes cover six of the OWASP Top 10 categories

Two Shopware releases, six OWASP categories (source: Shopware release notes 6.7.13.1 / 6.7.14.1)

A01 Broken Access Control: the category you build yourself

Broken Access Control has been number 1 since 2021, and it is the category where neither Shopware nor NestJS can do the work for you. The framework does not know that customer 4711 must not see the invoice of customer 4712.

In Shopware, access control runs through three mechanisms: the Administration ACL, the permissions of integrations and apps, and the question of which entity fields are visible through the Store API at all. All three were affected in the recent releases.

The privilege escalation via role mass assignment means: an admin user who was allowed to edit users could assign additional roles through the aclRoles field in an update request, roles beyond their own rights, because the field was not checked against their permission. The webhook fix means: an app that was only allowed to subscribe to events received data it had no read permission for.

For your project this means two things. Every integration that connects an ERP, a PIM or a marketing tool to the shop gets its own role with exactly the rights it needs. And every plugin controller you write checks its permission explicitly, because the ACL only applies where it is declared.

NestJS ships guards, but a guard nobody attaches to a route protects nothing. The most common mistake is the classic IDOR: the route GET /orders/:id checks that a valid token is present, but not that the order belongs to the token holder.

@Get(':id')
@UseGuards(JwtAuthGuard)
async findOne(@Param('id') id: string, @CurrentUser() user: User) {
  const order = await this.orders.findOne({ where: { id } });
  if (!order || order.customerId !== user.id) {
    throw new NotFoundException(); // deliberately 404, not 403
  }
  return order;
}
Enter fullscreen mode Exit fullscreen mode

The NotFoundException is no accident: a 403 confirms to the attacker that the order exists.

The second NestJS classic is mass assignment. Validate a DTO without whitelist: true and you accept every field the client sends, including role: 'admin'. The global ValidationPipe with whitelist and forbidNonWhitelisted is the one line that switches this off (NestJS documentation, validation):

app.useGlobalPipes(new ValidationPipe({
  whitelist: true,
  forbidNonWhitelisted: true,
  transform: true,
}));
Enter fullscreen mode Exit fullscreen mode

That SSRF has lived in this category since 2025 fits both stacks. In August, Shopware closed two SSRF holes via DNS rebinding, in the media import and in the app system. In NestJS the same hole appears as soon as an endpoint accepts a URL from the client and fetches it via HttpService: image proxy, webhook tester, link preview. The cloud provider's metadata address, 169.254.169.254, is then one request away, with the instance credentials.

A02 Security Misconfiguration: the jump to number 2 has a reason

That misconfiguration climbed from 5 to 2 surprises nobody who runs deployments. OWASP maps 16 CWEs to the category; in individual data sets 27.7 percent of applications were affected. The category lives in .env files, in reverse proxy configs and in defaults nobody has touched since installation.

The Shopware case from August is the textbook example. The Administration's password reset built the reset link from the request's Host header. Whoever could manipulate that header made the shop send the admin a reset link pointing at their own domain. One click, and the token was with the attacker. CVSS 9.3, fixed in 6.6.10.23 and 6.7.13.1 (GitHub advisory GHSA-xj2c-8fw5-mr6m). The workaround Shopware names: configure trusted hosts in Symfony, telling the framework under which domains the shop may answer at all.

Trusted hosts existed the whole time. The Shopware default leaves them unset, because the shop works without them. Misconfiguration is almost always a feature that runs and a safeguard that is missing.

The Shopware list you can work through in an hour (Shopware security reference):

  • APP_ENV=prod and APP_DEBUG=0 in production, an APP_SECRET that does not come from a copied .env.
  • Trusted hosts and trusted proxies set, so Host and X-Forwarded-* headers do not come from the client.
  • The Content Security Policy adjusted to your third-party scripts via shopware.security.csp_templates, not disabled.
  • shopware.media.enable_url_upload_feature only on if you really need URL uploads.

The NestJS counterpart: app.enableCors() without an origin list because it got in the way during frontend testing. Swagger reachable under /api/docs in production, with every endpoint and every schema. No helmet, so no security headers. And @nestjs/devtools-integration still in the dependencies although it is meant for local development only.

The last one had consequences: CVE-2025-54782 allowed any website to execute code on the developer's machine via a CSRF request to the running local devtools server, CVSS 9.4 (GitHub advisory GHSA-85cg-cmq5-qjm7). The affected machine was the development machine, which is usually where the deploy keys live.

app.use(helmet());
app.enableCors({ origin: ['https://shop.example.com'], credentials: true });
if (process.env.NODE_ENV !== 'production') {
  SwaggerModule.setup('api/docs', app, document);
}
Enter fullscreen mode Exit fullscreen mode

A03 Software Supply Chain: what you install, nobody has checked anymore

OWASP recut the category because "outdated components" no longer describes the problem. Since September 2025 the npm world has lived through it several times. The Shai-Hulud worm compromised hundreds of packages in several waves, stole developer tokens via install scripts and used those tokens to copy itself into further packages. Microsoft published its own guidance on the second wave on 9 December 2025 (Microsoft Security Blog).

On 4 August 2026 the CHAINDROP variant followed with more than 400 affected packages, among them keyv with more than 600 million downloads a month (Elastic Security Labs). keyv sits inside cache-manager, and cache-manager sits behind the CacheModule registered in the app.module.ts of many NestJS projects. Whether you were affected, npm ls keyv in the project folder tells you in two minutes.

npm 12, released on 8 July 2026, no longer runs install scripts of dependencies unless you explicitly allow them per package; git and tarball dependencies are blocked by default, and access tokens with two-factor bypass have been deprecated (InfoQ, August 2026). The rationale: according to JFrog, these three vectors were involved in roughly 53 percent of the malicious npm attacks observed over the past year.

For your NestJS project, in order of impact:

  • Commit the lockfile and install in CI with npm ci, never with npm install.
  • Switch install scripts off in CI with --ignore-scripts as long as you are not on npm 12.
  • Adopt new versions only after a few days' grace period; compromised versions are usually pulled within hours.
  • Generate an SBOM so that at the next wave you know in minutes whether you are affected.
# CI: deterministic, without install scripts
npm ci --ignore-scripts
npm audit --audit-level=high
Enter fullscreen mode Exit fullscreen mode

Composer has no install scripts in the npm sense, and Shopware plugins mostly come from the Shopware Store with review. The supply chain surface in the Shopware stack is the extensions themselves. The sandbox escape from August shows the risk: an installed app could execute arbitrary PHP functions and OS commands with the web server's rights from its app scripts, CVSS 9.6 (GHSA-6qhw-38wm-7g7h).

As early as March 2026, the communication channel between shop and app could be redirected through a repeated app registration: whoever knew the app secret could route traffic to their own server and obtain the shop's API credentials, CVE-2026-31889, CVSS 8.9 (GitHub advisory CVE-2026-31889). Every app, every plugin is code you hand the rights of your shop to.

npm supply chain timeline 2025/26: Shai-Hulud, npm 12 without install scripts, CHAINDROP with 400+ packages

One year of npm supply chain incidents, September 2025 to August 2026

A05 Injection: solved, until somebody writes around the framework

// vulnerable
qb.where(`order.customerEmail = '${email}'`);
// safe
qb.where('order.customerEmail = :email', { email });
Enter fullscreen mode Exit fullscreen mode

Injection today happens where somebody bypasses the framework. Both lines above exist in NestJS projects, often in the same repository. TypeORM, Prisma and MikroORM parameterise everything you send through their API. They do not parameterise what you write into a template string. Injection dropped from 3 to 5 because ORMs, prepared statements and auto-escaping template engines have defused the problem at scale. Still, 62,445 CVEs stand behind the category, more than behind any other.

In Shopware that framework is the Data Abstraction Layer. Use Criteria and repositories and you get parameterised queries for free. Write $connection->executeQuery("... WHERE id = '" . $id . "'") in a plugin and you have brought 2015 back. The three SQL injections from August and September sat where dynamic names travelled into SQL: field names from app manifests for custom entities, aggregation names in the Store API, range aggregations in the Store API (GHSA-p37c-pm9p-7vm5).

Names, not values. Prepared statements protect values. For identifiers, meaning table, column and aggregation names, you need an allowlist.

Then there is Twig. Shopware renders storefront and app scripts with Twig, and Twig escapes by default. But |raw exists, and template injection is a class of its own: CVE-2026-23498 allowed PHP code execution from templates through a gap in the filter validation of map() (GitLab Advisory Database). Whoever registers their own Twig filters or functions in plugins extends exactly this attack surface.

AI-generated code does not change this for the better. A coding assistant writes the string concatenation in the query as readily as the parameterised variant, depending on what sits in the context, and it has not checked the release date of the dependency it suggests. We covered this in more depth in Vibe coding vs. good code.

In short: injection has become a code review topic in both stacks. Search for string concatenation near SQL, Twig and shell.

A07, A09, A10: the three decided in operations

@nestjs/throttler is not installed until you install it (NestJS rate limiting). Shopware, by contrast, ships rate limiters for login, guest login, password reset, admin recovery, contact form, newsletter and cart since 6.4.6. They are configurable under shopware.api.rate_limiter, with backoff tiers such as 10 attempts in 10 seconds, then 15 in 30, then 20 in 60 (Shopware rate limiter). The August fix for the guest document download was a route that lacked this protection. Your own routes only get it if you configure it yourself.

For JWT handling in NestJS: set the signature algorithm explicitly, keep access tokens short, make refresh tokens revocable server-side. A JWT with a seven-day lifetime and no revocation is a password you cannot change.

Logging without alerting is the quietest category. Shopware writes login failures and API errors to logs; the core has no audit log for admin actions. NestJS logs what you configure it to log. Both are worthless if nobody looks.

The Host header attack from August leaves a single trace in the access log, a reset request with a foreign Host header, provided your log format records the host at all, and it only stands out if somebody searches for it. The same applies to the dozen 401s per minute that precede credential stuffing on the Store API.

An alert on three signals, login failures per minute, 5xx rate and new admin users, covers most real attacks. How to set that up with on-board tools is described in Setting up alerting with n8n.

The new category A10 is concrete in NestJS: an exception filter that writes error.stack into the response in production reveals file paths, versions and query fragments. NestJS's default filter does not do that; a hand-written one often does.

And a catch that logs an error and carries on leaves a multi-step order half written, the transaction open, the database connection occupied. OWASP names these examples in the category description (A10:2025). How a backend should be cut so that such states never arise is in our guide to enterprise backend architecture.

Patching is architecture, not maintenance

Everything above assumes your stack is on a version the vendor still secures.

Shopware delivers security fixes two ways: as patch releases for the current minor versions and as the Security Plugin, which backports known fixes to older versions. 6.6 and 6.7 receive security updates until 28 February 2028, 6.5 until 28 February 2027 (endoflife.date: Shopware). Anyone on 6.5 or below gets the August fixes only through the plugin, and Shopware itself calls the plugin the inferior route, because patches through an extension can have side effects (Shopware release policy).

Node.js is stricter. Node 20 reached end of life on 30 April 2026, Node 22 gets security fixes until April 2027, Node 24 is the current LTS, Node 26 follows at the end of October 2026 (endoflife.date: Node.js). A NestJS backend on Node 20 has run without runtime security updates since 30 April. OWASP lists that nowhere, because it is trivial. It still shows up regularly in incident reports.

Support timeline Shopware 6.5 to 6.7 and Node.js 20 to 26: who still gets security updates in October 2026

Who still patches your stack? Shopware and Node.js security support as of October 2026 (source: endoflife.date)

The practical consequence is a fixed patch window. Shopware publishes security releases as needed, without a fixed rhythm and without advance notice. With a staging environment and automated smoke tests, a security release goes live within 48 hours. Without one, you wait for the next project budget while the advisories with version numbers sit publicly on GitHub. Attackers read them on the day of publication.

What actually lands in your stack

Four categories you have to work on actively: Access Control, because only you know who may see what. Misconfiguration, because defaults are not made for production. Supply Chain, because packages get tampered with between maintainer and npm install. Injection, because it lives on at the edges of the framework. The other six you cover with discipline in operations: a patch window of days, rate limits on everything that authenticates, an alert somebody reads, and error handling that reveals nothing.

Whether your stack is there is answered by four questions that need no pentest:

  1. Which integration still has admin rights today although it only needs to read orders?
  2. Are APP_ENV, trusted hosts and CORS origins set explicitly in production, or are they running on defaults?
  3. Is the lockfile in the repo, and does CI run npm ci --ignore-scripts?
  4. How many days passed between 25 August 2026 and your Shopware update?

Whoever hesitates on one of these has their priority list. And if you want a second pair of eyes that looks at Shopware shop and backend together, that is the work we do as a Shopware agency and in enterprise software.

Frequently asked questions about OWASP Top 10, Shopware and NestJS

Is the OWASP Top 10 2025 final yet?
Yes. The list was published as a release candidate on 6 November 2025 and finalised in January 2026. There is no "OWASP Top 10 2026"; the next edition follows the usual rhythm of roughly four years.

Which OWASP category affects Shopware the most?
Going by the security releases of August and September 2026: Broken Access Control (privilege escalation, webhook ACL, SSRF), Injection (three SQL injections, Twig) and Supply Chain (app sandbox escape, app registration). The password reset via the Host header falls under Security Misconfiguration.

Is NestJS secure out of the box?
NestJS ships the tools but few defaults: guards, ValidationPipe, throttler and helmet only protect once you use them. The biggest risk lies in the npm dependencies.

Is the Shopware Security Plugin enough instead of an update?
It closes known holes in older versions, but Shopware recommends updating, because patches through a plugin can have side effects. 6.6 and 6.7 get security updates until February 2028, 6.5 until February 2027.

What is the fastest protection against npm supply chain attacks?
npm ci --ignore-scripts in CI, a committed lockfile, a few days' grace before adopting new versions, and two-factor authentication without bypass tokens on all npm accounts. Since npm 12, install scripts are off by default.


Originally published on next-levels.de. German version: OWASP Top 10 für Shopware- und NestJS-Projekte.``

Top comments (0)