security(compliance-hub): fix the consents table projection server side (24.05) - #7860
Merged
ar2rsawseen merged 3 commits intoJul 30, 2026
Merged
Conversation
/o/app_users/consents built its projection as
`params.qstring.project || params.qstring.projection || {defaults}`, so the
narrow default applied only when the caller omitted both parameters. Because a
query-string value arrives as a string, `project={}` is truthy and short-circuits
the chain, and an empty projection means "all fields" to MongoDB. The projection
was then forwarded unvalidated into appUsers.search() and on to
find(query, project).
A member holding nothing but compliance_hub read on an app could therefore turn a
consent-status table into full app_users documents: name, email, username, phone,
organization, customer-defined custom properties, payment totals, city/region,
acquisition source and device details. The Compliance Hub UI never sends a
projection, so the parameter was attacker-facing only.
The consent table renders a fixed set of columns, so the projection is now a
server-side constant and any project/projection on the request is ignored. The
allowlist matches the columns bound in the users table template, plus uid and
appUserExport for the per-row history and export actions.
/o/consent/search is fixed the same way. Its response shape is unchanged - full
consent_history documents, which hold only consent state and the device context
it changed in - but the projection is no longer taken from the request, and the
now-unreachable JSON.parse of the caller's value is removed.
Note the surrounding hardening pass validated user-supplied *queries* via
common.parseUserQuery but never covered projections; the only projection
sanitizer in the tree (dbviewer's sanitizeProjection) constrains values to 0/1
and not which fields may be requested, so it would not have closed this.
Regression tests assert that project={}, an explicit projection naming
name/email/username/custom, and the projection alias parameter all return only
the allowed fields.
Co-Authored-By: Claude <noreply@anthropic.com>
Addresses review feedback on the countly-platform port. The consent-search
endpoints were changed to a fixed empty projection for tidiness, which was scope
creep: their default was already an empty projection, so a caller could only ever
narrow the response and there was no vulnerability there to fix. Forcing {} removed
the ability to narrow.
Restored to their original behaviour, including the JSON.parse of a caller-supplied
value. The only change left is the one that fixes the actual issue:
/o/app_users/consents returns a fixed set of fields.
Co-Authored-By: Claude <noreply@anthropic.com>
mrmeghana
approved these changes
Jul 30, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Backport of #7859 to
release.24.05. The plugin code is identical on this branch, so the fix applied cleanly; only the CHANGELOG heading needed resolving.What changes
/o/app_users/consentsbuilt its projection asparams.qstring.project || params.qstring.projection || {defaults}, so the narrow default applied only when the caller omitted both parameters. A query-string value arrives as a string, soproject={}is truthy and short-circuits the chain, and an empty projection means "all fields" to MongoDB, which was then forwarded unvalidated intoappUsers.search().A member holding nothing but
compliance_hubread on an app could turn a consent-status table into fullapp_usersdocuments, including name, email, username, phone, customer-definedcustomproperties, payment totals and location.The projection is now a server-side constant matching the columns the users table actually renders, and any
project/projectionon the request is ignored./o/consent/searchgets the same treatment with its response shape unchanged.Tests
Same four regression cases as #7859, asserting only allowlisted fields are returned for
project={}, an explicit projection naming extra fields, and theprojectionalias.Verified locally:
npx eslint plugins/compliance-hub/andnode --checkpass.Note for reviewers
The CHANGELOG entry went under the current
24.05.51heading rather than being appended to the24.05.50"backport of #7535" Security Fixes block, since this is a separate fix. Happy to consolidate if you prefer.🤖 Generated with Claude Code