Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

storage: SignedUrlBuilder leaves '*' unencoded in the canonical query, so GCS rejects the URL (SignatureDoesNotMatch)

Closed Beginner friendly
#7,153 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@joshuatants is already working on this.

Since Oct 9, 2026.

  • #7148 by @joshuatants — merged

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
74/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
rust
Domain
backend, databases

Research direction

Start in src/storage/src/storage/signed_url.rs at the canonical_query block in sign_with, then compare its encoding with the V4 canonical request requirements linked in the issue. Add coverage for a query key or value containing * and run the relevant storage tests; done means the canonical query encodes * as %2A and the regression test passes.

Written by the indexing model from the issue text.

Description

api: storage
Problem

SignedUrlBuilder produces a URL that GCS rejects with 403 SignatureDoesNotMatch whenever a query parameter key or value contains *.

The canonical query string is built with url::form_urlencoded, which treats * as safe and leaves it unencoded:

https://github.com/googleapis/google-cloud-rust/blob/main/src/storage/src/storage/signed_url.rs (the canonical_query block in sign_with)

let mut canonical_query = url::form_urlencoded::Serializer::new("".to_string());
for (k, v) in &query_parameters {
    canonical_query.append_pair(k, v);
}
canonical_query
    .finish()
    .replace("%7E", "~") // rollback to ~
    .replace("+", "%20") // missing %20 in +

The V4 canonical request spec requires percent-encoding everything outside the RFC 3986 unreserved set (A-Z a-z 0-9 - . _ ~). GCS therefore canonicalizes * as %2A, and the signatures differ. The two existing .replace calls already patch over the same form_urlencoded mismatch for ~ and space; * is the remaining one.

Repro

The standard RFC 6266 / 5987 way to give a download a UTF-8 file name hits this:

let url = SignedUrlBuilder::for_object("projects/_/buckets/my-bucket", "some/object")
    .with_query_param(
        "response-content-disposition",
        "inline; filename=\"report.pdf\"; filename*=UTF-8''report.pdf",
    )
    .sign_with(&signer)
    .await?;
// GET url -> 403 SignatureDoesNotMatch

The CanonicalRequest in the 403 body shows filename%2A%3DUTF-8%27%27report.pdf, while the SDK signed filename*%3DUTF-8%27%27report.pdf.

Seen with google-cloud-storage 1.17.0. The same code is present in 1.20.0.

Proposed fix

Percent-encode * in the canonical query, the same way ~ and + are already handled. For example, add .replace("*", "%2A"), or build the query with percent_encoding and an RFC 3986 unreserved set. #7118 proposes that set for path encoding, and both could share it.

Workaround

Avoid * in signed query parameters. For Content-Disposition, drop filename* and use only an ASCII filename="...".

Dominant language
Rust
Stars
963
Forks
151
Avg merge
1d 26m
Merged PRs (30d)
267

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from googleapis/google-cloud-rust

All issues in googleapis/google-cloud-rust

Similar issues

More Rust issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.