Sep 30, 2026·6 min read·71 visits
An incomplete fix in Moment.js allows attackers to bypass directory traversal protections by passing a structured object instead of a string to moment.locale(), triggering dynamic execution or file loading via type confusion.
Moment.js versions 2.29.2 through 2.30.1 are vulnerable to a Path Traversal flaw (CWE-27) on server-side Node.js environments when dynamic locales are configured. The vulnerability stems from an object-coercion bypass in the locale-name sanitization routine, which assumes incoming variables are string primitives. An attacker can pass a structured object with custom 'match' and 'toString' properties to bypass regex-based directory checks, leading to arbitrary file loading via Node's internal 'require()' call.
Moment.js is a widely used legacy JavaScript date library handling date parsing, validation, manipulation, and formatting. In server-side Node.js applications, the library dynamically loads locale configurations on demand. This localization capability introduces a server-side file access attack surface if user input determines the chosen locale.
The vulnerability, tracked as CVE-2026-17495, is an object-coercion bypass that leads to path traversal (CWE-27) via type confusion (CWE-843). It resides in the dynamic locale loading mechanism. The flaw permits remote attackers to subvert the path validation checks and load arbitrary local files or modules from the host filesystem.
This flaw represents an incomplete remediation of CVE-2022-24785. While the previous fix validated that locale names do not contain directory traversal characters like slashes, it failed to verify that the parameter passed to the validation routine is a primitive string. This omission allows attackers to leverage JavaScript's loose typing and coercion behavior to bypass the checks.
The core flaw lies in the isLocaleNameSane(name) validation helper function inside src/lib/locale/locales.js. The routine is designed to evaluate whether a locale name contains forward (/) or backward (\) slashes. It attempts to perform this validation by calling name.match('^[^/\\]*$'). If the regex matches, the function returns true, signaling that the name is clean of directory traversal sequences.
However, the function does not assert that the incoming name parameter is a string primitive. In JavaScript, an attacker-controlled JSON payload can pass a structured object instead of a string. When the runtime evaluates name.match(...), it checks if the object exposes a match method. If the attacker supplies an object with a custom match property, the JavaScript engine executes this custom function rather than the default String.prototype.match() routine.
This behavior allows the attacker to force isLocaleNameSane to return true by crafting a custom match method that always returns a truthy value. Once bypassed, the execution flow reaches the dynamic import function, where a string concatenation operation is performed: aliasedRequire('./locale/' + name). At this point, the interpreter coerces the object to a string by implicitly invoking its toString() method. By configuring the object's toString() method to return a path-traversal sequence like ../../../package.json, the attacker forces the system to execute require('./locale/../../../package.json').
The vulnerability is located in the loadLocale routine where the package lazy-loads locale files. Below is the vulnerable code structure compared to the patched implementation:
// Vulnerable Implementation (versions 2.29.2 to 2.30.1)
function isLocaleNameSane(name) {
// Validation assumes name is a primitive string
return !!(name && name.match('^[^/\\]*$'));
}
function loadLocale(name) {
if (
locales[name] === undefined &&
typeof module !== 'undefined' &&
module &&
module.exports &&
isLocaleNameSane(name)
) {
try {
oldLocale = globalLocale._abbr;
aliasedRequire = require;
// Object is concatenated here, triggering implicit coercion via toString()
aliasedRequire('./locale/' + name);
getSetGlobalLocale(oldLocale);
} catch (e) {
locales[name] = null;
}
}
return locales[name];
}The official patch introduced in commit 5f7d983c9881e65e07574de9dda3190d99520c07 mitigates this vector by strictly typing the parameter and shifting to an alphanumeric whitelist pattern. The patch eliminates both the type confusion and the path-traversal bypass:
// Patched Implementation (version 2.31.0)
function isLocaleNameSane(name) {
// Fix: Verify name is strictly a primitive string before applying regex
// Fix: Limit inputs to alphanumeric and single hyphen sequences
return typeof name === 'string' && /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(name);
}By checking typeof name === 'string', the system immediately rejects objects, preventing custom prototype execution. The whitelisting regex further ensures that only canonical, safe locale identifiers are processed.
Exploitation of this vulnerability requires the host application to accept structured user input (such as JSON or parsed query parameters) and pass it directly to moment.locale() without validation. Server frameworks using extended query parsers (e.g., the qs library with Express) often automatically deserialize nested query parameters into rich JavaScript objects, exposing a direct ingestion pathway.
An attacker can transmit a JSON payload structured to exploit this object-coercion vulnerability as shown in the following execution proof-of-concept:
// Attack Payload Object
const payload = {
match: function(regex) {
// Satisfy the validation check by returning a truthy array
return ["bypass"];
},
toString: function() {
// Return target traversal sequence relative to the locale directory
return "../../../package.json";
}
};
// Triggering the vulnerable sink
moment.locale(payload);Alternatively, if an application is vulnerable to prototype pollution, an attacker can pollute the global Object.prototype with malicious match and toString methods. When a standard, unvalidated object is later passed to moment.locale(), it will inherit the poisoned properties. This results in the path-traversal code executing automatically, even if the application did not explicitly allow dynamic object structure input.
Below is the graphical representation of the coercion pipeline bypass:
The severity of CVE-2026-17495 is classified as Medium with a CVSS v3.1 score of 5.9. The primary vector is network-based, and it requires no privileges or user interaction. However, because exploitation is constrained by the application's input processing configurations, attack complexity is considered High.
The dynamic load routine uses Node's native require() function as the execution sink. This means that successful path traversal does not simply read text files; instead, it attempts to load and execute the target as a JavaScript module. If an attacker can write or control files in any accessible directory (such as a public upload directory, /tmp, or via a secondary log injection vulnerability), they can achieve Local File Inclusion (LFI) and execute arbitrary code in the context of the Node.js application process.
Even in environments where writing arbitrary files is not possible, an attacker can cause local module discovery, leading to denial of service through application crashes, or information disclosure if the targeted JSON or JavaScript configurations contain sensitive values that the application subsequently exposes.
The primary remediation path is to upgrade moment to version 2.31.0 or higher. This update replaces the lax validation step with a strict string verification check and a narrow alphanumeric whitelist, making path-traversal and type-confusion bypasses impossible.
If upgrading is not immediately possible due to legacy constraints, developers must implement manual validation. Every parameter sourced from an HTTP request, message queue, or external database must be explicitly validated as a string primitive before being passed to Moment's locale APIs. Sanitization routines should reject any input where typeof input !== 'string'.
Additionally, implementing runtime controls such as freezing Object.prototype (e.g., via the Node.js flag --frozen-intrinsics) can prevent secondary exploitation through prototype pollution. Automated security tools such as static application security testing (SAST) and software composition analysis (SCA) should be configured to flag vulnerable versions of the package in all continuous integration pipelines.
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
moment Moment.js | >= 2.29.2, <= 2.30.1 | 2.31.0 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-27 (Path Traversal), CWE-843 (Type Confusion) |
| Attack Vector | Network |
| CVSS v3.1 | 5.9 (Medium) |
| EPSS Score | 0.00357 (0.357% probability of exploitation) |
| Impact | Local File Inclusion (LFI) / Potential Remote Code Execution (RCE) |
| Exploit Status | Proof-of-Concept Available |
| CISA KEV Status | Not Listed |
Path Traversal (CWE-27) allows unauthorized file viewing or execution, facilitated by Type Confusion (CWE-843) where an input parameter is processed using an incompatible variable type.
CVE-2026-128087 (GHSA-9264-jvgf-6w2f) is an incorrect behavior order (CWE-696) and improper authorization (CWE-863) vulnerability in the russh Rust SSH library, affecting versions 0.55.0 through 0.65.0. During the initial SSH key exchange (KEX) handshake, the client state machine invokes the client::Handler::kex_done() callback—providing the negotiated raw shared secret bytes—prior to validating the remote server's host key or certificate via check_server_key(). Consequently, an untrusted or rogue SSH server can complete cryptographic key agreement and force a connecting client to execute side-effecting application logic inside kex_done() before the server's identity is checked and rejected.
CVE-2026-128082 (GHSA-6vhm-j9h7-f9gh) is an uncontrolled recursion vulnerability (CWE-674) in AutoMapper, a convention-based object-object mapper for .NET. When mapping nested or self-referential types into destination models that rely on parameterized constructors, C# positional records, or recursive ConstructUsing expressions, AutoMapper evaluates child constructor arguments prior to incrementing the parent's recursion depth counter. This ordering flaw bypasses both explicit MaxDepth limits and the default depth limit of 64. An unauthenticated remote attacker can supply deeply nested input payloads to affected endpoints, exhausting the thread call stack and triggering an uncatchable System.StackOverflowException that terminates the hosting .NET process.
An information exposure vulnerability in the @opentelemetry/instrumentation-http package for OpenTelemetry JavaScript versions 0.204.0 prior to 0.222.0 causes incoming HTTP server spans to record raw, unredacted query string parameters under the url.query attribute. While outgoing client spans applied built-in and user-configured query parameter redaction, incoming server spans bypassed this sanitization completely, resulting in the transmission of sensitive credentials such as pre-signed cloud URLs and API tokens to external observability backends.
CVE-2026-128089 is an uncontrolled resource consumption vulnerability in the dnspython toolkit for Python, affecting all versions prior to 2.9.2. When parsing Well-Known Service (WKS) records from text via dns.rdtypes.IN.WKS.WKS.from_text(), the parser fails to validate that numeric service port identifiers fall within the RFC 1035 valid range of 0 through 65535. An attacker capable of supplying untrusted zone files or RDATA text strings containing an arbitrarily large service port integer can force the parser to allocate an unbounded bitmap array using an inefficient linear append loop, resulting in severe CPU exhaustion, application hangs, or Out-Of-Memory (OOM) process termination.
CVE-2026-126347 (GHSA-5cjw-f85j-cv6r) is an open redirect vulnerability (CWE-601) in Tornado, an asynchronous Python networking library and web framework. The flaw resides in tornado.web.RedirectHandler when dynamic URL redirection templates incorporate unvalidated user input. Due to a parser differential between RFC 3986 (used by Python's urllib.parse.urlsplit) and the WHATWG URL Standard (used by modern web browsers), inputs containing three or more leading slashes bypass Tornado 6.6.0's validation logic, allowing unauthenticated attackers to redirect users to arbitrary external domains.
A Denial of Service (DoS) vulnerability exists in @sveltejs/adapter-node versions 5.5.7 and 6.0.0-next.0 through 6.0.0. When a SvelteKit server route returns a direct response from a fetch() call with a Content-Type of text/event-stream, the Node.js adapter attempts to mutate the response headers to disable proxy buffering. Because headers returned directly by the WHATWG Fetch API carry an immutable guard, this mutation throws an unhandled TypeError, terminating the underlying Node.js process and denying service to all active and subsequent clients.