GCP Signed URL Decoder

Read a Cloud Storage signed URL: which version it is, how long it lives, which service account signed it, and what it points at. Nothing is uploaded and the signature is not verified, because verifying would need the signer's private key.

Paste below, or drop a file anywhere on this panel

Or drop a file anywhere on this panel. Nothing is uploaded: the analysis runs in this tab.

The answer appears here

Paste on the left and press Decode the URL. Nothing leaves this tab.

Wanted a different tool?

Examples

Real input you can load into the tool above. Each one shows a different thing going wrong, because that is what the tool is for.

A week-long URL

Seven days is the V4 maximum, and the expiry is the only thing limiting a leaked URL because there is nothing else to check

https://storage.googleapis.com/my-bucket/reports/q3.pdf?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=signer%40my-project.iam.gserviceaccount.com%2F20260817%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260817T120000Z&X-Goog-Expires=604800&X-Goog-SignedHeaders=host&X-Goog-Signature=abc123

Over the seven day cap

The request is rejected with a 400 mentioning the expiration, which reads as a clock problem rather than a limit

https://storage.googleapis.com/my-bucket/a.pdf?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=signer%40p.iam.gserviceaccount.com%2F20260817%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260817T120000Z&X-Goog-Expires=1209600&X-Goog-SignedHeaders=host&X-Goog-Signature=abc

The older V2 scheme

V2 carries an absolute epoch second rather than a duration, and has no maximum expiry at all

https://storage.googleapis.com/my-bucket/a.pdf?GoogleAccessId=signer@p.iam.gserviceaccount.com&Expires=1700000000&Signature=abc123

A URL a mail client wrapped

No signature parameter, which is what a truncated URL looks like, and it grants nothing

https://storage.googleapis.com/my-bucket/a.pdf?X-Goog-Algorithm=GOOG4-RSA-SHA256&X-Goog-Credential=signer%40p.iam.gserviceaccount.com%2F20260817%2Fauto%2Fstorage%2Fgoog4_request&X-Goog-Date=20260817T120000Z&X-Goog-Expires=3600&X-Goog-SignedHeaders=host

Common mistakes

These are the ones that fail silently. The config is accepted, nothing raises an error, and the consequence arrives later.

  1. Treating a signed URL as a link rather than a credential

    It is a bearer token in a URL, so it works for whoever has it. URLs end up in browser history, in the Referer header of any page they link onwards from, in proxy and CDN logs, and in whatever chat application somebody pasted it into.

    Instead:Keep the expiry as short as the workflow allows and generate one per request. Never put one in a page that links onwards.

  2. Reading X-Goog-Expires as an absolute time

    In V4 it is a DURATION in seconds from the signing time. The older V2 Expires is an absolute epoch second. Reading one as the other gives an expiry in 1970 or in the far future, and both look like a number.

    Instead:Check the version first. A URL with X-Goog-Credential is V4 and its expiry is a duration.

  3. Asking for an expiry over seven days on V4

    604800 seconds is a hard cap. The request is rejected with a 400 mentioning the expiration, which reads as a clock problem rather than a limit.

    Instead:Cap it at seven days. A workflow needing longer access needs a credential rather than a URL.

  4. Expecting to revoke one signed URL

    There is no per-URL revocation. Cloud Storage evaluates the SIGNER's permissions at request time, so the only way to invalidate one is to remove that service account's access to the object, which invalidates every URL it has ever signed.

    Instead:Sign with a narrowly scoped service account per use case, so the blast radius of revoking is one workflow rather than all of them.

  5. Signing with a default or broadly privileged service account

    The credential parameter names the signer in the clear, and its permissions are what the URL grants at request time. A URL signed by an account with broad access carries that access to whoever holds it.

    Instead:Use a dedicated signer with access to exactly the prefix it signs for.

A signed URL is a bearer token written into a URL

Whoever holds it has the access it encodes, with no identity check. Being a URL, it goes where URLs go, and most of those places keep a copy.

Where a signed URL ends up

Browser history. The Referer header on any page it links onwards from. Proxy and load balancer access logs. CDN caches. The chat application somebody pasted it into. None of those are attacks, they are how URLs work, and every one of them is a copy of a working credential.

Seven days is a hard cap on V4, and V2 had none

X-Goog-Expires is a DURATION in seconds from the signing time, and the maximum is 604800. Ask for more and the request is rejected with a 400 that mentions the expiration, which reads as a clock problem. The older V2 scheme carries an absolute epoch second in Expires instead, with no cap at all, so a V2 URL can be valid for years.

V4   X-Goog-Expires=3600        a duration
V2   Expires=1700000000         an absolute time

Reading one as the other gives an expiry in 1970
or in the far future, and both look like a number.

The credential says who signed it, in the clear

X-Goog-Credential is the service account email, the date scope, the location, the service and the request type, separated by slashes. So a signed URL identifies its signer without revealing anything secret. That is useful twice: for tracing where a leaked URL came from, and for checking that the signer is a dedicated service account rather than a default one with broad access.

X-Goog-Credential=
  signer@project.iam.gserviceaccount.com
  /20260817/auto/storage/goog4_request

Revocation is the signer's permissions, and it is all or nothing

Cloud Storage evaluates the SIGNER's access at request time rather than at signing time. So removing that service account's access to the object invalidates every URL it has ever signed, at once. There is no way to revoke one URL, which is an argument for signing with a narrowly scoped account rather than a shared one.

The signature covers more than the path

The signed headers list is part of what was signed, and host is normally in it, which binds the URL to the endpoint it was made for. Anything signed and then changed, the method, a header, the expiry, invalidates the signature, so a URL that fails with a signature mismatch is usually one where something in the request differs from what the signer assumed.

What this cannot see

Whether the signature is valid. Verifying it needs the signer's private key, and nothing on this site should ever be given one. It also cannot tell you whether the object exists, whether the signer still has access, or whether the URL has already been used. Everything read here is what the URL states about itself, which is enough to answer the questions people actually have: how long it lives, who made it, and what it points at.