{"id":"GHSA-3w93-6hmw-whh4","kind":"vulnerability","aliases":["GHSA-3w93-6hmw-whh4"],"related":[],"title":"Unsigned Content-Type in Active Storage GCS direct uploads with cache_control","description":"### Impact\nWhen a GCS service is configured with the `cache_control` option, `ActiveStorage::Service::GCSService#url_for_direct_upload` signs direct-upload URLs with V4, and the V4 signature covers only the `Cache-Control` and `Host` headers. A direct-upload client can therefore send a `Content-Type` of its choosing, and can omit the declared `Content-MD5`, without invalidating the URL. GCS stores the supplied content type as object metadata, which takes precedence over the `response-content-type` that Active Storage sets on download URLs, so an uploaded object could be served as inline HTML under the application's bucket path on `storage.googleapis.com`. Script in such an object runs on the storage origin rather than the application's, but could register a Service Worker scoped to the bucket path and read or replace a victim's later responses for signed URLs in that bucket.\n\nApplications that use the GCS service with `cache_control` configured and accept direct uploads are affected. GCS services without `cache_control` sign with V2 and are not affected.\n\n### Details\nThe fixed releases sign every GCS direct-upload URL with V2, which binds an empty `Content-Type` and the declared `Content-MD5`. The configured `Cache-Control` is still returned by `headers_for_direct_upload` and sent by the client, but it is no longer part of the signature, and direct-upload URLs for these services are no longer limited to V4's seven-day maximum expiry. See the Active Storage CHANGELOG.\n\n### Workarounds\nRemoving the `cache_control` option from the GCS service configuration makes Active Storage sign direct uploads with V2.\n\n### Releases\nThe fixed releases are available at the normal locations.\n\nPatches for those who cannot upgrade at once:\n- 8.1: https://github.com/rails/rails/commit/8d93f6a872e9550a91bf0e4fce50083a235ef3b1.patch\n- 8.0: https://github.com/rails/rails/commit/ddc08ba61a8001e7402864c62e466cf732fc7614.patch\n\n### Credit\nThis issue was responsibly reported by HackerOne researcher [thepo1es](https://hackerone.com/thepo1es).","summary":"When an Active Storage GCS service has the cache_control option set, direct-upload URLs are signed with V4, which does not cover Content-Type or Content-MD5. A client can upload an object with a chosen content type, such as HTML, that GCS may then serve from the bucket path on storage.googleapis.com.","severity":"medium","cvss":4.7,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"activestorage","fixed":["8.1.4.1"],"affected":">= 8.1, < 8.1.4.1","ecosystem":"rubygems"},{"name":"activestorage","fixed":["8.0.5.2"],"affected":"< 8.0.5.2","ecosystem":"rubygems"}],"ecosystems":["rubygems"],"tags":["activestorage","cwe-345","gcs","package","rails","ruby"],"not_affected_if":["no-cache-control-option","no-direct-uploads","non-gcs-storage"],"how_to_check":"- Check the Rails version in `Gemfile.lock` for `activestorage`, against the affected ranges\n- Look in `config/storage.yml` for a GCS service that sets `cache_control`\n- Check whether that service accepts direct uploads from clients","fix":"- `activestorage` 8.1.x: move to `8.1.4.1`\n- `activestorage` 8.0.x: move to `8.0.5.2`\n- Workaround: remove the `cache_control` option from the GCS service configuration","status":"open","published_at":"2026-11-04T03:01:59+00:00","first_seen_at":"2026-11-09T22:53:29+00:00","triaged_at":"2026-11-09T22:57:10+00:00","updated_at":"2026-11-09T22:57:10+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-09T22:57:10Z","model":"claude-haiku-5-5","output":{"fix":"- `activestorage` 8.1.x: move to `8.1.4.1`\n- `activestorage` 8.0.x: move to `8.0.5.2`\n- Workaround: remove the `cache_control` option from the GCS service configuration","reach":"internet","title":"Rails Active Storage: GCS direct uploads let clients set Content-Type","impact":"medium","summary":"When an Active Storage GCS service has the cache_control option set, direct-upload URLs are signed with V4, which does not cover Content-Type or Content-MD5. A client can upload an object with a chosen content type, such as HTML, that GCS may then serve from the bucket path on storage.googleapis.com.","audience":"businesses","category":"package","nicknames":[],"who_cares":["rails","activestorage","gcs","ruby"],"confidence":0.8,"fix_status":"available","needs_auth":"unknown","worst_case":"uploaded objects served as inline HTML on the storage origin","analyst_note":"","how_to_check":"- Check the Rails version in `Gemfile.lock` for `activestorage`, against the affected ranges\n- Look in `config/storage.yml` for a GCS service that sets `cache_control`\n- Check whether that service accepts direct uploads from clients","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["no-cache-control-option","no-direct-uploads","non-gcs-storage"],"who_is_affected":"Applications that use the Active Storage GCS service with cache_control configured and that accept direct uploads from clients. GCS services without cache_control sign with V2 and are not affected.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"medium"},"cost_usd":0.000823,"input_tokens":5291,"output_tokens":588,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-345':28B '/rails/rails/commit/8d93f6a872e9550a91bf0e4fce50083a235ef3b1.patch':385C '/rails/rails/commit/ddc08ba61a8001e7402864c62e466cf732fc7614.patch':389C '/thepo1es).':402C '3w93':16A,21A '6hmw':17A,22A '8.0':386C '8.1':382C 'accept':250C 'activ':6A,35B,174C,342C,357C 'activestorag':26B,101C 'activestorage':24A,25A 'affect':254C,266C 'applic':192C,211C,239C 'avail':369C 'bind':281C 'bucket':85B,194C,222C,238C 'cach':12A,41B,98C,123C,246C,258C,296C,348C 'cache-control':122C,295C 'cannot':378C 'changelog':344C 'choos':142C 'chosen':72B 'client':65B,132C,310C 'configur':95C,248C,294C,355C 'content':3A,58B,62B,73B,138C,149C,159C,171C,285C,291C 'content-md5':61B,148C,290C 'content-typ':2A,57B,137C,284C 'control':13A,42B,99C,124C,247C,259C,297C,349C 'could':184C,214C 'cover':56B,119C 'credit':390C 'cwe':27B 'day':337C 'declar':147C,289C 'detail':267C 'direct':9A,46B,106C,110C,130C,251C,275C,304C,322C,360C 'direct-upload':45B,109C,129C,274C,321C 'download':178C 'empti':283C 'everi':272C 'expiri':339C 'fix':269C,366C 'gcs':8A,29B,37B,79B,92C,155C,243C,255C,273C,353C 'gcsservic':103C 'ghsa':15A,20A 'ghsa-3w93-6hmw-whh4':14A,19A 'github.com':384C,388C 'github.com/rails/rails/commit/8d93f6a872e9550a91bf0e4fce50083a235ef3b1.patch':383C 'github.com/rails/rails/commit/ddc08ba61a8001e7402864c62e466cf732fc7614.patch':387C 'hackeron':397C 'hackerone.com':401C 'hackerone.com/thepo1es).':400C 'header':127C,302C 'host':126C 'html':77B,189C 'impact':89C 'inlin':188C 'invalid':152C 'issu':392C 'later':231C 'limit':331C 'locat':373C 'longer':315C,330C 'make':356C 'maximum':338C 'may':80B 'md5':63B,150C,292C 'metadata':163C 'normal':372C 'object':69B,162C,183C,202C 'omit':145C 'option':43B,100C,350C 'origin':207C 'packag':30B 'part':316C 'patch':374C 'path':86B,195C,223C 'preced':166C 'rail':31B 'rather':208C 'read':225C 'regist':215C 'releas':270C,364C,367C 'remov':346C 'replac':227C 'report':395C 'research':398C 'respons':170C,232C,394C 'response-content-typ':169C 'return':300C 'rubi':32B 'run':203C 'scope':219C 'script':198C 'see':340C 'send':135C 'sent':307C 'serv':82B,186C 'servic':38B,93C,102C,217C,244C,256C,327C,354C 'set':44B,176C 'seven':336C 'seven-day':335C 'sign':50B,108C,234C,260C,271C,359C 'signatur':118C,319C 'still':299C 'storag':7A,36B,175C,206C,343C,358C 'storage.googleapis.com':88B,197C 'store':156C 'suppli':158C 'take':165C 'thepo1es':399C 'therefor':134C 'type':4A,59B,74B,139C,160C,172C,286C 'unsign':1A 'upgrad':379C 'upload':10A,47B,67B,107C,111C,131C,182C,252C,276C,305C,323C,361C 'url':48B,104C,112C,154C,179C,235C,277C,324C 'use':241C 'v2':262C,279C,363C 'v4':52B,114C,117C,333C 'victim':229C 'whh4':18A,23A 'without':151C,257C 'workaround':345C 'worker':218C","nicknames":[],"mitigations":[],"who_affected":"Applications that use the Active Storage GCS service with cache_control configured and that accept direct uploads from clients. GCS services without cache_control sign with V2 and are not affected.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"pkeys":["activestorage"],"ptokens":["activestorage"],"labels":["activestorage","cwe345","gcs","medium","package","rails","ruby","rubygems"],"campaign_type":null,"search_a":"'3w93':16A,21A '6hmw':17A,22A 'activ':6A 'activestorage':24A,25A 'cach':12A 'content':3A 'content-typ':2A 'control':13A 'direct':9A 'gcs':8A 'ghsa':15A,20A 'ghsa-3w93-6hmw-whh4':14A,19A 'storag':7A 'type':4A 'unsign':1A 'upload':10A 'whh4':18A,23A","search_b":"'-345':28B 'activ':35B 'activestorag':26B 'bucket':85B 'cach':41B 'chosen':72B 'client':65B 'content':58B,62B,73B 'content-md5':61B 'content-typ':57B 'control':42B 'cover':56B 'cwe':27B 'direct':46B 'direct-upload':45B 'gcs':29B,37B,79B 'html':77B 'may':80B 'md5':63B 'object':69B 'option':43B 'packag':30B 'path':86B 'rail':31B 'rubi':32B 'serv':82B 'servic':38B 'set':44B 'sign':50B 'storag':36B 'storage.googleapis.com':88B 'type':59B,74B 'upload':47B,67B 'url':48B 'v4':52B","search_c":"'/rails/rails/commit/8d93f6a872e9550a91bf0e4fce50083a235ef3b1.patch':385C '/rails/rails/commit/ddc08ba61a8001e7402864c62e466cf732fc7614.patch':389C '/thepo1es).':402C '8.0':386C '8.1':382C 'accept':250C 'activ':174C,342C,357C 'activestorag':101C 'affect':254C,266C 'applic':192C,211C,239C 'avail':369C 'bind':281C 'bucket':194C,222C,238C 'cach':98C,123C,246C,258C,296C,348C 'cache-control':122C,295C 'cannot':378C 'changelog':344C 'choos':142C 'client':132C,310C 'configur':95C,248C,294C,355C 'content':138C,149C,159C,171C,285C,291C 'content-md5':148C,290C 'content-typ':137C,284C 'control':99C,124C,247C,259C,297C,349C 'could':184C,214C 'cover':119C 'credit':390C 'day':337C 'declar':147C,289C 'detail':267C 'direct':106C,110C,130C,251C,275C,304C,322C,360C 'direct-upload':109C,129C,274C,321C 'download':178C 'empti':283C 'everi':272C 'expiri':339C 'fix':269C,366C 'gcs':92C,155C,243C,255C,273C,353C 'gcsservic':103C 'github.com':384C,388C 'github.com/rails/rails/commit/8d93f6a872e9550a91bf0e4fce50083a235ef3b1.patch':383C 'github.com/rails/rails/commit/ddc08ba61a8001e7402864c62e466cf732fc7614.patch':387C 'hackeron':397C 'hackerone.com':401C 'hackerone.com/thepo1es).':400C 'header':127C,302C 'host':126C 'html':189C 'impact':89C 'inlin':188C 'invalid':152C 'issu':392C 'later':231C 'limit':331C 'locat':373C 'longer':315C,330C 'make':356C 'maximum':338C 'md5':150C,292C 'metadata':163C 'normal':372C 'object':162C,183C,202C 'omit':145C 'option':100C,350C 'origin':207C 'part':316C 'patch':374C 'path':195C,223C 'preced':166C 'rather':208C 'read':225C 'regist':215C 'releas':270C,364C,367C 'remov':346C 'replac':227C 'report':395C 'research':398C 'respons':170C,232C,394C 'response-content-typ':169C 'return':300C 'run':203C 'scope':219C 'script':198C 'see':340C 'send':135C 'sent':307C 'serv':186C 'servic':93C,102C,217C,244C,256C,327C,354C 'set':176C 'seven':336C 'seven-day':335C 'sign':108C,234C,260C,271C,359C 'signatur':118C,319C 'still':299C 'storag':175C,206C,343C,358C 'storage.googleapis.com':197C 'store':156C 'suppli':158C 'take':165C 'thepo1es':399C 'therefor':134C 'type':139C,160C,172C,286C 'upgrad':379C 'upload':107C,111C,131C,182C,252C,276C,305C,323C,361C 'url':104C,112C,154C,179C,235C,277C,324C 'use':241C 'v2':262C,279C,363C 'v4':114C,117C,333C 'victim':229C 'without':151C,257C 'workaround':345C 'worker':218C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-04T03:01:59+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-3w93-6hmw-whh4","url":"https://github.com/rails/rails/security/advisories/GHSA-3w93-6hmw-whh4","severity":"medium","published_at":"2026-11-04T03:01:59+00:00","modified_at":"2026-11-04T03:01:59+00:00","first_seen_at":"2026-11-09T22:53:29+00:00","fetched_at":"2026-11-09T22:53:29+00:00","note":null}],"documents":[{"id":110678,"url":"https://github.com/rails/rails/security/advisories/GHSA-3w93-6hmw-whh4","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa-repo","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"}],"disclosures":[],"exploit_sources":[],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}