Sep 30, 2026·6 min read·5 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.
A stack-based Denial of Service (DoS) vulnerability via uncontrolled recursion in the brace-expansion library prior to versions 1.1.20, 2.1.6, 3.0.8, and 5.0.11 allows unauthenticated remote attackers to trigger native stack exhaustion, terminating the Node.js process via a crafted payload containing deeply nested brace groups.
An uncontrolled resource consumption vulnerability exists in the brace-expansion JavaScript library. Due to an algorithmic flaw in parsing a legacy Bash-compatibility quirk involving {a},b}-shaped expansion structures, untrusted inputs containing many trailing closing braces trigger successive full-input rescans. This behavior yields quadratic CPU time complexity and high memory overhead, allowing remote, unauthenticated attackers to cause a Denial of Service (DoS) by blocking the single-threaded Node.js event loop.
A denial-of-service vulnerability exists in the ip-address npm package prior to version 10.7.1. The library fails to limit the length of input strings parsed by the Address4 and Address6 constructors. When parsing highly malformed addresses, the diagnostic parser runs a synchronous regular expression search-and-replace that generates descriptive HTML error messages. Passing an excessively long string containing invalid characters causes severe memory amplification and CPU starvation, resulting in a thread hang or process crash in Node.js applications.
The ip-address library is vulnerable to a logical bypass in its subnet containment methods. The functions isInSubnet() and isHostInSubnet() do not verify that compared addresses belong to the same IP family before performing masking checks. Under specific circumstances, an IPv6 address can share leading bit patterns with an IPv4 subnet, causing the containment check to evaluate as true. This allows attackers to bypass access control lists, firewalls, and server-side request forgery protection layers in applications relying on the library.
CVE-2026-101894 is a critical path traversal vulnerability in @xhmikosr/decompress before versions 10.2.2 and 11.1.4, stemming from an incomplete hardening bypass of CVE-2026-53486 where static lexical containment checks fail to detect kernel-level resolution of crafted symlink chains, allowing arbitrary local file modification and execution.
A security-critical desynchronization vulnerability exists in fast-uri versions 4.1.3 and 4.1.4. Due to incorrect order-of-operations, the mailto scheme parser validates raw percent-encoded parameter keys instead of normalized keys, but subsequently decodes and writes them into a generic headers object. When the parsed URI is serialized, these keys are re-emitted literally, allowing attackers to bypass validation boundaries and smuggle unauthorized recipients, subjects, or body parameters in downstream mailing applications.