{"schema":"threatline.record/2","record":{"id":"CVE-2026-104853","aliases":["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` | `>= 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","categories":["Libraries & packages"],"product":"nx","vendor":"nrwl","ecosystem":"npm","affected_ranges":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-hrvq-x7jp-36xv","source_modified_at":"2026-10-05T23:28:36Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"nx"},"range":">= 13.10.0, < 22.7.10"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-hrvq-x7jp-36xv","source_modified_at":"2026-10-05T23:28:36Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"nx"},"range":">= 23.0.0, < 23.2.1"},{"source":"CVE List","source_url":"https://www.cve.org/CVERecord?id=CVE-2026-104853","source_modified_at":"2026-10-02T17:41:54.417Z","fetched_at":"2026-10-23T18:45:31.882637Z","product":"nx","vendor":"nrwl","versions":[{"version":">= 13.10.0, < 22.7.10","status":"affected"},{"version":">= 23.0.0, < 23.2.1","status":"affected"}],"defaultStatus":null},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-hrvq-x7jp-36xv","source_modified_at":"2026-10-05T23:45:08.231333021Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"nx","purl":"pkg:npm/nx"},"ranges":[{"events":[{"introduced":"13.10.0"},{"fixed":"22.7.10"}],"type":"SEMVER"}],"versions":[]},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-hrvq-x7jp-36xv","source_modified_at":"2026-10-05T23:45:08.231333021Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"nx","purl":"pkg:npm/nx"},"ranges":[{"events":[{"introduced":"23.0.0"},{"fixed":"23.2.1"}],"type":"SEMVER"}],"versions":[]}],"fixed_versions":[{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-hrvq-x7jp-36xv","source_modified_at":"2026-10-05T23:28:36Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"nx"},"version":"22.7.10"},{"source":"GitHub Advisory Database","source_url":"https://github.com/advisories/GHSA-hrvq-x7jp-36xv","source_modified_at":"2026-10-05T23:28:36Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"nx"},"version":"23.2.1"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-hrvq-x7jp-36xv","source_modified_at":"2026-10-05T23:45:08.231333021Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"nx","purl":"pkg:npm/nx"},"version":"22.7.10"},{"source":"OSV","source_url":"https://osv.dev/vulnerability/GHSA-hrvq-x7jp-36xv","source_modified_at":"2026-10-05T23:45:08.231333021Z","fetched_at":"2026-10-23T18:42:43.628194Z","package":{"ecosystem":"npm","name":"nx","purl":"pkg:npm/nx"},"version":"23.2.1"}],"cvss":5.8,"severity":"Medium","kev":{"listed":false},"epss":{"probability":0.00174,"percentile":0.0652,"score_date":"2026-10-23"},"published_at":"2026-10-02T16:52:07.624Z","modified_at":"2026-10-05T23:45:08.231333021Z","provenance":[{"source":"GitHub Advisory Database","url":"https://github.com/advisories/GHSA-hrvq-x7jp-36xv","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-05T23:28:36Z"},{"source":"CVE List","url":"https://www.cve.org/CVERecord?id=CVE-2026-104853","fetched_at":"2026-10-23T18:45:31.882637Z","source_modified_at":"2026-10-02T17:41:54.417Z"},{"source":"OSV","url":"https://osv.dev/vulnerability/GHSA-hrvq-x7jp-36xv","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-05T23:45:08.231333021Z"},{"source":"EPSS","url":"https://api.first.org/data/v1/epss?cve=CVE-2026-104853","fetched_at":"2026-10-23T18:42:43.628194Z","source_modified_at":"2026-10-23"}],"sources":["GitHub Advisory Database","CVE List","OSV","EPSS"],"references":[{"source":"CVE List","url":"https://github.com/nrwl/nx/security/advisories/GHSA-hrvq-x7jp-36xv","tags":["x_refsource_CONFIRM"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-104853","source":"GitHub Advisory Database"},{"source":"CVE List","url":"https://github.com/nrwl/nx/pull/36887","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/nrwl/nx/commit/38cd82a0c05e8212e538bdb87ccb198d6504e58f","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/nrwl/nx/commit/95474ab457e8e1dbdbefad29040c166105a8fb90","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/nrwl/nx/commit/de37c5ebd1852fac700d70726326236ac398af3f","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/nrwl/nx/releases/tag/22.7.10","tags":["x_refsource_MISC"]},{"source":"CVE List","url":"https://github.com/nrwl/nx/releases/tag/23.2.1","tags":["x_refsource_MISC"]},{"url":"https://github.com/advisories/GHSA-hrvq-x7jp-36xv","source":"GitHub Advisory Database"}],"locations":[],"motoko_assessment":{"status":"requires_architecture_review","automatic_remediation":false},"cve_state":"PUBLISHED","ssvc":{"exploitation":"none","automatable":"no","technical_impact":"partial","assessed_at":"2026-10-02T17:41:40.332975Z"},"classification":{"method":"product-first software-layer rules","rules_version":"2.1","matched_on":"ecosystem","confidence":"heuristic","reviewed":false},"change_timestamp":"2026-10-05T23:45:08.231333021Z","content_hash":"7635a03ac543b8163e5c04d5eacc8d27242a163845337c884ab66696e823eed7"}}
