Someone found a second door in a room most people never enter
CVE-2026-17495 is a path-traversal bypass in moment.js's moment.locale() function affecting versions 2.29.2 through 2.30.x, fixed in 2.31.0. The original CVE-2022-24785 fix added string validation to locale names, but this new variant passes a crafted *non-string* value (e.g., an object or array with a toString() override) that slips past the check, allowing dir/../../filename-style traversal to load unintended locale files on server-side Node.js deployments. Browser-side usage is completely unaffected. Reporter: mattjohnsonpint.
The vendor rates this MEDIUM at 5.9, and even that feels generous. The CVSS vector (AC:H) already concedes the attack is hard to pull off — the application must take raw, unvalidated, non-string user input and funnel it directly into moment.locale(). In practice, almost no application does this. Locale selection is nearly always hardcoded, drawn from an allowlist, or coerced to a string long before moment ever sees it. The impact is integrity-only (C:N/I:H/A:N): you can trick the server into loading the wrong locale data file, but you cannot read arbitrary files or achieve code execution through this path alone. noisgate downgrades to LOW 3.0.
3 steps from start to impact.
Identify a server-side Node.js app using moment.js 2.29.2–2.30.x
moment.locale(). This is the hardest prerequisite — the vast majority of moment.js installs are client-side bundles where locale loading uses bundled data, not filesystem paths.- Target runs Node.js server-side
- Target uses moment >= 2.29.2 and < 2.31.0
- A user-controlled input reaches moment.locale()
- Most moment usage is browser-side where this is irrelevant
- Most server apps hardcode locale or use an allowlist
- Moment.js is in maintenance mode; new server-side adoption is near zero
Craft a non-string payload that bypasses type validation
{toString: () => '../../etc/passwd'} or an array with traversal elements) that passes the string-type check added in 2.29.2. The application must pass this non-string value to moment.locale() without type coercion. This requires the app's input parsing to preserve the object type — typical with JSON body parsers that don't sanitize or flatten.- Application accepts JSON or other structured input
- Input is not type-coerced to string before reaching moment.locale()
- Express/Koa middleware commonly coerces query params to strings
- Type-safe frameworks (TypeScript strict) catch this at compile time
- Input validation libraries (Joi, Zod, ajv) reject non-string locale params
../ payload, but are not specifically tuned for this vector.Trigger unauthorized locale file load
moment.locale() resolves the crafted path, Node.js attempts to require() a file outside the locale directory. The impact is loading an unintended JavaScript module as a 'locale definition.' If no attacker-controlled .js file exists at the traversed path, the require fails silently or throws. Integrity impact: the app's date formatting may use attacker-chosen locale data.- A .js file exists at the traversed path that Node can parse as a module
- The file's export structure is compatible with moment's locale format expectations
- Node.js require() is strict about module format — loading arbitrary files usually throws
- No confidentiality impact: file *contents* are not returned to the attacker
- Achieving RCE requires a separate file-upload primitive to plant a .js payload
--policy flag blocks unauthorized module loads.The supporting signals.
| In-the-wild exploitation | None observed. No campaigns, no threat-actor tooling, no mentions on attacker forums as of 2026-09-15. |
|---|---|
| Proof-of-concept | No public PoC exploit repository found. The advisory describes the technique (non-string locale bypass) but no weaponized code is circulating. |
| EPSS | Not yet scored (CVE published same day). Expect < 1st percentile given the prerequisite chain. |
| KEV status | Not listed on CISA KEV catalog. |
| CVSS vector | CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N — Network-reachable but high complexity, no auth required, integrity-only impact. No confidentiality or availability loss. |
| Affected versions | moment 2.29.2 through 2.30.x (npm). Versions prior to 2.29.2 are affected by the original CVE-2022-24785 instead. |
| Fixed version | moment 2.31.0. No distro backports expected — moment is an npm package consumed directly. |
| Exposure data | ~27M weekly npm downloads, but the vast majority are browser bundles or transitive dependencies. Server-side Node.js installs passing user input to moment.locale() represent a tiny fraction — estimated <0.1% of the install base is reachable. |
| Disclosure date | 2026-09-15 (GHSA-4p3w-j4w9-5jqw published). |
| Reporter | mattjohnsonpint via GitHub Security Advisory. |
noisgate verdict.
The single most decisive factor is the vanishingly small reachable population: the bug only fires on server-side Node.js apps that pass raw non-string user input to moment.locale(), a pattern estimated at well under 0.1% of the ~27M-download install base. Integrity-only impact with no path to code execution absent a separate file-upload primitive further caps the real-world risk.
Why this verdict
- Friction — server-side only: moment.js's ~27M weekly npm downloads are overwhelmingly browser bundles and transitive dependencies. The locale *filesystem* path traversal only fires in Node.js server-side contexts, eliminating the vast majority of the install base.
- Friction — non-string input prerequisite: The application must pass an unvalidated, non-string (object/array) value directly into
moment.locale(). Standard web frameworks coerce query parameters to strings; JSON body parsers could preserve objects, but any input validation layer (Joi, Zod, TypeScript strict mode) blocks this. - Friction — AC:H acknowledged by vendor: The CVSS vector itself rates Attack Complexity as High, meaning even the vendor recognizes the conditions are unusual.
- Impact ceiling — integrity only, no RCE:
C:N/I:H/A:Nmeans the attacker can load wrong locale data but cannot read files or execute arbitrary code through this path alone. Chaining to RCE requires a *separate* file-upload vulnerability. - Role multiplier: moment.js is a date-formatting utility library, not a high-value-role component (not a DC, hypervisor, IdP, PAM, backup agent, or network edge). Even when embedded in a CI/CD pipeline or production API server, this locale-loading traversal does not escalate to supply-chain pivot, domain takeover, or fleet compromise. No high-value-role floor applies.
Why not higher?
The MEDIUM vendor rating assumes a generic server-side Node.js deployment where user input reaches moment.locale() unchecked. In practice, the intersection of (a) server-side moment usage, (b) non-string user input reaching the locale API, and (c) no input validation is extremely rare. The integrity-only impact with no path to RCE or data exfiltration does not justify MEDIUM in a risk-prioritized enterprise queue.
Why not lower?
IGNORE would require that the vulnerability is entirely theoretical or affects no production code. Moment.js *is* used server-side in legacy Node.js applications, and JSON body parsers *can* deliver non-string objects. The traversal is real and reproducible under the right conditions, so documenting and eventually patching is appropriate — just not urgently.
What to do — in priority order.
- Add type validation before moment.locale() calls — Insert a guard like
if (typeof locale !== 'string') return;before any call tomoment.locale(userInput). This is a one-line fix that neutralizes the bypass entirely. Deploy within your standard change window — no noisgate mitigation SLA applies for LOW severity. - Upgrade moment to 2.31.0 — The definitive fix. Run
npm update momentor pin"moment": ">=2.31.0"in package.json. Under the noisgate remediation SLA for LOW, treat this as backlog hygiene with no hard deadline, but bundle it with your next dependency refresh. - Migrate off moment.js entirely — Moment.js is in maintenance mode and the project itself recommends alternatives (date-fns, Luxon, Day.js). If you are still using moment server-side, this CVE is another reason to schedule migration. This is a strategic action, not an emergency.
- WAF path-traversal rules — The payload is inside a JSON body field as a non-string type (object/array). Most WAFs inspect string values for
../patterns but do not deeply inspect JSON object structures for traversal payloads embedded in toString() overrides. - Network segmentation — This is an application-layer library vulnerability. Network controls do not prevent a web request from delivering a crafted JSON body to an endpoint that calls moment.locale().
Crowdsourced verification payload.
Run this on any host where your Node.js application is deployed, or in your CI pipeline. Requires node in PATH. Invoke: bash check_moment_cve2026_17495.sh. No elevated privileges needed.
#!/usr/bin/env bash
# check_moment_cve2026_17495.sh
# Checks whether the installed moment.js version is vulnerable to CVE-2026-17495
# (path traversal via non-string locale name, affects 2.29.2 through 2.30.x)
# Exit codes: 0 = PATCHED, 1 = VULNERABLE, 2 = UNKNOWN
set -euo pipefail
# Try to find moment version from node_modules
if [ -f "node_modules/moment/package.json" ]; then
VERSION=$(node -e "console.log(require('./node_modules/moment/package.json').version)" 2>/dev/null)
elif command -v npm &>/dev/null; then
VERSION=$(npm ls moment --json 2>/dev/null | node -e "
const fs = require('fs');
const data = JSON.parse(fs.readFileSync('/dev/stdin','utf8'));
const deps = data.dependencies || {};
if (deps.moment) { console.log(deps.moment.version); }
else { process.exit(1); }
" 2>/dev/null) || true
fi
if [ -z "${VERSION:-}" ]; then
echo "UNKNOWN - moment.js not found in this project"
exit 2
fi
echo "Detected moment.js version: $VERSION"
# Parse semver components
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3 | cut -d- -f1)
# Vulnerable range: >= 2.29.2 and < 2.31.0
if [ "$MAJOR" -eq 2 ]; then
if [ "$MINOR" -eq 29 ] && [ "$PATCH" -ge 2 ]; then
echo "VULNERABLE - version $VERSION is in affected range (2.29.2 - 2.30.x)"
exit 1
elif [ "$MINOR" -eq 30 ]; then
echo "VULNERABLE - version $VERSION is in affected range (2.29.2 - 2.30.x)"
exit 1
elif [ "$MINOR" -ge 31 ]; then
echo "PATCHED - version $VERSION is >= 2.31.0"
exit 0
else
echo "PATCHED - version $VERSION is below 2.29.2 (not affected by this CVE; check CVE-2022-24785 instead)"
exit 0
fi
else
echo "UNKNOWN - unexpected major version $MAJOR"
exit 2
fiIf you remember one thing.
moment.locale() — a pattern that is vanishingly rare in production. noisgate reassesses this as LOW (3.0), downgraded from the vendor's MEDIUM 5.9. Under the noisgate remediation SLA for LOW, there is no hard deadline — treat this as backlog and bundle the moment upgrade to 2.31.0 into your next scheduled dependency refresh. There is no noisgate mitigation SLA for LOW findings. If you do have server-side moment usage with user-controlled locale input, add a typeof locale === 'string' guard as a quick fix. Longer term, consider migrating off moment.js entirely — the library is in maintenance mode and the ecosystem has moved on.Sources
- GHSA-4p3w-j4w9-5jqw — moment.js Non-String Locale Path Traversal
- CVE-2022-24785 — Original moment.js Path Traversal (predecessor)
- CWE-27: Path Traversal 'dir/../../filename'
- moment.js npm package — download statistics
- Snyk — moment.js vulnerability database
- Moment.js Project Status — maintenance mode announcement
- PkgPulse — Why Developers Are Abandoning Moment.js in 2026
What defenders are saying.
Crowdsourced verification outputs.
Results submitted by users who ran the verification payload against their environment.