security(star-rating): authorize the /o?method=star ratings read - #7955
Open
ar2rsawseen wants to merge 1 commit into
Open
security(star-rating): authorize the /o?method=star ratings read#7955ar2rsawseen wants to merge 1 commit into
ar2rsawseen wants to merge 1 commit into
Conversation
The star-rating dashboard read performed no authorization. Authentication on /o is per method: every core method calls validateUserForDataReadAPI itself, and the default branch hands the validators to plugins as helpers without calling them, so a plugin that claims a request is responsible for authorizing it. This branch claimed the request, returned true, and never called a validator, so the endpoint answered callers with no account, token or session, for any app_id they supplied. What it disclosed is the set of platform and application-version combinations that have received ratings for that application. Rating comments and the detailed feedback in /o/feedback/data were not affected; those reads are authorized. Wrap the branch in validateRead(params, FEATURE_NAME, ...), which is the same check the sibling reads in this file already apply (/o/feedback/data and /o/feedback/widgets). Authorization runs before the period parameter is validated, so an unauthorized caller cannot probe the endpoint through its error responses. app_id needs no extra guard here: the core /o case rejects a request without one, and this branch only concatenates it into a collection name, so there is no ObjectID conversion that could throw for a global admin whose validateRead call does not require app_id. The only caller is the dashboard Ratings page (plugins/star-rating/frontend/public/javascripts/countly.models.js, starRatingPlugin.requestPlatformVersion), which sends the session credential and an app_id the member has access to, so it is unaffected. Reported through the security bug bounty programme (received 2026-08-18). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
/o?method=starreturns the platform and application-version combinations that have received star ratings. It performed no authorization, so any caller who supplies anapp_idreceived that application's data with noapi_key, noauth_tokenand no session.Measured against the running API with no credentials at all:
Why it was reachable
Authentication on
/ois per method, not global. Every core method authorizes itself (case 'durations'→validateUserForDataReadAPI(...),case 'events'→validateUserForDataReadAPI(...)), and thedefault:branch hands the validators to plugins as helpers:default: if (!plugins.dispatch(apiPath, { params: params, validateUserForDataReadAPI: validateUserForDataReadAPI, // passed, never called ... })) { common.returnMessage(params, 400, 'Invalid path, ...'); }dispatchreturns truthy when a plugin claims the request, and the only fallback is400 Invalid pathwhen nothing does. So a plugin registering on/omust authorize the request itself. Every other plugin does (views/api/api.js:879→validateReadat :886;times-of-day:177→ :185), and this plugin does it in its own siblings (/o/feedback/data:1258→ :1302,/o/feedback/widgets:1420→ :1422). Themethod === 'star'branch claimed the request, returnedtrue, and never called a validator.This was an omission rather than a design choice: the only caller is the dashboard Ratings page (
starRatingPlugin.requestPlatformVersioninplugins/star-rating/frontend/public/javascripts/countly.models.js), which sends the session credential and the active app. The plugin's deliberately public endpoints are a separate, clearly named family (/o/sdk,/feedback/widgets,/i/feedback/input).Impact
Cross-application disclosure of a specific customer's platform and application-version inventory, to an unauthenticated caller who knows the
app_id. Rating comments and detailed feedback live in/o/feedback/data, which is authorized and unaffected. Medium under SECURITY.md.Fix
Wrap the branch in
validateRead(params, FEATURE_NAME, …), the same check the sibling reads already apply. Authorization runs before theperiodparameter is validated, so an unauthorized caller cannot probe the endpoint through its error responses.app_idneeds no extra guard: the core/ocase rejects a request without one, and this branch only concatenates it into a collection name, so there is noObjectID()conversion that could throw for a global admin (whosevalidateReaddoes not requireapp_id). The diff is mostly re-indentation from introducing the callback.Scope of the class
Every plugin
/ohandler in countly-server, countly-platform and countly-enterprise-plugins was swept for a missing validator, and the surviving read candidates were probed unauthenticated.method=staris the only unauthenticated tenant-data read. The other credential-less responders return hardcoded lookup tables that ignoreapp_id(/o/langmap,/o/sources), are widget configuration for end-user SDKs (/o/feedback/widget), or are the intentional hooks API-endpoint trigger (/o/hooks)./o/surveys/*,/o/calculated_metrics/*and/o/journey-engine/foldersall authorize correctly.Reported through the security bug bounty program (received 2026-08-18).
🤖 Generated with Claude Code