Repository navigation
9.8.2
Release 9.8.2
Security patch on top of 9.8.1, and nothing else. Every change is about the same thing: an
ACL variable that cannot be resolved must never widen what a caller can see or write.
Upgrade if you use readFilter, writeFilter or mergeRequest with @ variables, or if you
expose change streams. The documented per-user pattern
readFilter: {"owner": "@user._id"} did the opposite of what it promises for JWT-authenticated
callers — see the first item.
Security
Unresolved ACL variables no longer widen access
A variable the server could not resolve became MongoDB null. In a query {"owner": null} matches
every document that has no owner, so a filter meant to narrow a read widened it instead.
It happened whenever a variable named a property the account does not carry, and a JWT account
carries only the token's claims — never _id. So with the documented pattern
readFilter: {"owner": "@user._id"}, every JWT caller read every unowned document; with
writeFilter, modified or deleted them; and mergeRequest stamped owner: null on everything
they wrote, creating more of them. A file realm account behaves the same way, as does a missing
@qparams entry or any custom VarResolver that returns null.
Now an unresolved variable in readFilter/writeFilter is replaced with a random value that
matches no document — the same answer unbound variables have always had in ACL predicates — and a
warning naming the variable is logged. The rest of the filter is unaffected, so
{"$or": [{"owner": "@user._id"}, {"public": true}]} still returns the public documents.
mergeRequest refuses the write with 403 when an @user variable is unbound, rather than
writing a document nobody owns.
Negations are the exception to keep in mind: {"owner": {"$ne": "@user._id"}} still matches
when the variable is unbound. Do not negate @ variables in filters.
Change streams apply readFilter and projectResponse
A change stream applied neither. A caller whose REST reads were limited to their own documents
received every event on the collection over WebSocket or SSE, fullDocument included, along with
the properties projectResponse hides.
Both are now applied, as they are to the equivalent GET:
- the
readFilteris rewritten ontofullDocument, so events that carry none — deletes — are not
delivered when the filter is about the document's own fields; - an exclusion
projectResponseremoves the properties fromfullDocument,
fullDocumentBeforeChangeandupdateDescription.updatedFields; - a filter that cannot be rewritten safely (
$expr,$where,$text) and an inclusion
projection, which would strip the event's resume token, refuse the stream with403.
Aggregation variables named @... are bound by the server alone
?avars={"@user._id": "someone-else"} — and its flat form ?@user._id=... — supplied a value for
a variable the server is supposed to resolve. It took effect wherever the account had no such
property, which for a JWT account is every dotted name, so an aggregation or change stream written
as {"$var": "@user._id"} to scope itself to the caller could be pointed at another user's data.
A supplied @ name is now ignored: the server's own value is used, and a request carrying one
still runs.
Upgrading
Drop-in: no configuration change, no change to ACLs, aggregations or stream definitions.
What changes is what those ACLs now do, and the expected symptom is fewer documents and fewer
events, not more:
| Configuration | Before | Now |
|---|---|---|
readFilter/writeFilter with a variable the account lacks |
matched every document missing that field | matches nothing |
mergeRequest with an unbound @user variable |
wrote null |
403 |
a change stream, for a caller with a readFilter |
every event | only the caller's own |
a change stream, with $expr/$where/$text in the filter or an inclusion projectResponse |
opened | 403 |
?avars={"@user…": …} |
bound the supplied value | ignored |
If your ACLs identify users by @user._id and your clients authenticate with a JWT, those
filters now match nothing — which is the point, but it is also a behaviour change to plan for. A
JWT account is identified by @user.sub; to keep using _id, add it to
jwtTokenManager/account-properties-claims so it travels in the token, string ids only.
Watch the log for ACL filter variable … is not bound: each line is a filter that was widening
access before this release.
Dependencies
- GraalVM: 25.1.3
- Java: 25+