← Back to Feed CACHED · 2026-09-15 06:11:24 · CACHE_KEY CVE-2026-17495
CVE-2026-17495 · CWE-27 · Disclosed 2026-09-15

moment.js Path Traversal via Crafted Non-String Locale Name

ASSESSED — NOISGATE
Vendor
—
—
—
Reassessed
—
—
—
Verdict: —
Do you agree?
01 · The Real Story

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.

"Bypass of old fix, but only fires if your app feeds raw non-string user input into moment.locale()"
02 · The Attack Path

3 steps from start to impact.

STEP 01

Identify a server-side Node.js app using moment.js 2.29.2–2.30.x

The attacker must find an internet-facing Node.js application that imports the moment library and exposes a user-controlled parameter that eventually reaches 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.
Conditions required:
  • Target runs Node.js server-side
  • Target uses moment >= 2.29.2 and < 2.31.0
  • A user-controlled input reaches moment.locale()
Where this breaks in practice:
  • 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
Detection/coverage: SCA scanners (Snyk, npm audit, Dependabot) flag the vulnerable version range. No runtime detection signature needed at this step.
STEP 02

Craft a non-string payload that bypasses type validation

The attacker constructs an object (e.g., {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.
Conditions required:
  • Application accepts JSON or other structured input
  • Input is not type-coerced to string before reaching moment.locale()
Where this breaks in practice:
  • 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
Detection/coverage: WAF rules inspecting for path traversal sequences in JSON values can catch the ../ payload, but are not specifically tuned for this vector.
STEP 03

Trigger unauthorized locale file load

When 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.
Conditions required:
  • 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
Where this breaks in practice:
  • 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
Detection/coverage: Runtime application monitoring (Datadog APM, Dynatrace) can flag unexpected require() paths. Node.js --policy flag blocks unauthorized module loads.
03 · Compensating Control

1
Add type validation before moment.locale() calls — Insert a guard like if (typeof locale !== 'string') return; before any call to moment.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.
2
Upgrade moment to 2.31.0 — The definitive fix. Run npm update moment or 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.
3
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.
What doesn't work
  • 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().
04 · Intelligence Metadata

The supporting signals.

In-the-wild exploitationNone observed. No campaigns, no threat-actor tooling, no mentions on attacker forums as of 2026-09-15.
Proof-of-conceptNo public PoC exploit repository found. The advisory describes the technique (non-string locale bypass) but no weaponized code is circulating.
EPSSNot yet scored (CVE published same day). Expect < 1st percentile given the prerequisite chain.
KEV statusNot listed on CISA KEV catalog.
CVSS vectorCVSS: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 versionsmoment 2.29.2 through 2.30.x (npm). Versions prior to 2.29.2 are affected by the original CVE-2022-24785 instead.
Fixed versionmoment 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 date2026-09-15 (GHSA-4p3w-j4w9-5jqw published).
Reportermattjohnsonpint via GitHub Security Advisory.

Sources.

  1. GHSA-4p3w-j4w9-5jqw — moment.js Non-String Locale Path Traversal
  2. CVE-2022-24785 — Original moment.js Path Traversal (predecessor)
  3. CWE-27: Path Traversal 'dir/../../filename'
  4. moment.js npm package — download statistics
  5. Snyk — moment.js vulnerability database
  6. Moment.js Project Status — maintenance mode announcement
  7. PkgPulse — Why Developers Are Abandoning Moment.js in 2026
05 · The Call

Final Verdict
↓ DOWNGRADED to LOW (3.0/10)

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:N means 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.

06 · Verification

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.

noisgate-verify.sh
BASHREAD-ONLYSAFE
#!/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
fi