{"id":"GHSA-hrvq-x7jp-36xv","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` | `\u003e= 13.10.0, \u003c 22.7.10`; `\u003e= 23.0.0, \u003c 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\u003e [!IMPORTANT]\n\u003e `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","aliases":["CVE-2026-104853"],"modified":"2026-10-05T23:45:08.231333021Z","published":"2026-10-05T23:28:35Z","database_specific":{"github_reviewed_at":"2026-10-05T23:28:35Z","nvd_published_at":"2026-10-02T17:17:03Z","cwe_ids":["CWE-22","CWE-73"],"severity":"MODERATE","github_reviewed":true},"references":[{"type":"WEB","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":"WEB","url":"https://github.com/nrwl/nx/pull/36887"},{"type":"WEB","url":"https://github.com/nrwl/nx/commit/38cd82a0c05e8212e538bdb87ccb198d6504e58f"},{"type":"WEB","url":"https://github.com/nrwl/nx/commit/95474ab457e8e1dbdbefad29040c166105a8fb90"},{"type":"WEB","url":"https://github.com/nrwl/nx/commit/de37c5ebd1852fac700d70726326236ac398af3f"},{"type":"PACKAGE","url":"https://github.com/nrwl/nx"},{"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"}],"affected":[{"package":{"name":"nx","ecosystem":"npm","purl":"pkg:npm/nx"},"ranges":[{"type":"SEMVER","events":[{"introduced":"13.10.0"},{"fixed":"22.7.10"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-hrvq-x7jp-36xv/GHSA-hrvq-x7jp-36xv.json"}},{"package":{"name":"nx","ecosystem":"npm","purl":"pkg:npm/nx"},"ranges":[{"type":"SEMVER","events":[{"introduced":"23.0.0"},{"fixed":"23.2.1"}]}],"database_specific":{"source":"https://github.com/github/advisory-database/blob/main/advisories/github-reviewed/2026/10/GHSA-hrvq-x7jp-36xv/GHSA-hrvq-x7jp-36xv.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","score":"CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:P/VC:L/VI:H/VA:L/SC:N/SI:N/SA:N"}]}