Report malformed percent-escapes in Google resource URLs instead of throwing URIError - #60
Closed
sarbojitrana wants to merge 1 commit into
Closed
Conversation
getGatekeeperClassFor() decoded user-supplied URL parts with a bare decodeURIComponent(). A bare `%` -- ordinary in a Gmail search or label like "50% off" -- made it throw `URIError: URI malformed` instead of the friendly errors the rest of the function raises for bad input. Route the four user-input decodes in that function through a decodePercentEncoded() helper that names the offending URL and points at `%25`. Malformed input is rejected rather than read literally: these values scope the binding, so guessing at intent could scope it to something other than what the user thinks they connected. Fixes cloudflare#55
|
All contributors have signed the CLA ✍️ ✅ |
Author
|
I have read the CLA Document and I hereby sign the CLA |
Author
|
recheck |
Member
|
Hi @sarbojitrana, Apologies but, per our contributing guide, we prefer not to receive external pull requests. We prefer instead to receive issue reports, which we can then direct our own agents to solve. Could you please file an issue instead? |
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.
Fixes #55.
Root cause
getGatekeeperClassFor()inpackages/gatekeeper-google/src/google.tshands user-supplied URL text straight todecodeURIComponent(), with notry.decodeURIComponent()throwsURIError: URI malformedon any%that isn't followed by two hex digits, so a bare%in the URL escapes the function's own error handling entirely:That is out of step with the rest of the same function, which explains itself whenever input is wrong — "Invalid Google Sheets URL: no spreadsheet ID found", "Gmail label name must be between 1 and 320 bytes." A user who pastes a search containing a percent sign gets none of that, just the raw decode failure. A
%in a Gmail search or label is not exotic: "50% off", "Q1 100%", any label with a percentage in it.There were four such bare decodes in the function, not two. The issue names the two Gmail branches and the calendar one; grepping the function turns up a fourth in the BigQuery branch:
%in the calendar-ID path segment%in any project/dataset/table segment#search/%anywhere in the query#label/%anywhere in the labelThe Gmail two are the easy ones to hit, since the whole hash is user text; the other two need a
%in one specific segment.The fix
One local helper, routed through at all four sites:
Now:
Three notes on the choices, since they were the judgement calls:
Reject rather than repair. The tempting alternative is to treat an undecodable
%as a literal%and carry on. I deliberately didn't. These strings scope the binding — which search, which label, which calendar, which BigQuery table the agent may read. Silently reinterpreting one risks scoping a capability to something other than what the user believes they connected, and a wrong guess here fails open. An error the user can act on is the safer failure. The message names%25so the fix is obvious.Scope: this function, not all seven packages. #55 notes the same pattern lives in seven gatekeepers and asks whether the fix should be a shared helper. Going by CONTRIBUTING.md's "no more than a dozen or so lines", I kept this to the one function the issue reports, as a local helper. A shared safe-decode utility across confluence/email/homeassistant/linear/notion/slack/google is a much bigger and more opinionated change — happy to open a discussion for it if you want that instead, and this patch is a clean no-op to revert if so.
Included BigQuery, though #55 doesn't mention it. It's the same bug on the same user input in the same function, and leaving it would mean fixing three of four identical call sites. Say the word and I'll drop that one line.
google.ts:3001also decodes, but its input is KV keys this file wrote withencodeURIComponent()— never malformed, never user input. Left alone.Proof
Before/after, driving the same branch dispatch with
decodePercentEncodedlifted verbatim out of the patched source (testing the shipped helper, not a paraphrase):Well-formed input decodes byte-identically to before — this is a pure error-path change:
Note
#search/50%25+off— the properly-escaped form of the same "50% off" search Gmail's own UI produces — still decodes correctly, and the+-to-space normalization above line 941 is untouched.Pre-existing validation still fires (the fix doesn't swallow anything):
Checks:
pnpm lint:check— exit 0 (no new warnings; the one pre-existinggoogle.tswarning is at line 2246, unrelated)pnpm --filter @gadgets/google-gatekeeper types:check— exit 0pnpm test— all suites pass except@gadgets/integration-tests, which fails identically on a clean tree at0eaec6c(Could not resolve "./generated/format-blueprints.js"). Verified by stashing the patch and re-running: same failure, so it's pre-existing and unrelated.Diff is 18 insertions / 4 deletions in one file.