---
id: "CVE-2026-128069"
page: "https://infrastack.ai/live/CVE-2026-128069"
kind: "vulnerability"
aliases: ["CVE-2026-128069", "GHSA-h7wh-q2qr-458c"]
title: "rails-html-sanitizer: Possible XSS via shared scrubber state in concurrent calls"
severity: "medium"
cvss: 4.7
epss: null
exploited: false
status: "open"
category: "package"
products: [{"name": "rails-html-sanitizer", "fixed": ["1.7.2"], "affected": "< 1.7.2", "ecosystem": "rubygems"}]
ecosystems: ["rubygems"]
tags: ["cwe-362", "cwe-79", "multi-threaded", "package", "puma", "rails", "ruby"]
not_affected_if: ["custom-scrubber", "single-configuration", "single-threaded"]
published_at: "2026-11-13T18:09:16+00:00"
first_seen_at: "2026-11-13T18:12:12+00:00"
updated_at: "2026-11-13T18:13:40+00:00"
sources: [{"source": "ghsa", "url": "https://github.com/advisories/GHSA-h7wh-q2qr-458c", "first_seen_at": "2026-11-13T18:12:12+00:00"}, {"source": "nvd", "url": "https://nvd.nist.gov/vuln/detail/CVE-2026-128069", "first_seen_at": "2026-11-13T18:49:29+00:00"}]
exploit_sources: []
---

# rails-html-sanitizer: Possible XSS via shared scrubber state in concurrent calls

Link for people: https://infrastack.ai/live/CVE-2026-128069 (this .md is for agents)

rails-html-sanitizer shares one scrubber across sanitize calls in a process, so concurrent calls with different allowlists can interleave. An attacker can get a permissive allowlist applied to another request, letting script tags or event-handler attributes through to stored or rendered output (cross-site scripting).

_Summary, check and fix written by a model from the sources; labelled as such._

## Affected

- rubygems:rails-html-sanitizer affected < 1.7.2 fixed 1.7.2

## How to check

- Check the installed gem version with `bundle exec gem list rails-html-sanitizer` or in Gemfile.lock; affected below `1.7.2`.
- Confirm the app runs more than one thread per process (for example Puma with multiple threads).
- Search the code for `sanitize` calls that pass `tags:` or `attributes:` options, or that use no options while other calls differ.

## Fix

- rails-html-sanitizer: move to `1.7.2`

## Timeline

- 2026-11-13T18:12:12+00:00 ghsa https://github.com/advisories/GHSA-h7wh-q2qr-458c
- 2026-11-13T18:49:29+00:00 nvd https://nvd.nist.gov/vuln/detail/CVE-2026-128069

## Documents

- [advisory] Maintainer's advisory: api.github.com https://api.github.com/repos/rails/rails-html-sanitizer/security-advisories/GHSA-h7wh-q2qr-458c
- [advisory] Maintainer's advisory: github.com https://github.com/rails/rails-html-sanitizer/security/advisories/GHSA-h7wh-q2qr-458c
- [fix] Patch: github.com https://github.com/rails/rails-html-sanitizer/commit/15f764c3563d5dd9b97ad32eaa7454577ccec4b9
- [fix] Release: github.com https://github.com/rails/rails-html-sanitizer/releases/tag/v1.7.2

## From the sources

## Summary

`Rails::HTML::SafeListSanitizer` (both the HTML4 and HTML5 variants) shares one `Rails::HTML::PermitScrubber` across every `sanitize` call on the same instance, assigning its `tags` and `attributes` on each call without synchronization. Two concurrent `sanitize` calls with different `tags:` or `attributes:` options can interleave, so one call is scrubbed with the other call's allowlist.

- Versions affected: `< 1.7.2`
- Not affected: none
- Fixed versions: `1.7.2`

## Impact

Action View memoizes a single `SafeListSanitizer` per helper module and uses it for every `sanitize` call in the process, so in a multi-threaded server (for example Puma) all requests share the same `PermitScrubber`. If any code path calls `sanitize` with a more permissive allowlist, an attacker who can trigger that path concurrently with another request can cause the other request to be sanitized with the permissive allowlist. If the permissive allowlist admits `script` or an event-handler attribute, the strict call emits it unchanged, which is a cross-site scripting vector when the strict output is stored or rendered to other users.

Applications are affected only if they run more than one thread per process and some `sanitize` call passes `tags:` or `attributes:` options that differ from another call's, including a call that passes no options and so uses the defaults. Applications that use a single configuration everywhere, pass their own `scrubber:`, or run single-threaded are not affected.

## Workarounds

Use the same allowlist for every `sanitize` call, or pass a `scrubber:` built per call (`Rails::HTML::PermitScrubber.new` with `tags=` and `attributes=` set) instead of `tags:` and `attributes:` options.

## Credit

Reported by hiumee via HackerOne.

---

_Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai_
