All posts
Forward Development

Vendure 3.7.4: patch two vulnerabilities, then check stock strategies

Vendure 3.7.4 fixes a high-severity API key flaw and a stock overselling race. Here is how to find custom stock strategies and stage the upgrade.

  • Vendure
  • Operations
  • Migrations

The second-to-last unit of a popular variant sells twice within a few hundred milliseconds. Later the warehouse finds stockOnHand and stockAllocated out of line with what is on the shelf, and someone has to email a customer about an order you cannot fill. Separately, a staff account that should only manage some integrations turns out to be able to take over a more privileged API key. Vendure v3.7.4 fixes both problems. Because the fixes change how stock is allocated and who can manage API keys, it is not a version bump you merge on Friday afternoon.

The release notes say it directly: if you are on an earlier 3.x version, upgrade. We agree. But if you have custom stock logic, give the upgrade a short, planned staging cycle, because the release deliberately changes behaviour that 3.7.3 allowed.

What the two vulnerabilities are

The high-severity issue (GHSA-37xp-mjp8-6f9x) is API key privilege escalation. An administrator with only UpdateApiKey could rotate an API key that had higher privileges than their own and receive its new plaintext secret, which let them take over a live credential. createApiKey, updateApiKey and deleteApiKeys also did not compare the caller's permissions with the key's roles, and API key reads were not limited to keys the caller could manage. If any non-SuperAdmin role can touch API keys in your admin, this is the reason to upgrade now.

The low-severity issue (GHSA-8ghm-q833-cmgp) is stock overselling under concurrent checkout. Stock levels were updated with an unlocked read-modify-write, and the saleable stock check ran separately from allocation. Two checkouts for the same variant could both pass the check and both allocate. Low severity is a fair rating from a security point of view. From a merchant's point of view, it is the bug that causes cancellations during a flash sale.

What changes when you upgrade

No APIs are removed or renamed, and the release notes say no database migrations are required. The behaviour changes are where the risk sits:

  • API keys. createApiKey, updateApiKey, rotateApiKey and deleteApiKeys now throw unless the caller holds every permission of the key's roles. A key the caller cannot manage is reported as not found and left out of list queries. If you delegated key management to a limited role, those users will see fewer keys and get errors where they used to succeed. That is the fix working as intended, but tell them before it happens.
  • Allocation is capped. DefaultStockLocationStrategy now caps allocation at available stock: stockOnHand - stockAllocated - outOfStockThreshold, summed across all stock locations. Before this release it allocated the full quantity without reading stock. An order that loses the race for the last units is now under-allocated rather than oversold, and a new StockShortfallEvent is published.
  • Row locks. The saleable stock check and allocation now take row locks on StockLevel rows. SQLite and SQL.js have no row locks, so they fall back to an unlocked read and log a warning once per process.
  • Absolute stock updates. StockMovementService.adjustProductVariantStock() runs in its own transaction with a locked read. When two absolute updates run at the same time, the later value is stored. Previously both changes were added together. If an ERP or inventory sync pushes absolute stock values, the result changes when two syncs overlap.

Find your custom stock strategies first

Two kinds of custom code need attention. Search your own code and any third-party plugins you vendor or install:

grep -rnE "extends BaseStockLocationStrategy|StockAllocationStrategy|stockLocationStrategy|stockAllocationStrategy" \
  src/ plugins/ node_modules/@your-scope/

A plugin can set these strategies through its configuration function, so a clean vendure-config.ts does not mean you have no custom strategy. Check what is registered at runtime, not only what you wrote yourself.

For each hit:

  • Subclasses of BaseStockLocationStrategy that override init() must call super.init(injector). If they do not, they now throw an InternalServerError that says so, instead of the older TypeError. It is a one-line fix, and it is better to find it in staging than through a failed checkout.
  • Custom StockAllocationStrategy.shouldAllocateStock() may now be called more than once per transition. It must return the same result for the same arguments and have no side effects. If yours writes to a table, emits an event, calls an external service or increments a counter, move that work out. The usual offenders are logging allocations to a third-party system and sending a webhook from inside the decision.
  • Custom location strategies that read stock should use the new StockLevelService.lockStockLevelsForVariants() and getLockedStockLevelsForVariant() with StockLevelLockOptions, so they take the same row locks as core. A custom strategy that reads stock without locks reintroduces the race the advisory describes.

Stage the upgrade and read the migrate output

Upgrade every @vendure/* package together to 3.7.4. Mixed versions across core, dashboard and plugins cause problems that look unrelated to the upgrade.

No migrations are required for this release, but vendure migrate reports more clearly now, and that is useful during the upgrade. It used to print "No pending migrations found" in two situations where that was misleading: when the configured migrations patterns matched no files, and when the database schema was out of sync with the entities. Both are now reported. Run it against a staging copy of production before and after the package bump. If it reports drift, find out why before deploying. On long-lived stores, especially ones moved from another platform, entity changes are sometimes applied by hand or with synchronize on a dev box and never captured in a migration.

One catch: the exit code is unchanged. A CI step that only checks the exit status will not see the new warnings. Capture the output and fail the job on it, or call runMigrations() programmatically and use the new RunMigrationsOptions.onDiagnostic hook.

Test allocation under real concurrency

Unit tests will not show a race condition. Before deploying:

  1. Run staging on the same database engine as production. If you test on SQLite you are testing the unlocked fallback, which is the old behaviour. Look for the once-per-process warning in the logs as confirmation.
  2. Set a variant to a small stockOnHand, for example 2, and fire several checkouts for it at the same moment. Use a script that drives the Shop API through to order placement, not a browser.
  3. Check that stockAllocated never goes above available stock, that the losing orders are under-allocated, and that StockShortfallEvent fires for them.
  4. If you run inventory sync, start two absolute updates at the same time and confirm that last-write-wins is acceptable for your integration.
  5. Using a limited admin role, try to list, rotate and delete an API key with broader roles. All of these should fail, and the key should not appear in the list.

Decide what a shortfall means for your store

The release gives you StockShortfallEvent but does not decide what happens next. Before going live, decide whether a short order is refunded, backordered or held for a person to review, and subscribe to the event to do it. Without that, an under-allocated order is a quieter problem than an oversell, but it is still a problem.

If this upgrade is part of a larger move off Magento or a homegrown platform onto Vendure, stock locking and allocation semantics belong in your cutover plan from the start. That is the kind of detail we focus on in Magento 2 and Adobe Commerce migrations to Medusa or Vendure. If you have custom stock strategies and want another engineer to review them before 3.7.4 goes to production, get in touch.

Need this done on a real stack?

Magento 2, Adobe Commerce, migrations to Medusa.js or Vendure, enterprise Next.js, WordPress, and AI automation.

Contact us