Jun 19, 2026·6 min read·4 visits
A use-after-free vulnerability in the Nokogiri gem's CRuby extension allows remote attackers to trigger process crashes or memory corruption when updating XML attribute values.
A use-after-free (UAF) vulnerability exists in the CRuby native extension of the Nokogiri gem when updating XML attribute values. If child nodes of an XML attribute are wrapped by Ruby objects prior to setting the attribute's value, the underlying C memory structures are freed while the Ruby wrapper retains a dangling pointer. This results in memory corruption, invalid pointer dereferences, and application crashes during execution or garbage collection.
The Nokogiri library is a widely used Ruby gem for parsing, manipulating, and querying XML and HTML documents. To achieve high performance, Nokogiri relies on a native C extension that acts as a wrapper around the libxml2 library. This architecture divides application state into two distinct spaces: managed Ruby heap memory and unmanaged native system memory managed by libxml2.
The vulnerability designated as GHSA-PHWJ-RPRQ-35PP is a use-after-free (UAF) flaw classified under CWE-416. It resides within the native extension layer of Nokogiri that manages XML attribute nodes. Specifically, the flaw exists within the implementation of attribute value updates, where native memory deallocation is executed without checking the state of corresponding Ruby wrappers.
The attack surface is exposed when a Ruby application processes untrusted XML data, instantiates Ruby-side wrappers for the child nodes of XML attributes, and subsequently modifies those attributes. The resulting memory instability can be exploited to cause immediate application crashes, presenting a localized denial-of-service (DoS) vector.
The root cause of GHSA-PHWJ-RPRQ-35PP lies in the lifecycle mismatch between native libxml2 structures and Ruby proxy objects. When an XML attribute (xmlAttr) contains a text value, libxml2 represents this value internally as a linked list of child nodes (xmlNode). When a Ruby developer calls Nokogiri::XML::Attr#child or Attr#children, Nokogiri instantiates a Ruby object proxy wrapping the underlying native xmlNode structure.
To maintain a mapping between native nodes and their Ruby proxies, Nokogiri utilizes the _private field of the libxml2 xmlNode structure. This field contains a pointer back to the Ruby wrapper. This back-reference allows Nokogiri to return the same Ruby object when the same native node is accessed multiple times, preventing object duplication.
In vulnerable versions of Nokogiri, when the #value= or #content= methods are invoked on an attribute, the native C function set_value is executed in ext/nokogiri/xml_attr.c. This function immediately calls xmlFreeNodeList(attr->children) to clear the existing attribute contents from memory. The function fails to inspect the _private field of the child nodes being deallocated.
Consequently, if a child node has been wrapped in a Ruby object, the native node memory is freed, but the Ruby proxy object remains alive and active on the Ruby heap. The Ruby wrapper holds a now-dangling pointer to the deallocated C structure. Any subsequent read, write, or traversal operation involving this wrapper dereferences the invalid pointer, resulting in memory corruption or process termination.
To understand the mechanical differences between the vulnerable and patched states, analyze the implementation of the attribute update process in ext/nokogiri/xml_attr.c. In the vulnerable code, the deallocation phase unconditionally cleans the linked list of child nodes.
The patch introduced in version 1.19.4 replaces the raw set_value function with noko_xml_attr_set_value and introduces a safety loop to inspect each child node before any deallocation occurs.
/* Unlink and pin any wrapped children */
xmlNode *cur = attr->children;
xmlNode *next;
while (cur) {
next = cur->next;
if (cur->_private) {
xmlUnlinkNode(cur);
noko_xml_document_pin_node(cur);
}
cur = next;
}The added loop iterates over the linked list of children (attr->children). If cur->_private is evaluated as true, indicating that a Ruby wrapper exists, the code unlinks the node using xmlUnlinkNode(cur). This removes the node from the attribute's child list so that it will not be freed by subsequent mutation functions.
The unlinked node is then passed to noko_xml_document_pin_node(cur). This function registers the node within Nokogiri's document-level node cache. This action pins the node in native memory, ensuring its structure is preserved as long as the document or the corresponding Ruby proxy object exists, successfully eliminating the use-after-free condition.
Exploitation of GHSA-PHWJ-RPRQ-35PP requires a precise sequence of API interactions on a parsed XML structure. The initial prerequisite is the ingestion of an XML document containing an element with at least one attribute. The application must then access the attribute's children to initialize the Ruby proxy wrappers.
The following sequence represents a reliable proof of concept to demonstrate the vulnerability:
require 'nokogiri'
doc = Nokogiri::XML("<root target_attr='value_to_leak'/>")
attribute_node = doc.root.attribute("target_attr")
# This instantiates the Ruby-land proxy object
dangling_child = attribute_node.child
# This frees the native memory behind dangling_child
attribute_node.value = "new_replaced_value"
# Trigger Garbage Collection to force marking of the dangling pointer
GC.start(full_mark: true)
# Attempting to access the orphaned object triggers a segmentation fault
puts dangling_child.to_sUpon executing the mutation step (attribute_node.value = "new_replaced_value"), the C memory representing 'value_to_leak' is reclaimed by the operating system allocator. The variable dangling_child continues to point to this address.
When GC.start is called, the Ruby virtual machine traverses active heap references to mark reachable objects. During this phase, or when explicitly invoking methods on dangling_child, the VM attempts to dereference the invalid address, causing an immediate segmentation fault.
The security impact of GHSA-PHWJ-RPRQ-35PP is primarily confined to application availability. An unauthenticated remote attacker who can control XML inputs and influence the application's processing flow to trigger the vulnerable code path can cause the underlying Ruby process to terminate.
In environments running single-process web servers or worker queues, repeated process crashes can lead to a sustained denial-of-service condition. Because Nokogiri is heavily used in background jobs and document processors, this vulnerability presents an easy target for service disruption.
Achieving arbitrary code execution (RCE) via this use-after-free flaw is theoretically possible but highly complex in practice. The attacker would need to execute a precise heap grooming attack to reclaim the freed xmlNode memory allocation with controlled payload data before the pointer is dereferenced. This is difficult because of the asynchronous nature of Ruby's allocator and GC pauses.
The CVSS v4.0 score of 2.3 reflects this limited severity. The low rating is due to the high specificity of the required API usage pattern, which is not a standard pattern in most Nokogiri-based applications.
The definitive remediation for this vulnerability is to upgrade the Nokogiri dependency to version 1.19.4 or later. This release incorporates the defensive node unlinking and pinning logic in the CRuby native extension. JRuby environments are inherently unaffected as the Java-backed implementation of Nokogiri does not use this C extension.
If an immediate upgrade is not feasible, developers must audit their codebases to eliminate vulnerable API usage patterns. Avoid accessing attribute values via their child nodes. Instead, read and write attribute values using standard string-based interfaces.
# Vulnerable pattern
child_node = attr.child
attr.value = "new"
# Safe pattern
attr_value_string = attr.value
attr.value = "new"To detect instances of this vulnerability dynamically during development, test suites can be executed under the control of AddressSanitizer (ASan). Compiling Ruby and Nokogiri with ASan enabled allows immediate detection of the use-after-free invalid memory read as soon as the attribute value is changed, providing detailed backtraces of the deallocation and access points.
CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:U| Product | Affected Versions | Fixed Version |
|---|---|---|
Nokogiri Nokogiri Maintainers | < 1.19.4 | 1.19.4 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-416 |
| Vulnerability Class | Use-After-Free (UAF) |
| CVSS Score | 2.3 (Low) |
| Attack Vector | Network |
| Exploit Status | Proof-of-Concept |
| KEV Status | Not Listed |
| Patched Version | 1.19.4 |
Referencing memory after it has been freed, which can cause a program to crash, use unexpected values, or execute arbitrary code.
A critical missing authorization vulnerability exists in the API Pages Controller of Alchemy CMS. An unauthenticated remote attacker can exploit the 'nested' action to retrieve the entire nested page tree. Furthermore, by appending the query parameter '?elements=true', the attacker can extract sensitive content from draft, unpublished, and restricted pages, bypassing all access controls.
Nokogiri is a popular Ruby gem used for parsing XML and HTML documents. A Use-After-Free (UAF) vulnerability exists in its CRuby implementation during XInclude processing. When an application traverses an XML document and exposes nodes to Ruby before calling `do_xinclude`, the underlying C library `libxml2` can free these structures in-place. This leaves active Ruby objects holding pointers to freed memory, leading to potential segmentation faults, memory corruption, or information disclosure.
A client-side Stored Cross-Site Scripting (XSS) vulnerability exists in the JupyterLab Extension Manager. This vulnerability allows an attacker to register a malicious package on the Python Package Index (PyPI) with a crafted metadata homepage URL using the 'javascript:' pseudo-protocol. When a JupyterLab user opens the Extension Manager and clicks the extension name, the browser executes arbitrary JavaScript code within the context of the JupyterLab origin. This can lead to the theft of active workspace documents, credentials, and API tokens. The issue affects all versions of JupyterLab prior to version 4.5.9.
An arbitrary Remote Code Execution (RCE) vulnerability exists in ouroboros-ai due to an incomplete fix for CVE-2026-47211. Ouroboros automatically loads environment configurations from local .env files located in the current working directory (CWD) of cloned repositories. Although a denylist (_UNTRUSTED_ENV_DENYLIST) was introduced in version 0.39.0 to filter out execution-routing environment variables, multiple critical configuration variables were omitted, enabling complete sandbox bypass and arbitrary system command execution.
An OS command injection vulnerability (CWE-78) exists in agentic-flow versions 2.0.13 and prior. The package's Model Context Protocol (MCP) server tools directly interpolate user-controlled parameters into shell command strings executed via child_process.execSync without validation. If an AI agent processes untrusted external input and forwards it as parameters to any affected tool, an attacker can break out of the shell argument quotes and execute arbitrary OS commands on the host machine.
A high-severity denial of service vulnerability in the undici WebSocket client (CVE-2026-12151) arises from uncontrolled memory consumption. Although undici validates individual fragment sizes against a cumulative payload limit, it fails to cap the total number of frames in a single message stream. This allows a rogue or compromised WebSocket server to send an infinite sequence of small or empty continuation frames, causing unbounded memory allocation and eventual heap exhaustion on the client process.