{"id":"GHSA-gx4c-2hqx-cw2r","summary":"rclone: S3 backend does not strip X-Amz-Security-Token on a same-host HTTPS-\u003eHTTP redirect","details":"## Vulnerability Details\n\n**File**: `backend/s3/s3.go`\n**Lines**: 1359-1380 (functions `s3CheckRedirect` / `s3RedirectCrossesHost`)\n\n### Root Cause\nCommit `e7b1eb774` (released in v1.74.3) added a `CheckRedirect` policy for\nthe S3 HTTP client whose purpose is to strip the `X-Amz-Security-Token`\nheader (the AWS STS session token) whenever a redirect chain \"crosses a\nhost\", so the token isn't forwarded to an unintended origin.\n\n`s3RedirectCrossesHost` decides this purely by comparing `url.URL.Host`\n(hostname[:port]); it never looks at `url.URL.Scheme`. A redirect that keeps\nthe exact same host:port but changes the scheme from `https://` to `http://`\ntherefore compares as \"same host\" and `X-Amz-Security-Token` is *not*\nstripped — it is sent again, this time over plaintext HTTP.\n\n```go\nfunc s3RedirectCrossesHost(req *http.Request, via []*http.Request) bool {\n\tif len(via) == 0 {\n\t\treturn false\n\t}\n\thost := via[0].URL.Host\n\tfor _, redirect := range via[1:] {\n\t\tif redirect.URL.Host != host {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn host != req.URL.Host\n}\n```\n\n### Attack Scenario\n1. The user configures an `s3` remote (or `--s3-endpoint` pointing at a\n   self-hosted/third-party S3-compatible service) using temporary\n   credentials that include an STS `session_token` (common for assumed-role\n   / CI / Kubernetes IRSA setups).\n2. The configured endpoint responds to a request with a 3xx redirect to the\n   *same* host:port but with `http://` instead of `https://` (TLS-front\n   misconfiguration, maintenance redirect, or a malicious/compromised\n   storage provider trying to harvest the token).\n3. rclone's S3 HTTP client follows the redirect and re-sends the request,\n   including `X-Amz-Security-Token`, over the now-unencrypted connection to\n   that same host.\n4. Any passive observer on that now-plaintext network path can read the STS\n   session token from the request headers.\n\n### Impact\nDisclosure of the AWS STS session token (`X-Amz-Security-Token`) in\ncleartext for the remainder of its validity window. This is the exact class\nof leak that `e7b1eb774` was written to close — it just doesn't cover the\nscheme-downgrade axis of \"crossing a host\".\n\n### Vulnerable Code\n```go\nfunc s3RedirectCrossesHost(req *http.Request, via []*http.Request) bool {\n\tif len(via) == 0 {\n\t\treturn false\n\t}\n\thost := via[0].URL.Host\n\tfor _, redirect := range via[1:] {\n\t\tif redirect.URL.Host != host {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn host != req.URL.Host\n}\n```\n\n### Recommended Fix\nAlso compare `URL.Scheme`, so a scheme downgrade on the same host is treated\nthe same as a host change:\n\n```go\nfunc s3RedirectCrossesHost(req *http.Request, via []*http.Request) bool {\n\tif len(via) == 0 {\n\t\treturn false\n\t}\n\tscheme, host := via[0].URL.Scheme, via[0].URL.Host\n\tfor _, redirect := range via[1:] {\n\t\tif redirect.URL.Host != host || redirect.URL.Scheme != scheme {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn host != req.URL.Host || scheme != req.URL.Scheme\n}\n```\n\n### Verification\nAdded a unit test (`backend/s3/redirect_scheme_test.go`) that calls the real,\nunmodified `s3RedirectCrossesHost` / `s3CheckRedirect` with an\n`https://bucket.example.com` -\u003e `http://bucket.example.com` redirect chain.\n\nOn unpatched code (commit 16091ce365, current master / v1.74.3):\n- `s3RedirectCrossesHost` returns `false`\n- `s3CheckRedirect` leaves `X-Amz-Security-Token: SECRET-SESSION-TOKEN`\n  intact on the outgoing (plaintext) request.\n\n```\n=== RUN   TestSchemeDowngradeNotDetectedAsCrossHost\n    redirect_scheme_test.go:23: initial=https://bucket.example.com final=http://bucket.example.com s3RedirectCrossesHost=false\n--- PASS: TestSchemeDowngradeNotDetectedAsCrossHost (0.00s)\n```\n\nAfter applying the one-line fix above (also adding scheme comparison), the\ntoken is correctly stripped and all existing redirect tests\n(`TestClientRemovesSecurityTokenOnCrossHostRedirect`,\n`TestClientDoesNotRestoreSecurityTokenAfterCrossHostRedirect`,\n`TestClientKeepsSecurityTokenOnSameHostRedirect`,\n`TestClientStopsAfterTenRedirects`) continue to pass.\n\nA minimal fix commit is ready and can be pushed to a private fork once this\nreport is acknowledged.","aliases":["BIT-rclone-2026-79782","CVE-2026-79782","GO-2026-6196"],"modified":"2026-09-25T14:25:44.301703326Z","published":"2026-08-05T20:43:11Z","database_specific":{"github_reviewed_at":"2026-08-05T20:43:11Z","nvd_published_at":null,"cwe_ids":["CWE-319","CWE-522"],"severity":"LOW","github_reviewed":true},"references":[{"type":"WEB","url":"https://github.com/rclone/rclone/security/advisories/GHSA-gx4c-2hqx-cw2r"},{"type":"WEB","url":"https://github.com/rclone/rclone/commit/1a28451ea6fc8ac1806b0e9923dcb5b3f543f7fa"},{"type":"PACKAGE","url":"https://github.com/rclone/rclone"},{"type":"WEB","url":"https://github.com/rclone/rclone/releases/tag/v1.74.4"}],"affected":[{"package":{"name":"github.com/rclone/rclone","ecosystem":"Go","purl":"pkg:golang/github.com/rclone/rclone"},"ranges":[{"type":"SEMVER","events":[{"introduced":"0"},{"fixed":"1.74.4"}]}],"database_specific":{"last_known_affected_version_range":"\u003c= 1.74.3","source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/08/GHSA-gx4c-2hqx-cw2r/GHSA-gx4c-2hqx-cw2r.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:A/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N"}]}