Skip to content

security(star-rating): forward only the widget's own parameters from /i/feedback/input (24.05) - #7953

Open
ar2rsawseen wants to merge 1 commit into
release.24.05from
backport/feedback-input-param-allowlist-2405
Open

security(star-rating): forward only the widget's own parameters from /i/feedback/input (24.05)#7953
ar2rsawseen wants to merge 1 commit into
release.24.05from
backport/feedback-input-param-allowlist-2405

Conversation

@ar2rsawseen

Copy link
Copy Markdown
Member

Backport of #7952 to release.24.05.

/i/feedback/input replays its request into the generic /i processor with no_checksum set, because the web feedback widget cannot compute a checksum (it does not hold the app's salt). It built that replayed request from the caller's entire query string, and the handler's only check is on events (a single [CLY]_star_rating event). Every other parameter was forwarded and then processed with checksum verification disabled.

On an app with a checksum salt configured, a caller holding only the public app key could therefore append unrelated write parameters and have them accepted unsigned: old_device_id reaches appUsers.merge(), token_session reaches the push token binding, and begin_session / user_details / consent / crash ride along the same way.

Fix (identical to #7952): rebuild the forwarded query from the parameters the widget actually sends — events, app_key, device_id, sdk_name, sdk_version, timestamp, hour, dow, app_version — and drop everything else. Values are URL encoded so a value cannot inject a second parameter, and non-scalars are dropped rather than stringified. The 24.05 widget sends the same nine parameters as master, so the allowlist is unchanged.

Tests: test/unit-tests/star-rating.input-utils.js, 8 cases, all passing on this branch; eslint clean.

🤖 Generated with Claude Code

…/i/feedback/input

Backport of #7952 to release.24.05.

/i/feedback/input accepts a feedback submission from the web widget, which cannot
compute a checksum because it does not hold the app's salt, so the handler replays
the request into the generic /i processor with no_checksum set. It built that
replayed request from the caller's entire query string:

    url: "/i?" + ob.params.href.split("/i/feedback/input?")[1]

The handler's only check is on the events parameter, which has to be a single
[CLY]_star_rating event. Every other parameter was forwarded untouched and then
processed with checksum verification disabled, so a caller holding just the public
app key could append unrelated write parameters and have them accepted unsigned on
an app that has a checksum salt configured. old_device_id reaches
appUsers.merge(), and token_session reaches the push token binding;
begin_session, user_details, consent and crash ride along the same way.

Rebuild the forwarded query from the parameters the widget actually sends
(events, app_key, device_id, sdk_name, sdk_version, timestamp, hour, dow,
app_version) and drop everything else, so the star rating submission keeps working
while any other operation has to go through /i and satisfy the checksum. Values are
URL encoded, so a parameter value cannot inject a second parameter, and non scalar
values are dropped rather than stringified, since a JSON body can put an object in
a query string parameter.

The helper lives in api/input-utils.js so it can be unit tested; the existing
plugin tests only vary device_id and never sent old_device_id here, so they are
unaffected.

Note this is the only unauthenticated no_checksum forwarder: the sole other
no_checksum caller is api/utils/taskmanager.js, which replays a stored task URL
created by an authenticated user.

Reported through the security bug bounty programme (received 2026-08-18).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant