All vulnerabilities
GHSA-2qh9-qcxq-f8qc

wheels: Scope chains are ignored by findByKey() and exists(key=)

wheels
CVSS
8.1High
ExploitationNo known exploitationNot in CISA KEV, which does not prove it is unexploitedCISA KEV
Exploit odds (EPSS)No scoreNo FIRST EPSS score in this snapshot yet
Fixed in4.2.0Fixed versionGitHub
Published12 Oct 2026Updated 12 Oct 2026GitHub

What fixes it

A fixed version on one release line is not the upgrade target for every line. Check your exact version and any vendor backports.

Description

Impact

Wheels 4.0 added chainable model scopes (model("Book").published().findAll()). Two chain terminals ignore the scope's conditions:

  • findByKey(key) replaces the merged WHERE clause with the primary-key condition, so model("Book").published().findByKey(id) returns the record even when it is outside the scope.
  • exists(key=...) on a scope chain or the query builder does the same; in production (error information off) it returns true for an out-of-scope key instead of throwing.

Applications that use scopes for authorization or multi-tenancy (for example a currentTenant or ownedBy(user) scope chained into finders, as the multi-tenancy guide recommends) can therefore load, and then update or delete, records belonging to another user or tenant when the record key comes from the request.

findAll, findOne, findFirst, count and other aggregates, updateAll, deleteAll, findEach and findInBatches respect the scope and are not affected.

Patches

Fixed in 4.2.0. findByKey(), exists(key=...), updateByKey() and deleteByKey() now combine the key condition with an incoming scope chain, query builder or where argument (AND) instead of replacing it, so a key outside the scope matches nothing. Lookups with no where are unchanged.

Workarounds

Use findOne(where="id = #key#") or findAll(where=...) on the scope chain instead of findByKey, or check ownership explicitly after loading the record.

Text from GitHub Advisory Database

Affected versions 1

  • wheels, other>= 4.0.0, <= 4.1.2GitHub