{"id":"GHSA-2w9f-fxwj-w2qf","kind":"vulnerability","aliases":["GHSA-2w9f-fxwj-w2qf"],"related":[],"title":"Possible XSS vulnerability in Active Support MessagePack serializer","description":"### Impact\n`ActiveSupport::MessagePack` serializes an `ActiveSupport::SafeBuffer` as its bytes alone and deserializes it as a new `SafeBuffer`, so a buffer that is no longer `html_safe?` (for example, one mutated in place with `gsub!`) is restored as `html_safe`. If such a buffer holds untrusted input and is stored through a MessagePack serializer, rendering it after it is loaded bypasses ERB auto-escaping, possibly leading to XSS.\n\nApplications on Rails 8.1 are affected if they use a MessagePack serializer, such as `config.action_dispatch.cookies_serializer = :message_pack` or `:message_pack_allow_marshal`, or `serializer: :message_pack` for `Rails.cache`, `ActiveSupport::MessageVerifier` or `ActiveSupport::MessageEncryptor`, and store a `SafeBuffer` that was mutated in place with untrusted input. Rails 8.0 and earlier do not serialize `SafeBuffer` with MessagePack and are not affected.\n\n### Details\nThe fixed releases serialize a `SafeBuffer` that is not `html_safe?` with a marker and deserialize it as a `String`; a `SafeBuffer` that is `html_safe?` keeps the existing format. Versions without the fix cannot read an unsafe buffer serialized by the fixed releases, so during a rolling deploy a cookie or cache entry holding one could fail to load on a process that has not been upgraded.\n\n### Workarounds\nApplications that cannot upgrade can convert a `SafeBuffer` to a `String` with `to_str` before storing it in a value that a MessagePack serializer writes.\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/4ca090665976e6fbc471c38689b399f84b43cdc7.patch\n\n### Credit\nThis issue was responsibly reported by HackerOne researcher [jcwilk](https://hackerone.com/jcwilk).","summary":"ActiveSupport's MessagePack serializer restores a SafeBuffer that was mutated to be unsafe as html_safe. If that buffer holds untrusted input and is stored and later rendered, ERB auto-escaping is bypassed, which can lead to cross-site scripting.","severity":"medium","cvss":4.7,"epss":null,"exploited":false,"exploited_at":null,"products":[{"name":"activesupport","fixed":["8.1.4.1"],"affected":">= 8.1, < 8.1.4.1","ecosystem":"rubygems"}],"ecosystems":["rubygems"],"tags":["activesupport","cwe-79","package","rails","ruby","self-hosted"],"not_affected_if":["default-serializer","no-untrusted-safebuffer","rails-8.0-or-earlier"],"how_to_check":"- Check the Rails version in the Gemfile.lock for `activesupport` and confirm it is `8.1.x` below `8.1.4.1`\n- Search config for `cookies_serializer = :message_pack` or `:message_pack_allow_marshal`, and `serializer: :message_pack` on `Rails.cache`, `MessageVerifier` or `MessageEncryptor`\n- Check whether untrusted input is stored as a SafeBuffer modified in place (for example with `gsub!`) before being written through one of those serializers","fix":"- `activesupport` (rubygems) `8.1.4.1`, for Rails 8.1 applications\n- Rails 8.0 and earlier are not affected\n- Maintainer patch for those who cannot upgrade at once: https://github.com/rails/rails/commit/4ca090665976e6fbc471c38689b399f84b43cdc7.patch","status":"open","published_at":"2026-11-04T03:01:57+00:00","first_seen_at":"2026-11-09T22:53:29+00:00","triaged_at":"2026-11-09T22:57:12+00:00","updated_at":"2026-11-09T22:57:12+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-09T22:57:12Z","model":"claude-haiku-5-5","output":{"fix":"- `activesupport` (rubygems) `8.1.4.1`, for Rails 8.1 applications\n- Rails 8.0 and earlier are not affected\n- Maintainer patch for those who cannot upgrade at once: https://github.com/rails/rails/commit/4ca090665976e6fbc471c38689b399f84b43cdc7.patch","reach":"unknown","title":"Rails: MessagePack serializer can mark untrusted text as safe HTML","impact":"medium","summary":"ActiveSupport's MessagePack serializer restores a SafeBuffer that was mutated to be unsafe as html_safe. If that buffer holds untrusted input and is stored and later rendered, ERB auto-escaping is bypassed, which can lead to cross-site scripting.","audience":"businesses","category":"package","nicknames":[],"who_cares":["rails","ruby","activesupport","self-hosted"],"confidence":0.8,"fix_status":"available","needs_auth":"unknown","worst_case":"Untrusted text renders unescaped, enabling cross-site scripting in users' browsers.","analyst_note":"","how_to_check":"- Check the Rails version in the Gemfile.lock for `activesupport` and confirm it is `8.1.x` below `8.1.4.1`\n- Search config for `cookies_serializer = :message_pack` or `:message_pack_allow_marshal`, and `serializer: :message_pack` on `Rails.cache`, `MessageVerifier` or `MessageEncryptor`\n- Check whether untrusted input is stored as a SafeBuffer modified in place (for example with `gsub!`) before being written through one of those serializers","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["rails-8.0-or-earlier","default-serializer","no-untrusted-safebuffer"],"who_is_affected":"Applications on Rails 8.1 that use a MessagePack serializer for cookies, cache entries or message verifiers or encryptors, and that store a SafeBuffer modified in place with untrusted input. Rails 8.0 and earlier do not serialize SafeBuffer with MessagePack and are not affected. Apps using the default serializers can skip it.","user_interaction":"unknown","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"medium"},"cost_usd":0.000922,"input_tokens":5244,"output_tokens":796,"severity_before":"medium"},"analyst_status":null,"analyst_note":null,"search":"'-79':22B '/jcwilk).':329C '/rails/rails/commit/4ca090665976e6fbc471c38689b399f84b43cdc7.patch':316C '2w9f':11A,16A '8.0':187C '8.1':143C,313C 'activ':5A 'activesupport':19A,20B,29B,72C,76C,169C,172C 'affect':145C,199C 'allow':161C 'alon':81C 'applic':140C,270C 'auto':59B,134C 'auto-escap':58B,133C 'avail':300C 'buffer':47B,91C,114C,239C 'bypass':62B,131C 'byte':80C 'cach':253C 'cannot':235C,272C,309C 'config.action_dispatch.cookies':154C 'convert':275C 'cooki':251C 'could':257C 'credit':317C 'cross':68B 'cross-sit':67B 'cwe':21B 'deploy':249C 'deseri':83C,216C 'detail':200C 'earlier':189C 'entri':254C 'erb':57B,132C 'escap':60B,135C 'exampl':99C 'exist':229C 'fail':258C 'fix':202C,234C,243C,297C 'format':230C 'fxwj':12A,17A 'ghsa':10A,15A 'ghsa-2w9f-fxwj-w2qf':9A,14A 'github.com':315C 'github.com/rails/rails/commit/4ca090665976e6fbc471c38689b399f84b43cdc7.patch':314C 'gsub':105C 'hackeron':324C 'hackerone.com':328C 'hackerone.com/jcwilk).':327C 'hold':48B,115C,255C 'host':28B 'html':43B,96C,109C,210C,225C 'impact':71C 'input':50B,117C,185C 'issu':319C 'jcwilk':326C 'keep':227C 'later':55B 'lead':65B,137C 'load':130C,260C 'locat':304C 'longer':95C 'marker':214C 'marshal':162C 'messag':156C,159C,165C 'messageencryptor':173C 'messagepack':7A,31B,73C,123C,150C,195C,292C 'messageverifi':170C 'mutat':38B,101C,180C 'new':87C 'normal':303C 'one':100C,256C 'pack':157C,160C,166C 'packag':23B 'patch':305C 'place':103C,182C 'possibl':1A,136C 'process':263C 'rail':24B,142C,186C 'rails.cache':168C 'read':236C 'releas':203C,244C,295C,298C 'render':56B,125C 'report':322C 'research':325C 'respons':321C 'restor':33B,107C 'roll':248C 'rubi':25B 'safe':44B,97C,110C,211C,226C 'safebuff':35B,77C,88C,177C,193C,206C,222C,277C 'script':70B 'self':27B 'self-host':26B 'serial':8A,32B,74C,124C,151C,155C,164C,192C,204C,240C,293C 'site':69B 'store':53B,120C,175C,285C 'str':283C 'string':220C,280C 'support':6A 'unsaf':41B,238C 'untrust':49B,116C,184C 'upgrad':268C,273C,310C 'use':148C 'valu':289C 'version':231C 'vulner':3A 'w2qf':13A,18A 'without':232C 'workaround':269C 'write':294C 'xss':2A,139C","nicknames":[],"mitigations":[],"who_affected":"Applications on Rails 8.1 that use a MessagePack serializer for cookies, cache entries or message verifiers or encryptors, and that store a SafeBuffer modified in place with untrusted input. Rails 8.0 and earlier do not serialize SafeBuffer with MessagePack and are not affected. Apps using the default serializers can skip it.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"pkeys":["activesupport"],"ptokens":["activesupport"],"labels":["activesupport","cwe79","medium","package","rails","ruby","rubygems","selfhosted"],"campaign_type":null,"search_a":"'2w9f':11A,16A 'activ':5A 'activesupport':19A 'fxwj':12A,17A 'ghsa':10A,15A 'ghsa-2w9f-fxwj-w2qf':9A,14A 'messagepack':7A 'possibl':1A 'serial':8A 'support':6A 'vulner':3A 'w2qf':13A,18A 'xss':2A","search_b":"'-79':22B 'activesupport':20B,29B 'auto':59B 'auto-escap':58B 'buffer':47B 'bypass':62B 'cross':68B 'cross-sit':67B 'cwe':21B 'erb':57B 'escap':60B 'hold':48B 'host':28B 'html':43B 'input':50B 'later':55B 'lead':65B 'messagepack':31B 'mutat':38B 'packag':23B 'rail':24B 'render':56B 'restor':33B 'rubi':25B 'safe':44B 'safebuff':35B 'script':70B 'self':27B 'self-host':26B 'serial':32B 'site':69B 'store':53B 'unsaf':41B 'untrust':49B","search_c":"'/jcwilk).':329C '/rails/rails/commit/4ca090665976e6fbc471c38689b399f84b43cdc7.patch':316C '8.0':187C '8.1':143C,313C 'activesupport':72C,76C,169C,172C 'affect':145C,199C 'allow':161C 'alon':81C 'applic':140C,270C 'auto':134C 'auto-escap':133C 'avail':300C 'buffer':91C,114C,239C 'bypass':131C 'byte':80C 'cach':253C 'cannot':235C,272C,309C 'config.action_dispatch.cookies':154C 'convert':275C 'cooki':251C 'could':257C 'credit':317C 'deploy':249C 'deseri':83C,216C 'detail':200C 'earlier':189C 'entri':254C 'erb':132C 'escap':135C 'exampl':99C 'exist':229C 'fail':258C 'fix':202C,234C,243C,297C 'format':230C 'github.com':315C 'github.com/rails/rails/commit/4ca090665976e6fbc471c38689b399f84b43cdc7.patch':314C 'gsub':105C 'hackeron':324C 'hackerone.com':328C 'hackerone.com/jcwilk).':327C 'hold':115C,255C 'html':96C,109C,210C,225C 'impact':71C 'input':117C,185C 'issu':319C 'jcwilk':326C 'keep':227C 'lead':137C 'load':130C,260C 'locat':304C 'longer':95C 'marker':214C 'marshal':162C 'messag':156C,159C,165C 'messageencryptor':173C 'messagepack':73C,123C,150C,195C,292C 'messageverifi':170C 'mutat':101C,180C 'new':87C 'normal':303C 'one':100C,256C 'pack':157C,160C,166C 'patch':305C 'place':103C,182C 'possibl':136C 'process':263C 'rail':142C,186C 'rails.cache':168C 'read':236C 'releas':203C,244C,295C,298C 'render':125C 'report':322C 'research':325C 'respons':321C 'restor':107C 'roll':248C 'safe':97C,110C,211C,226C 'safebuff':77C,88C,177C,193C,206C,222C,277C 'serial':74C,124C,151C,155C,164C,192C,204C,240C,293C 'store':120C,175C,285C 'str':283C 'string':220C,280C 'unsaf':238C 'untrust':116C,184C 'upgrad':268C,273C,310C 'use':148C 'valu':289C 'version':231C 'without':232C 'workaround':269C 'write':294C 'xss':139C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-11-04T03:01:57+00:00","kev":null,"cvss_vector":null,"cvss_by":null,"poc_count":0,"sources":[{"source":"ghsa-repo","external_id":"GHSA-2w9f-fxwj-w2qf","url":"https://github.com/rails/rails/security/advisories/GHSA-2w9f-fxwj-w2qf","severity":"medium","published_at":"2026-11-04T03:01:57+00:00","modified_at":"2026-11-04T03:01:57+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":110680,"url":"https://github.com/rails/rails/security/advisories/GHSA-2w9f-fxwj-w2qf","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"}