{"id":"CVE-2026-104853","aliases":["GHSA-hrvq-x7jp-36xv"],"url":"https://o3.security/vulnerability/CVE-2026-104853","summary":"Nx: Path traversal in nx migrate package-migrations extraction","details":"## Summary\n\n`nx migrate` reads each target package's `nx-migrations.migrations` value from its manifest and extracts the referenced file to a path built by joining that value onto a temporary directory. The value is never validated, so a package whose `migrations` field contains `..` segments (or an absolute path) steers the extraction to write outside the temporary directory. A hostile package — or any package pulled in transitively through a trusted package's `packageGroup` — can write attacker-controlled content, or truncate an existing file, anywhere the running user can write. This happens during migration *planning*, before the user reviews the migration list and before `--run-migrations`, so it does not require the user to approve or execute anything.\n\n**Most workspaces need no action.** By default `nx migrate` does not run the nx installed in your workspace — it installs `nx@latest` into a temporary directory and performs the upgrade planning, including this extraction, with that copy. Now that a patched nx is the latest release, a default `nx migrate` run is unaffected whatever version the workspace has installed. The installed version only runs, and is only then exposed, when that hand-off is bypassed — see Remediation.\n\n## Severity\n\nExploitable when the victim runs `nx migrate` against a package the attacker controls, directly or through a trusted package's `packageGroup`. The primary impact is a file write with attacker-controlled content and no path confinement; overwriting an auto-loaded file (a shell rc, a git hook, a CI script) escalates that write to code execution. There is no known evidence of exploitation in the wild.\n\n## Affected & Patched Versions\n\n| Package | Vulnerable | Patched |\n| --- | --- | --- |\n| `nx` | `>= 13.10.0, < 22.7.10`; `>= 23.0.0, < 23.2.1` | `22.7.10`, `23.2.1` |\n\nEvery version in the ranges above is affected. The lower bound is 13.10.0, the first release where `nx migrate` extracted a package's migrations file from its tarball; earlier versions resolved migrations without that extraction.\n\n> [!IMPORTANT]\n> `nx migrate` normally fetches and runs `nx@latest` rather than the nx installed in your workspace. The ranges above therefore say where the vulnerable code ships, not who is exposed — it only runs when that hand-off is bypassed.\n\n## Remediation\n\n**If you run `nx migrate` normally, there is nothing to do.** It resolves and runs the latest nx, which is patched, so your workspace's own nx version does not matter for this flaw.\n\nUpgrade only if you bypass that hand-off and run the workspace's nx instead — that is, if you set `NX_USE_LOCAL` or `NX_MIGRATE_USE_LOCAL`, pin `NX_MIGRATE_CLI_VERSION` to an affected version, resume an existing run with `--run-id`, or run where the temporary install fails and `nx migrate` falls back to the local nx. In those cases upgrade to **22.7.10** (22.x line) or **23.2.1** (23.x line) or later:\n\n```\nnx migrate 23.2.1\n```\n\nThe fix is a drop-in — no configuration changes are required, and no legitimate `migrations` value is affected (real packages reference `./migrations.json` or another path within their own directory, all of which remain valid). Either way, do not run `nx migrate` against packages, or `packageGroup` members, that you do not trust.\n\n## Details\n\nWhile planning an upgrade, `nx migrate` extracts each target package's migrations file to a destination built by joining the package's own `nx-migrations.migrations` value onto a temporary directory. That value is read from the manifest without validation, and it is handled asymmetrically: the name Nx matches against the archive entries is normalized (so its `..` segments collapse), while the destination path it writes to is a raw join that keeps the `..` segments and resolves outside the temporary directory. Because the attacker controls the tarball, they name their entry to equal the normalized form; the match then succeeds and the bytes are written to the un-normalized, escaping destination. The normalization is not a defence — it only dictates what the attacker must name their entry.\n\nThe same value also seeds the directory used for prompt-file extraction, which has the same shape, so both writes are steerable from the one field.\n\nTwo distinct primitives fall out of this:\n\n- **Truncation** — the destination write stream is opened, and truncates, before any tar entry is inspected. So a package that points `migrations` at an existing file empties that file even when no tar entry matches — no crafted archive required.\n- **Controlled write** — when a tar entry's name matches, its bytes are written to the escaping destination. The extractor performs no path containment of its own.\n\n## Credits\n\n- **Arkadiusz Marta** (RE:SOURCE) — Reporter","published":"2026-10-02T16:52:07.624Z","modified":"2026-10-21T02:54:33.096465187Z","cvss":null,"epss":null,"cisaKev":null,"exploitsKnown":null,"affectedPackages":[{"ecosystem":"npm","name":"nx","fixedVersion":"22.7.10"},{"ecosystem":"npm","name":"nx","fixedVersion":"23.2.1"}],"fix":{"url":"https://github.com/nrwl/nx/commit/38cd82a0c05e8212e538bdb87ccb198d6504e58f","label":"nrwl/nx@38cd82a"},"references":[{"type":"WEB","url":"https://github.com/nrwl/nx/releases/tag/22.7.10"},{"type":"WEB","url":"https://github.com/nrwl/nx/releases/tag/23.2.1"},{"type":"ADVISORY","url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/104xxx/CVE-2026-104853.json"},{"type":"ADVISORY","url":"https://github.com/nrwl/nx/security/advisories/GHSA-hrvq-x7jp-36xv"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-104853"},{"type":"FIX","url":"https://github.com/nrwl/nx/commit/38cd82a0c05e8212e538bdb87ccb198d6504e58f"},{"type":"FIX","url":"https://github.com/nrwl/nx/commit/95474ab457e8e1dbdbefad29040c166105a8fb90"},{"type":"FIX","url":"https://github.com/nrwl/nx/commit/de37c5ebd1852fac700d70726326236ac398af3f"},{"type":"FIX","url":"https://github.com/nrwl/nx/pull/36887"},{"type":"PACKAGE","url":"https://github.com/nrwl/nx"}],"provenance":{"sources":["OSV.dev","FIRST.org (EPSS)"],"lastVerified":"2026-10-21T02:54:33.096465187Z"}}