CVE-2026-97209 (GCVE-0-2026-97209)
Vulnerability from cvelistv5 – Published: 2026-10-30 08:59 – Updated: 2026-10-30 16:50
VLAI
EPSS
VEX
Title
Insecure temporary file in mix deps.partition lets a local user execute code in another user's dependency build
Summary
Insecure Temporary File vulnerability in Mix deps.partition in Elixir allows a local user to execute code as another user via a swapped partition config file. Partitioned dependency compilation passes the project configuration to each partition VM through a file in a fixed-name directory under the shared system temporary directory, and the partition trusts what it reads back. Another local user on the same host can exploit this during a mix deps.compile run that uses partitioned compilation.
Mix.Tasks.Deps.Partition.server/4 in lib/mix/lib/mix/tasks/deps.partition.ex creates the directory with File.mkdir_p/1, which accepts an existing directory without checking its owner, so a user who created it first can replace the file before client/0 reads it.
This issue affects mix: from 1.20.0-rc.4 before 1.20.5.
Severity
SSVC
Exploitation: poc
Automatable: no
Technical Impact: total
CISA Coordinator · CISA-ADP (v2.0.3)
Decision recorded 2026-10-30 16:50 UTC
CWE
- CWE-377 - Insecure Temporary File
Assigner
References
6 references
Impacted products
2 products
| Vendor | Product | Version | CPE status | |
|---|---|---|---|---|
| elixir-lang | elixir |
Affected:
1.20.0-rc.4 , < 1.20.5
(semver)
cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:* |
||
| elixir-lang | elixir |
Affected:
7f3d95b60c28ae3026a5c1bf943166391245bfd1 , < *
(git)
cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:* |
Date Public
2026-10-30 08:59
{
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-97209",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "no"
},
{
"Technical Impact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-30T16:50:17.250968Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-30T16:50:46.465Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/elixir-lang/elixir/security/advisories/GHSA-96q6-xhwp-fc8q"
}
],
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Deps.Partition\u0027"
],
"packageName": "mix",
"packageURL": "pkg:otp/mix?repository_url=https:%2F%2Fgithub.com%2Felixir-lang%2Felixir\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Felixir-lang%2Felixir.git",
"product": "elixir",
"programFiles": [
"lib/mix/tasks/deps.partition.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/3"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/4"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:client/0"
}
],
"repo": "https://github.com/elixir-lang/elixir",
"vendor": "elixir-lang",
"versions": [
{
"lessThan": "1.20.5",
"status": "affected",
"version": "1.20.0-rc.4",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Deps.Partition\u0027"
],
"packageName": "elixir-lang/elixir",
"packageURL": "pkg:github/elixir-lang/elixir",
"product": "elixir",
"programFiles": [
"lib/mix/lib/mix/tasks/deps.partition.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/3"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/4"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:client/0"
}
],
"repo": "https://github.com/elixir-lang/elixir",
"vendor": "elixir-lang",
"versions": [
{
"changes": [
{
"at": "55eb91220a1de14dc5aa27029d1f45b0f5832b2d",
"status": "unaffected"
},
{
"at": "9adbe955c8664f4be77d879dd38c003dd39fa777",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "7f3d95b60c28ae3026a5c1bf943166391245bfd1",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eThe attack requires partitioned dependency compilation, which is off by default and enabled by setting \u003ccode\u003eMIX_OS_DEPS_COMPILE_PARTITION_COUNT\u003c/code\u003e to a value greater than one, a \u003ccode\u003emix deps.compile\u003c/code\u003e run that compiles at least two non-local dependencies, and a system temporary directory shared with other local users, which is the default on Linux when \u003ccode\u003eTMPDIR\u003c/code\u003e is unset. Platforms with per-user temporary directories, such as macOS and Windows by default, are not affected unless \u003ccode\u003eTMPDIR\u003c/code\u003e points at a shared location.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "The attack requires partitioned dependency compilation, which is off by default and enabled by setting `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` to a value greater than one, a `mix deps.compile` run that compiles at least two non-local dependencies, and a system temporary directory shared with other local users, which is the default on Linux when `TMPDIR` is unset. Platforms with per-user temporary directories, such as macOS and Windows by default, are not affected unless `TMPDIR` points at a shared location."
}
],
"value": "The attack requires partitioned dependency compilation, which is off by default and enabled by setting MIX_OS_DEPS_COMPILE_PARTITION_COUNT to a value greater than one, a mix deps.compile run that compiles at least two non-local dependencies, and a system temporary directory shared with other local users, which is the default on Linux when TMPDIR is unset. Platforms with per-user temporary directories, such as macOS and Windows by default, are not affected unless TMPDIR points at a shared location."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*",
"versionEndExcluding": "1.20.5",
"versionStartIncluding": "1.20.0-rc.4",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Quinn Wilton"
},
{
"lang": "en",
"type": "reporter",
"value": "Quinn Wilton"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Jos\u00e9 Valim"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-15T10:43:20.000Z",
"datePublic": "2026-10-30T08:59:17.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eInsecure Temporary File vulnerability in Mix \u003ccode\u003edeps.partition\u003c/code\u003e in Elixir allows a local user to execute code as another user via a swapped partition config file. Partitioned dependency compilation passes the project configuration to each partition VM through a file in a fixed-name directory under the shared system temporary directory, and the partition trusts what it reads back. Another local user on the same host can exploit this during a \u003ccode\u003emix deps.compile\u003c/code\u003e run that uses partitioned compilation.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eMix.Tasks.Deps.Partition.server/4\u003c/code\u003e in \u003ccode\u003elib/mix/lib/mix/tasks/deps.partition.ex\u003c/code\u003e creates the directory with \u003ccode\u003eFile.mkdir_p/1\u003c/code\u003e, which accepts an existing directory without checking its owner, so a user who created it first can replace the file before \u003ccode\u003eclient/0\u003c/code\u003e reads it.\u003c/p\u003e\n\u003cp\u003eThis issue affects mix: from 1.20.0-rc.4 before 1.20.5.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Insecure Temporary File vulnerability in Mix `deps.partition` in Elixir allows a local user to execute code as another user via a swapped partition config file. Partitioned dependency compilation passes the project configuration to each partition VM through a file in a fixed-name directory under the shared system temporary directory, and the partition trusts what it reads back. Another local user on the same host can exploit this during a `mix deps.compile` run that uses partitioned compilation.\n\n`Mix.Tasks.Deps.Partition.server/4` in `lib/mix/lib/mix/tasks/deps.partition.ex` creates the directory with `File.mkdir_p/1`, which accepts an existing directory without checking its owner, so a user who created it first can replace the file before `client/0` reads it.\n\nThis issue affects mix: from 1.20.0-rc.4 before 1.20.5."
}
],
"value": "Insecure Temporary File vulnerability in Mix deps.partition in Elixir allows a local user to execute code as another user via a swapped partition config file. Partitioned dependency compilation passes the project configuration to each partition VM through a file in a fixed-name directory under the shared system temporary directory, and the partition trusts what it reads back. Another local user on the same host can exploit this during a mix deps.compile run that uses partitioned compilation.\n\nMix.Tasks.Deps.Partition.server/4 in lib/mix/lib/mix/tasks/deps.partition.ex creates the directory with File.mkdir_p/1, which accepts an existing directory without checking its owner, so a user who created it first can replace the file before client/0 reads it.\n\nThis issue affects mix: from 1.20.0-rc.4 before 1.20.5."
}
],
"impacts": [
{
"capecId": "CAPEC-26",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eA local user on a shared host who creates the partition directory before the victim\u0027s build replaces the serialized configuration and runs arbitrary shell commands as the developer or CI user whose \u003ccode\u003emix deps.compile\u003c/code\u003e uses partitioned compilation.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "A local user on a shared host who creates the partition directory before the victim\u0027s build replaces the serialized configuration and runs arbitrary shell commands as the developer or CI user whose `mix deps.compile` uses partitioned compilation."
}
],
"value": "A local user on a shared host who creates the partition directory before the victim\u0027s build replaces the serialized configuration and runs arbitrary shell commands as the developer or CI user whose mix deps.compile uses partitioned compilation."
}
]
},
{
"capecId": "CAPEC-155",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eEven without pre-creating the directory, the world-readable partition file discloses the project configuration, including \u003ccode\u003eMix.install\u003c/code\u003e compile-time config, to other local users while the build runs.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Even without pre-creating the directory, the world-readable partition file discloses the project configuration, including `Mix.install` compile-time config, to other local users while the build runs."
}
],
"value": "Even without pre-creating the directory, the world-readable partition file discloses the project configuration, including Mix.install compile-time config, to other local users while the build runs."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "LOCAL",
"baseScore": 5.4,
"baseSeverity": "MEDIUM",
"privilegesRequired": "LOW",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "PASSIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-377",
"description": "CWE-377 Insecure Temporary File",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-30T09:01:05.212Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "Insecure temporary file in mix deps.partition lets a local user execute code in another user\u0027s dependency build",
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/elixir-lang/elixir/security/advisories/GHSA-96q6-xhwp-fc8q"
},
{
"name": "EEF CNA record for CVE-2026-97209",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-97209.html"
},
{
"name": "OSV record EEF-CVE-2026-97209",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-97209"
},
{
"name": "Introducing commit 7f3d95b in elixir-lang/elixir",
"tags": [
"related"
],
"url": "https://github.com/elixir-lang/elixir/commit/7f3d95b60c28ae3026a5c1bf943166391245bfd1"
},
{
"name": "Fix commit 55eb912 in elixir-lang/elixir",
"tags": [
"patch"
],
"url": "https://github.com/elixir-lang/elixir/commit/55eb91220a1de14dc5aa27029d1f45b0f5832b2d"
},
{
"name": "Fix commit 9adbe95 in elixir-lang/elixir",
"tags": [
"patch"
],
"url": "https://github.com/elixir-lang/elixir/commit/9adbe955c8664f4be77d879dd38c003dd39fa777"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Insecure temporary file in mix deps.partition lets a local user execute code in another user\u0027s dependency build",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eOn a multi-user host, set \u003ccode\u003eTMPDIR\u003c/code\u003e to a private per-user directory with mode \u003ccode\u003e0700\u003c/code\u003e before running Mix, so that \u003ccode\u003eSystem.tmp_dir/0\u003c/code\u003e resolves to a location other users cannot pre-create or read. Alternatively, keep partitioned compilation disabled by leaving \u003ccode\u003eMIX_OS_DEPS_COMPILE_PARTITION_COUNT\u003c/code\u003e unset or at \u003ccode\u003e1\u003c/code\u003e, which keeps the configuration inside the parent process.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "On a multi-user host, set `TMPDIR` to a private per-user directory with mode `0700` before running Mix, so that `System.tmp_dir/0` resolves to a location other users cannot pre-create or read. Alternatively, keep partitioned compilation disabled by leaving `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` unset or at `1`, which keeps the configuration inside the parent process."
}
],
"value": "On a multi-user host, set TMPDIR to a private per-user directory with mode 0700 before running Mix, so that System.tmp_dir/0 resolves to a location other users cannot pre-create or read. Alternatively, keep partitioned compilation disabled by leaving MIX_OS_DEPS_COMPILE_PARTITION_COUNT unset or at 1, which keeps the configuration inside the parent process."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eOn a multi-user Linux host where \u003ccode\u003eSystem.tmp_dir()\u003c/code\u003e resolves to \u003ccode\u003e/tmp\u003c/code\u003e, the attacker creates \u003ccode\u003e/tmp/mix_deps_partition\u003c/code\u003e as their own directory and watches it for new files.\u003c/li\u003e\n\u003cli\u003eThe victim runs \u003ccode\u003emix deps.compile\u003c/code\u003e (directly, or through \u003ccode\u003emix compile\u003c/code\u003e or \u003ccode\u003emix test\u003c/code\u003e) with \u003ccode\u003eMIX_OS_DEPS_COMPILE_PARTITION_COUNT=2\u003c/code\u003e on a project with at least two non-local dependencies. \u003ccode\u003eFile.mkdir_p/1\u003c/code\u003e succeeds on the existing directory and the config file is written into it.\u003c/li\u003e\n\u003cli\u003eBefore the partition VMs boot and read the file, the attacker decodes it, adds a \u003ccode\u003ecompile:\u003c/code\u003e shell command to every dependency in \u003ccode\u003econfig[:deps]\u003c/code\u003e, re-encodes it and writes it back.\u003c/li\u003e\n\u003cli\u003eEach partition decodes the attacker\u0027s term and \u003ccode\u003edeps.compile\u003c/code\u003e runs the injected command as the victim user.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. On a multi-user Linux host where `System.tmp_dir()` resolves to `/tmp`, the attacker creates `/tmp/mix_deps_partition` as their own directory and watches it for new files.\n2. The victim runs `mix deps.compile` (directly, or through `mix compile` or `mix test`) with `MIX_OS_DEPS_COMPILE_PARTITION_COUNT=2` on a project with at least two non-local dependencies. `File.mkdir_p/1` succeeds on the existing directory and the config file is written into it.\n3. Before the partition VMs boot and read the file, the attacker decodes it, adds a `compile:` shell command to every dependency in `config[:deps]`, re-encodes it and writes it back.\n4. Each partition decodes the attacker\u0027s term and `deps.compile` runs the injected command as the victim user."
}
],
"value": "* On a multi-user Linux host where System.tmp_dir() resolves to /tmp, the attacker creates /tmp/mix_deps_partition as their own directory and watches it for new files.\n* The victim runs mix deps.compile (directly, or through mix compile or mix test) with MIX_OS_DEPS_COMPILE_PARTITION_COUNT=2 on a project with at least two non-local dependencies. File.mkdir_p/1 succeeds on the existing directory and the config file is written into it.\n* Before the partition VMs boot and read the file, the attacker decodes it, adds a compile: shell command to every dependency in config[:deps], re-encodes it and writes it back.\n* Each partition decodes the attacker\u0027s term and deps.compile runs the injected command as the victim user."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Payload written to a shared location\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eMix.Tasks.Deps.Partition.server/4\u003c/code\u003e in \u003ccode\u003elib/mix/lib/mix/tasks/deps.partition.ex\u003c/code\u003e encodes \u003ccode\u003eMix.ProjectStack.peek()\u003c/code\u003e with \u003ccode\u003e:erlang.term_to_binary/1\u003c/code\u003e and Base64, calls \u003ccode\u003eFile.mkdir_p/1\u003c/code\u003e on \u003ccode\u003ePath.join(System.tmp_dir(), \"mix_deps_partition\")\u003c/code\u003e, and writes the payload to a file with a random name inside it. The directory name is fixed, carries no user id, and \u003ccode\u003eFile.mkdir_p/1\u003c/code\u003e succeeds on a directory that already exists, whoever owns it. The file is created with the default mode, so it is readable by other users in a shared temporary directory.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Partition VMs trust the file\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eEach partition is a fresh \u003ccode\u003eelixir\u003c/code\u003e VM started with \u003ccode\u003e--config-file \u0026lt;path\u0026gt;\u003c/code\u003e. \u003ccode\u003eMix.Tasks.Deps.Partition.client/0\u003c/code\u003e reads the file with \u003ccode\u003eFile.read!/1\u003c/code\u003e, decodes it with \u003ccode\u003e:erlang.binary_to_term/1\u003c/code\u003e, and pushes the decoded entry (project name, config and \u003ccode\u003emix.exs\u003c/code\u003e path) with \u003ccode\u003eMix.ProjectStack.push/3\u003c/code\u003e. Nothing verifies that the file still holds what the parent wrote.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Attacker control\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eA local user who created \u003ccode\u003emix_deps_partition\u003c/code\u003e before the victim\u0027s build owns it and may replace or rewrite any file inside, so they swap the payload between the parent\u0027s write and the client\u0027s read. The client then compiles the attacker\u0027s \u003ccode\u003edeps\u003c/code\u003e list, and a dependency\u0027s \u003ccode\u003ecompile:\u003c/code\u003e option is a shell command Mix executes in the victim\u0027s build. On the development branch the payload also carries \u003ccode\u003ecode_path\u003c/code\u003e and \u003ccode\u003ecompiler_options\u003c/code\u003e, which the client applies with \u003ccode\u003e:code.set_path/1\u003c/code\u003e and \u003ccode\u003eCode.compiler_options/1\u003c/code\u003e, so there the attacker also controls the client VM\u0027s code path.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**1. Payload written to a shared location**\n\n`Mix.Tasks.Deps.Partition.server/4` in `lib/mix/lib/mix/tasks/deps.partition.ex` encodes `Mix.ProjectStack.peek()` with `:erlang.term_to_binary/1` and Base64, calls `File.mkdir_p/1` on `Path.join(System.tmp_dir(), \"mix_deps_partition\")`, and writes the payload to a file with a random name inside it. The directory name is fixed, carries no user id, and `File.mkdir_p/1` succeeds on a directory that already exists, whoever owns it. The file is created with the default mode, so it is readable by other users in a shared temporary directory.\n\n**2. Partition VMs trust the file**\n\nEach partition is a fresh `elixir` VM started with `--config-file \u003cpath\u003e`. `Mix.Tasks.Deps.Partition.client/0` reads the file with `File.read!/1`, decodes it with `:erlang.binary_to_term/1`, and pushes the decoded entry (project name, config and `mix.exs` path) with `Mix.ProjectStack.push/3`. Nothing verifies that the file still holds what the parent wrote.\n\n**3. Attacker control**\n\nA local user who created `mix_deps_partition` before the victim\u0027s build owns it and may replace or rewrite any file inside, so they swap the payload between the parent\u0027s write and the client\u0027s read. The client then compiles the attacker\u0027s `deps` list, and a dependency\u0027s `compile:` option is a shell command Mix executes in the victim\u0027s build. On the development branch the payload also carries `code_path` and `compiler_options`, which the client applies with `:code.set_path/1` and `Code.compiler_options/1`, so there the attacker also controls the client VM\u0027s code path."
}
],
"value": "1. Payload written to a shared location\n\nMix.Tasks.Deps.Partition.server/4 in lib/mix/lib/mix/tasks/deps.partition.ex encodes Mix.ProjectStack.peek() with :erlang.term_to_binary/1 and Base64, calls File.mkdir_p/1 on Path.join(System.tmp_dir(), \"mix_deps_partition\"), and writes the payload to a file with a random name inside it. The directory name is fixed, carries no user id, and File.mkdir_p/1 succeeds on a directory that already exists, whoever owns it. The file is created with the default mode, so it is readable by other users in a shared temporary directory.\n\n2. Partition VMs trust the file\n\nEach partition is a fresh elixir VM started with --config-file \u003cpath\u003e. Mix.Tasks.Deps.Partition.client/0 reads the file with File.read!/1, decodes it with :erlang.binary_to_term/1, and pushes the decoded entry (project name, config and mix.exs path) with Mix.ProjectStack.push/3. Nothing verifies that the file still holds what the parent wrote.\n\n3. Attacker control\n\nA local user who created mix_deps_partition before the victim\u0027s build owns it and may replace or rewrite any file inside, so they swap the payload between the parent\u0027s write and the client\u0027s read. The client then compiles the attacker\u0027s deps list, and a dependency\u0027s compile: option is a shell command Mix executes in the victim\u0027s build. On the development branch the payload also carries code_path and compiler_options, which the client applies with :code.set_path/1 and Code.compiler_options/1, so there the attacker also controls the client VM\u0027s code path."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-97209",
"datePublished": "2026-10-30T08:59:17.615Z",
"dateReserved": "2026-10-14T21:45:01.541Z",
"dateUpdated": "2026-10-30T16:50:46.465Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2",
"vulnerability-lookup:meta": {
"epss": {
"cve": "CVE-2026-97209",
"date": "2026-10-30",
"epss": "0.00115",
"percentile": "0.01538"
},
"nvd": {
"cve": {
"affected": [
{
"affectedData": [
{
"cpes": [
"cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Deps.Partition\u0027"
],
"packageName": "mix",
"packageURL": "pkg:otp/mix?repository_url=https:%2F%2Fgithub.com%2Felixir-lang%2Felixir\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Felixir-lang%2Felixir.git",
"product": "elixir",
"programFiles": [
"lib/mix/tasks/deps.partition.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/3"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/4"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:client/0"
}
],
"repo": "https://github.com/elixir-lang/elixir",
"vendor": "elixir-lang",
"versions": [
{
"lessThan": "1.20.5",
"status": "affected",
"version": "1.20.0-rc.4",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Deps.Partition\u0027"
],
"packageName": "elixir-lang/elixir",
"packageURL": "pkg:github/elixir-lang/elixir",
"product": "elixir",
"programFiles": [
"lib/mix/lib/mix/tasks/deps.partition.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/3"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/4"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:client/0"
}
],
"repo": "https://github.com/elixir-lang/elixir",
"vendor": "elixir-lang",
"versions": [
{
"changes": [
{
"at": "55eb91220a1de14dc5aa27029d1f45b0f5832b2d",
"status": "unaffected"
},
{
"at": "9adbe955c8664f4be77d879dd38c003dd39fa777",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "7f3d95b60c28ae3026a5c1bf943166391245bfd1",
"versionType": "git"
}
]
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db"
}
],
"cveTags": [],
"descriptions": [
{
"lang": "en",
"value": "Insecure Temporary File vulnerability in Mix deps.partition in Elixir allows a local user to execute code as another user via a swapped partition config file. Partitioned dependency compilation passes the project configuration to each partition VM through a file in a fixed-name directory under the shared system temporary directory, and the partition trusts what it reads back. Another local user on the same host can exploit this during a mix deps.compile run that uses partitioned compilation.\n\nMix.Tasks.Deps.Partition.server/4 in lib/mix/lib/mix/tasks/deps.partition.ex creates the directory with File.mkdir_p/1, which accepts an existing directory without checking its owner, so a user who created it first can replace the file before client/0 reads it.\n\nThis issue affects mix: from 1.20.0-rc.4 before 1.20.5."
}
],
"id": "CVE-2026-97209",
"lastModified": "2026-10-30T19:39:14.897",
"metrics": {
"cvssMetricV40": [
{
"cvssData": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "LOCAL",
"availabilityRequirement": "NOT_DEFINED",
"baseScore": 5.4,
"baseSeverity": "MEDIUM",
"confidentialityRequirement": "NOT_DEFINED",
"exploitMaturity": "NOT_DEFINED",
"integrityRequirement": "NOT_DEFINED",
"modifiedAttackComplexity": "NOT_DEFINED",
"modifiedAttackRequirements": "NOT_DEFINED",
"modifiedAttackVector": "NOT_DEFINED",
"modifiedPrivilegesRequired": "NOT_DEFINED",
"modifiedSubAvailabilityImpact": "NOT_DEFINED",
"modifiedSubConfidentialityImpact": "NOT_DEFINED",
"modifiedSubIntegrityImpact": "NOT_DEFINED",
"modifiedUserInteraction": "NOT_DEFINED",
"modifiedVulnAvailabilityImpact": "NOT_DEFINED",
"modifiedVulnConfidentialityImpact": "NOT_DEFINED",
"modifiedVulnIntegrityImpact": "NOT_DEFINED",
"privilegesRequired": "LOW",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "PASSIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
],
"ssvcV203": [
{
"source": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"ssvcData": {
"id": "CVE-2026-97209",
"options": [
{
"exploitation": "poc"
},
{
"automatable": "no"
},
{
"technicalImpact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-30T16:50:17.250968Z",
"version": "2.0.3"
}
}
]
},
"published": "2026-10-30T09:17:15.640",
"references": [
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://cna.erlef.org/cves/CVE-2026-97209.html"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/elixir-lang/elixir/commit/55eb91220a1de14dc5aa27029d1f45b0f5832b2d"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/elixir-lang/elixir/commit/7f3d95b60c28ae3026a5c1bf943166391245bfd1"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/elixir-lang/elixir/commit/9adbe955c8664f4be77d879dd38c003dd39fa777"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/elixir-lang/elixir/security/advisories/GHSA-96q6-xhwp-fc8q"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-97209"
},
{
"source": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"url": "https://github.com/elixir-lang/elixir/security/advisories/GHSA-96q6-xhwp-fc8q"
}
],
"sourceIdentifier": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"vulnStatus": "Deferred",
"weaknesses": [
{
"description": [
{
"lang": "en",
"value": "CWE-377"
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
]
}
},
"vulnrichment": {
"containers": {
"adp": [
{
"metrics": [
{
"other": {
"content": {
"id": "CVE-2026-97209",
"options": [
{
"Exploitation": "poc"
},
{
"Automatable": "no"
},
{
"Technical Impact": "total"
}
],
"role": "CISA Coordinator",
"timestamp": "2026-10-30T16:50:17.250968Z",
"version": "2.0.3"
},
"type": "ssvc"
}
}
],
"providerMetadata": {
"dateUpdated": "2026-10-30T16:50:40.071Z",
"orgId": "134c704f-9b21-4f2e-91b3-4a467353bcc0",
"shortName": "CISA-ADP"
},
"references": [
{
"tags": [
"exploit"
],
"url": "https://github.com/elixir-lang/elixir/security/advisories/GHSA-96q6-xhwp-fc8q"
}
],
"title": "CISA ADP Vulnrichment"
}
],
"cna": {
"affected": [
{
"cpes": [
"cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Deps.Partition\u0027"
],
"packageName": "mix",
"packageURL": "pkg:otp/mix?repository_url=https:%2F%2Fgithub.com%2Felixir-lang%2Felixir\u0026vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Felixir-lang%2Felixir.git",
"product": "elixir",
"programFiles": [
"lib/mix/tasks/deps.partition.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/3"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/4"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:client/0"
}
],
"repo": "https://github.com/elixir-lang/elixir",
"vendor": "elixir-lang",
"versions": [
{
"lessThan": "1.20.5",
"status": "affected",
"version": "1.20.0-rc.4",
"versionType": "semver"
}
]
},
{
"collectionURL": "https://github.com",
"cpes": [
"cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*"
],
"defaultStatus": "unaffected",
"modules": [
"\u0027Elixir.Mix.Tasks.Deps.Partition\u0027"
],
"packageName": "elixir-lang/elixir",
"packageURL": "pkg:github/elixir-lang/elixir",
"product": "elixir",
"programFiles": [
"lib/mix/lib/mix/tasks/deps.partition.ex"
],
"programRoutines": [
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/3"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:server/4"
},
{
"name": "\u0027Elixir.Mix.Tasks.Deps.Partition\u0027:client/0"
}
],
"repo": "https://github.com/elixir-lang/elixir",
"vendor": "elixir-lang",
"versions": [
{
"changes": [
{
"at": "55eb91220a1de14dc5aa27029d1f45b0f5832b2d",
"status": "unaffected"
},
{
"at": "9adbe955c8664f4be77d879dd38c003dd39fa777",
"status": "unaffected"
}
],
"lessThan": "*",
"status": "affected",
"version": "7f3d95b60c28ae3026a5c1bf943166391245bfd1",
"versionType": "git"
}
]
}
],
"configurations": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eThe attack requires partitioned dependency compilation, which is off by default and enabled by setting \u003ccode\u003eMIX_OS_DEPS_COMPILE_PARTITION_COUNT\u003c/code\u003e to a value greater than one, a \u003ccode\u003emix deps.compile\u003c/code\u003e run that compiles at least two non-local dependencies, and a system temporary directory shared with other local users, which is the default on Linux when \u003ccode\u003eTMPDIR\u003c/code\u003e is unset. Platforms with per-user temporary directories, such as macOS and Windows by default, are not affected unless \u003ccode\u003eTMPDIR\u003c/code\u003e points at a shared location.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "The attack requires partitioned dependency compilation, which is off by default and enabled by setting `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` to a value greater than one, a `mix deps.compile` run that compiles at least two non-local dependencies, and a system temporary directory shared with other local users, which is the default on Linux when `TMPDIR` is unset. Platforms with per-user temporary directories, such as macOS and Windows by default, are not affected unless `TMPDIR` points at a shared location."
}
],
"value": "The attack requires partitioned dependency compilation, which is off by default and enabled by setting MIX_OS_DEPS_COMPILE_PARTITION_COUNT to a value greater than one, a mix deps.compile run that compiles at least two non-local dependencies, and a system temporary directory shared with other local users, which is the default on Linux when TMPDIR is unset. Platforms with per-user temporary directories, such as macOS and Windows by default, are not affected unless TMPDIR points at a shared location."
}
],
"cpeApplicability": [
{
"nodes": [
{
"cpeMatch": [
{
"criteria": "cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*",
"versionEndExcluding": "1.20.5",
"versionStartIncluding": "1.20.0-rc.4",
"vulnerable": true
}
],
"negate": false,
"operator": "OR"
}
],
"operator": "AND"
}
],
"credits": [
{
"lang": "en",
"type": "finder",
"value": "Quinn Wilton"
},
{
"lang": "en",
"type": "reporter",
"value": "Quinn Wilton"
},
{
"lang": "en",
"type": "remediation developer",
"value": "Jos\u00e9 Valim"
},
{
"lang": "en",
"type": "coordinator",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-15T10:43:20.000Z",
"datePublic": "2026-10-30T08:59:17.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eInsecure Temporary File vulnerability in Mix \u003ccode\u003edeps.partition\u003c/code\u003e in Elixir allows a local user to execute code as another user via a swapped partition config file. Partitioned dependency compilation passes the project configuration to each partition VM through a file in a fixed-name directory under the shared system temporary directory, and the partition trusts what it reads back. Another local user on the same host can exploit this during a \u003ccode\u003emix deps.compile\u003c/code\u003e run that uses partitioned compilation.\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eMix.Tasks.Deps.Partition.server/4\u003c/code\u003e in \u003ccode\u003elib/mix/lib/mix/tasks/deps.partition.ex\u003c/code\u003e creates the directory with \u003ccode\u003eFile.mkdir_p/1\u003c/code\u003e, which accepts an existing directory without checking its owner, so a user who created it first can replace the file before \u003ccode\u003eclient/0\u003c/code\u003e reads it.\u003c/p\u003e\n\u003cp\u003eThis issue affects mix: from 1.20.0-rc.4 before 1.20.5.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Insecure Temporary File vulnerability in Mix `deps.partition` in Elixir allows a local user to execute code as another user via a swapped partition config file. Partitioned dependency compilation passes the project configuration to each partition VM through a file in a fixed-name directory under the shared system temporary directory, and the partition trusts what it reads back. Another local user on the same host can exploit this during a `mix deps.compile` run that uses partitioned compilation.\n\n`Mix.Tasks.Deps.Partition.server/4` in `lib/mix/lib/mix/tasks/deps.partition.ex` creates the directory with `File.mkdir_p/1`, which accepts an existing directory without checking its owner, so a user who created it first can replace the file before `client/0` reads it.\n\nThis issue affects mix: from 1.20.0-rc.4 before 1.20.5."
}
],
"value": "Insecure Temporary File vulnerability in Mix deps.partition in Elixir allows a local user to execute code as another user via a swapped partition config file. Partitioned dependency compilation passes the project configuration to each partition VM through a file in a fixed-name directory under the shared system temporary directory, and the partition trusts what it reads back. Another local user on the same host can exploit this during a mix deps.compile run that uses partitioned compilation.\n\nMix.Tasks.Deps.Partition.server/4 in lib/mix/lib/mix/tasks/deps.partition.ex creates the directory with File.mkdir_p/1, which accepts an existing directory without checking its owner, so a user who created it first can replace the file before client/0 reads it.\n\nThis issue affects mix: from 1.20.0-rc.4 before 1.20.5."
}
],
"impacts": [
{
"capecId": "CAPEC-26",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eA local user on a shared host who creates the partition directory before the victim\u0027s build replaces the serialized configuration and runs arbitrary shell commands as the developer or CI user whose \u003ccode\u003emix deps.compile\u003c/code\u003e uses partitioned compilation.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "A local user on a shared host who creates the partition directory before the victim\u0027s build replaces the serialized configuration and runs arbitrary shell commands as the developer or CI user whose `mix deps.compile` uses partitioned compilation."
}
],
"value": "A local user on a shared host who creates the partition directory before the victim\u0027s build replaces the serialized configuration and runs arbitrary shell commands as the developer or CI user whose mix deps.compile uses partitioned compilation."
}
]
},
{
"capecId": "CAPEC-155",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eEven without pre-creating the directory, the world-readable partition file discloses the project configuration, including \u003ccode\u003eMix.install\u003c/code\u003e compile-time config, to other local users while the build runs.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Even without pre-creating the directory, the world-readable partition file discloses the project configuration, including `Mix.install` compile-time config, to other local users while the build runs."
}
],
"value": "Even without pre-creating the directory, the world-readable partition file discloses the project configuration, including Mix.install compile-time config, to other local users while the build runs."
}
]
}
],
"metrics": [
{
"cvssV4_0": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "LOCAL",
"baseScore": 5.4,
"baseSeverity": "MEDIUM",
"privilegesRequired": "LOW",
"providerUrgency": "NOT_DEFINED",
"subAvailabilityImpact": "NONE",
"subConfidentialityImpact": "NONE",
"subIntegrityImpact": "NONE",
"userInteraction": "PASSIVE",
"valueDensity": "NOT_DEFINED",
"vectorString": "CVSS:4.0/AV:L/AC:L/AT:P/PR:L/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N",
"version": "4.0",
"vulnAvailabilityImpact": "HIGH",
"vulnConfidentialityImpact": "HIGH",
"vulnIntegrityImpact": "HIGH",
"vulnerabilityResponseEffort": "NOT_DEFINED"
},
"format": "CVSS",
"scenarios": [
{
"lang": "en",
"value": "GENERAL"
}
]
}
],
"problemTypes": [
{
"descriptions": [
{
"cweId": "CWE-377",
"description": "CWE-377 Insecure Temporary File",
"lang": "en",
"type": "CWE"
}
]
}
],
"providerMetadata": {
"dateUpdated": "2026-10-30T09:01:05.212Z",
"orgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"shortName": "EEF"
},
"references": [
{
"name": "Insecure temporary file in mix deps.partition lets a local user execute code in another user\u0027s dependency build",
"tags": [
"vendor-advisory",
"related"
],
"url": "https://github.com/elixir-lang/elixir/security/advisories/GHSA-96q6-xhwp-fc8q"
},
{
"name": "EEF CNA record for CVE-2026-97209",
"tags": [
"related"
],
"url": "https://cna.erlef.org/cves/CVE-2026-97209.html"
},
{
"name": "OSV record EEF-CVE-2026-97209",
"tags": [
"related"
],
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-97209"
},
{
"name": "Introducing commit 7f3d95b in elixir-lang/elixir",
"tags": [
"related"
],
"url": "https://github.com/elixir-lang/elixir/commit/7f3d95b60c28ae3026a5c1bf943166391245bfd1"
},
{
"name": "Fix commit 55eb912 in elixir-lang/elixir",
"tags": [
"patch"
],
"url": "https://github.com/elixir-lang/elixir/commit/55eb91220a1de14dc5aa27029d1f45b0f5832b2d"
},
{
"name": "Fix commit 9adbe95 in elixir-lang/elixir",
"tags": [
"patch"
],
"url": "https://github.com/elixir-lang/elixir/commit/9adbe955c8664f4be77d879dd38c003dd39fa777"
}
],
"source": {
"discovery": "EXTERNAL"
},
"title": "Insecure temporary file in mix deps.partition lets a local user execute code in another user\u0027s dependency build",
"workarounds": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eOn a multi-user host, set \u003ccode\u003eTMPDIR\u003c/code\u003e to a private per-user directory with mode \u003ccode\u003e0700\u003c/code\u003e before running Mix, so that \u003ccode\u003eSystem.tmp_dir/0\u003c/code\u003e resolves to a location other users cannot pre-create or read. Alternatively, keep partitioned compilation disabled by leaving \u003ccode\u003eMIX_OS_DEPS_COMPILE_PARTITION_COUNT\u003c/code\u003e unset or at \u003ccode\u003e1\u003c/code\u003e, which keeps the configuration inside the parent process.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "On a multi-user host, set `TMPDIR` to a private per-user directory with mode `0700` before running Mix, so that `System.tmp_dir/0` resolves to a location other users cannot pre-create or read. Alternatively, keep partitioned compilation disabled by leaving `MIX_OS_DEPS_COMPILE_PARTITION_COUNT` unset or at `1`, which keeps the configuration inside the parent process."
}
],
"value": "On a multi-user host, set TMPDIR to a private per-user directory with mode 0700 before running Mix, so that System.tmp_dir/0 resolves to a location other users cannot pre-create or read. Alternatively, keep partitioned compilation disabled by leaving MIX_OS_DEPS_COMPILE_PARTITION_COUNT unset or at 1, which keeps the configuration inside the parent process."
}
],
"x_proofOfConcept": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003col\u003e\n\u003cli\u003eOn a multi-user Linux host where \u003ccode\u003eSystem.tmp_dir()\u003c/code\u003e resolves to \u003ccode\u003e/tmp\u003c/code\u003e, the attacker creates \u003ccode\u003e/tmp/mix_deps_partition\u003c/code\u003e as their own directory and watches it for new files.\u003c/li\u003e\n\u003cli\u003eThe victim runs \u003ccode\u003emix deps.compile\u003c/code\u003e (directly, or through \u003ccode\u003emix compile\u003c/code\u003e or \u003ccode\u003emix test\u003c/code\u003e) with \u003ccode\u003eMIX_OS_DEPS_COMPILE_PARTITION_COUNT=2\u003c/code\u003e on a project with at least two non-local dependencies. \u003ccode\u003eFile.mkdir_p/1\u003c/code\u003e succeeds on the existing directory and the config file is written into it.\u003c/li\u003e\n\u003cli\u003eBefore the partition VMs boot and read the file, the attacker decodes it, adds a \u003ccode\u003ecompile:\u003c/code\u003e shell command to every dependency in \u003ccode\u003econfig[:deps]\u003c/code\u003e, re-encodes it and writes it back.\u003c/li\u003e\n\u003cli\u003eEach partition decodes the attacker\u0027s term and \u003ccode\u003edeps.compile\u003c/code\u003e runs the injected command as the victim user.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. On a multi-user Linux host where `System.tmp_dir()` resolves to `/tmp`, the attacker creates `/tmp/mix_deps_partition` as their own directory and watches it for new files.\n2. The victim runs `mix deps.compile` (directly, or through `mix compile` or `mix test`) with `MIX_OS_DEPS_COMPILE_PARTITION_COUNT=2` on a project with at least two non-local dependencies. `File.mkdir_p/1` succeeds on the existing directory and the config file is written into it.\n3. Before the partition VMs boot and read the file, the attacker decodes it, adds a `compile:` shell command to every dependency in `config[:deps]`, re-encodes it and writes it back.\n4. Each partition decodes the attacker\u0027s term and `deps.compile` runs the injected command as the victim user."
}
],
"value": "* On a multi-user Linux host where System.tmp_dir() resolves to /tmp, the attacker creates /tmp/mix_deps_partition as their own directory and watches it for new files.\n* The victim runs mix deps.compile (directly, or through mix compile or mix test) with MIX_OS_DEPS_COMPILE_PARTITION_COUNT=2 on a project with at least two non-local dependencies. File.mkdir_p/1 succeeds on the existing directory and the config file is written into it.\n* Before the partition VMs boot and read the file, the attacker decodes it, adds a compile: shell command to every dependency in config[:deps], re-encodes it and writes it back.\n* Each partition decodes the attacker\u0027s term and deps.compile runs the injected command as the victim user."
}
],
"x_technicalAnalysis": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003e\u003cstrong\u003e1. Payload written to a shared location\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e\u003ccode\u003eMix.Tasks.Deps.Partition.server/4\u003c/code\u003e in \u003ccode\u003elib/mix/lib/mix/tasks/deps.partition.ex\u003c/code\u003e encodes \u003ccode\u003eMix.ProjectStack.peek()\u003c/code\u003e with \u003ccode\u003e:erlang.term_to_binary/1\u003c/code\u003e and Base64, calls \u003ccode\u003eFile.mkdir_p/1\u003c/code\u003e on \u003ccode\u003ePath.join(System.tmp_dir(), \"mix_deps_partition\")\u003c/code\u003e, and writes the payload to a file with a random name inside it. The directory name is fixed, carries no user id, and \u003ccode\u003eFile.mkdir_p/1\u003c/code\u003e succeeds on a directory that already exists, whoever owns it. The file is created with the default mode, so it is readable by other users in a shared temporary directory.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e2. Partition VMs trust the file\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eEach partition is a fresh \u003ccode\u003eelixir\u003c/code\u003e VM started with \u003ccode\u003e--config-file \u0026lt;path\u0026gt;\u003c/code\u003e. \u003ccode\u003eMix.Tasks.Deps.Partition.client/0\u003c/code\u003e reads the file with \u003ccode\u003eFile.read!/1\u003c/code\u003e, decodes it with \u003ccode\u003e:erlang.binary_to_term/1\u003c/code\u003e, and pushes the decoded entry (project name, config and \u003ccode\u003emix.exs\u003c/code\u003e path) with \u003ccode\u003eMix.ProjectStack.push/3\u003c/code\u003e. Nothing verifies that the file still holds what the parent wrote.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e3. Attacker control\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eA local user who created \u003ccode\u003emix_deps_partition\u003c/code\u003e before the victim\u0027s build owns it and may replace or rewrite any file inside, so they swap the payload between the parent\u0027s write and the client\u0027s read. The client then compiles the attacker\u0027s \u003ccode\u003edeps\u003c/code\u003e list, and a dependency\u0027s \u003ccode\u003ecompile:\u003c/code\u003e option is a shell command Mix executes in the victim\u0027s build. On the development branch the payload also carries \u003ccode\u003ecode_path\u003c/code\u003e and \u003ccode\u003ecompiler_options\u003c/code\u003e, which the client applies with \u003ccode\u003e:code.set_path/1\u003c/code\u003e and \u003ccode\u003eCode.compiler_options/1\u003c/code\u003e, so there the attacker also controls the client VM\u0027s code path.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "**1. Payload written to a shared location**\n\n`Mix.Tasks.Deps.Partition.server/4` in `lib/mix/lib/mix/tasks/deps.partition.ex` encodes `Mix.ProjectStack.peek()` with `:erlang.term_to_binary/1` and Base64, calls `File.mkdir_p/1` on `Path.join(System.tmp_dir(), \"mix_deps_partition\")`, and writes the payload to a file with a random name inside it. The directory name is fixed, carries no user id, and `File.mkdir_p/1` succeeds on a directory that already exists, whoever owns it. The file is created with the default mode, so it is readable by other users in a shared temporary directory.\n\n**2. Partition VMs trust the file**\n\nEach partition is a fresh `elixir` VM started with `--config-file \u003cpath\u003e`. `Mix.Tasks.Deps.Partition.client/0` reads the file with `File.read!/1`, decodes it with `:erlang.binary_to_term/1`, and pushes the decoded entry (project name, config and `mix.exs` path) with `Mix.ProjectStack.push/3`. Nothing verifies that the file still holds what the parent wrote.\n\n**3. Attacker control**\n\nA local user who created `mix_deps_partition` before the victim\u0027s build owns it and may replace or rewrite any file inside, so they swap the payload between the parent\u0027s write and the client\u0027s read. The client then compiles the attacker\u0027s `deps` list, and a dependency\u0027s `compile:` option is a shell command Mix executes in the victim\u0027s build. On the development branch the payload also carries `code_path` and `compiler_options`, which the client applies with `:code.set_path/1` and `Code.compiler_options/1`, so there the attacker also controls the client VM\u0027s code path."
}
],
"value": "1. Payload written to a shared location\n\nMix.Tasks.Deps.Partition.server/4 in lib/mix/lib/mix/tasks/deps.partition.ex encodes Mix.ProjectStack.peek() with :erlang.term_to_binary/1 and Base64, calls File.mkdir_p/1 on Path.join(System.tmp_dir(), \"mix_deps_partition\"), and writes the payload to a file with a random name inside it. The directory name is fixed, carries no user id, and File.mkdir_p/1 succeeds on a directory that already exists, whoever owns it. The file is created with the default mode, so it is readable by other users in a shared temporary directory.\n\n2. Partition VMs trust the file\n\nEach partition is a fresh elixir VM started with --config-file \u003cpath\u003e. Mix.Tasks.Deps.Partition.client/0 reads the file with File.read!/1, decodes it with :erlang.binary_to_term/1, and pushes the decoded entry (project name, config and mix.exs path) with Mix.ProjectStack.push/3. Nothing verifies that the file still holds what the parent wrote.\n\n3. Attacker control\n\nA local user who created mix_deps_partition before the victim\u0027s build owns it and may replace or rewrite any file inside, so they swap the payload between the parent\u0027s write and the client\u0027s read. The client then compiles the attacker\u0027s deps list, and a dependency\u0027s compile: option is a shell command Mix executes in the victim\u0027s build. On the development branch the payload also carries code_path and compiler_options, which the client applies with :code.set_path/1 and Code.compiler_options/1, so there the attacker also controls the client VM\u0027s code path."
}
]
}
},
"cveMetadata": {
"assignerOrgId": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"assignerShortName": "EEF",
"cveId": "CVE-2026-97209",
"datePublished": "2026-10-30T08:59:17.615Z",
"dateReserved": "2026-10-14T21:45:01.541Z",
"dateUpdated": "2026-10-30T16:50:46.465Z",
"state": "PUBLISHED"
},
"dataType": "CVE_RECORD",
"dataVersion": "5.2"
}
}
}
Loading…
Loading…
Drag nodes to arrange them. Double-click a related vulnerability to open it, or a source to visit its link. The Physics button on the left tunes the layout and the simulation; the legend at the bottom left hides or shows nodes by kind and by sighting type.
Experimental. This forecast is provided for visualization only and may change without notice. Do not use it for operational decisions.
Forecast uses a logistic model when the trend is rising, or an exponential decay model when the trend is falling. Fitted via linearized least squares.
Sightings
| Author | Source | Type | Date | Other |
|---|
Nomenclature
- Seen: The vulnerability was mentioned, discussed, or observed by the user.
- Confirmed: The vulnerability has been validated from an analyst's perspective.
- Published Proof of Concept: A public proof of concept is available for this vulnerability.
- Exploited: The vulnerability was observed as exploited by the user who reported the sighting.
- Patched: The vulnerability was observed as successfully patched by the user who reported the sighting.
- Not exploited: The vulnerability was not observed as exploited by the user who reported the sighting.
- Not confirmed: The user expressed doubt about the validity of the vulnerability.
- Not patched: The vulnerability was not observed as successfully patched by the user who reported the sighting.
Vulnerability timeline
Sightings and recorded dates, grouped by day · UTC
Loading timeline…
Loading…
Loading…
The MITRE ATT&CK techniques below are AI-generated suggestions, inferred from the description of the
vulnerability by the CIRCL/vulnerability-attack-technique-classification-roberta-base
model, served locally by ML-Gateway.
They have not been verified by an analyst and are provided for guidance only.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
The approach is described in our paper Mapping CVEs to MITRE ATT&CK Techniques: A Curated Gold-Set Classifier and the Limits of LLM-Assisted Label Expansion.
Browse all ATT&CK techniques and the vulnerabilities related to each.
Loading…
Related by attack behaviour
Vulnerabilities whose description is nearest to this one in the vector space of the CIRCL/vulnerability-attack-technique-biencoder model. This is a similarity search over the bi-encoder space (plain cosine), not a classification, and it has no measured accuracy.