{"containers":{"cna":{"affected":[{"cpes":["cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"],"defaultStatus":"unaffected","modules":["rebar_packages","rebar_app_utils","rebar_fetch"],"packageName":"rebar3","packageURL":"pkg:otp/rebar3?repository_url=https:%2F%2Fgithub.com%2Ferlang%2Frebar3&vcs_url=git%2Bhttps:%2F%2Fgithub.com%2Ferlang%2Frebar3.git","product":"rebar3","programFiles":["apps/rebar/src/rebar_packages.erl","apps/rebar/src/rebar_app_utils.erl","apps/rebar/src/rebar_fetch.erl"],"programRoutines":[{"name":"rebar_packages:parse_deps/1"},{"name":"rebar_app_utils:dep_to_app/7"},{"name":"rebar_fetch:download_source/2"},{"name":"rebar_fetch:download_source_online/2"}],"repo":"https://github.com/erlang/rebar3","vendor":"erlang","versions":[{"lessThan":"3.27.2","status":"affected","version":"3.7.0-rc1","versionType":"semver"}]},{"collectionURL":"https://github.com","cpes":["cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"],"defaultStatus":"unaffected","modules":["rebar_packages","rebar_app_utils","rebar_fetch"],"packageName":"erlang/rebar3","packageURL":"pkg:github/erlang/rebar3","product":"rebar3","programFiles":["apps/rebar/src/rebar_packages.erl","apps/rebar/src/rebar_app_utils.erl","apps/rebar/src/rebar_fetch.erl"],"programRoutines":[{"name":"rebar_packages:parse_deps/1"},{"name":"rebar_app_utils:dep_to_app/7"},{"name":"rebar_fetch:download_source/2"},{"name":"rebar_fetch:download_source_online/2"}],"repo":"https://github.com/erlang/rebar3","vendor":"erlang","versions":[{"lessThan":"e35282b26e499b1d9e8f30bba9dccd71cd8b02ba","status":"affected","version":"56b7d88975aa8da6446857cfd92de0825024bf63","versionType":"git"}]}],"configurations":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>Affects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata gives a dependency a crafted <code>app</code> name, when dependencies are fetched with <code>rebar3 get-deps</code> or another command that fetches dependencies. hex.pm rejects such names when a release is published since 2026-10-12, and every requirement <code>app</code> name in hex.pm's public registry on 2026-10-06 was a letter followed by letters, digits, <code>_</code> and <code>-</code>. Packages on other Hex repositories are affected when the repository operator signs such metadata.</p>"},{"base64":false,"type":"text/markdown","value":"Affects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata gives a dependency a crafted `app` name, when dependencies are fetched with `rebar3 get-deps` or another command that fetches dependencies. hex.pm rejects such names when a release is published since 2026-10-12, and every requirement `app` name in hex.pm's public registry on 2026-10-06 was a letter followed by letters, digits, `_` and `-`. Packages on other Hex repositories are affected when the repository operator signs such metadata."}],"value":"Affects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata gives a dependency a crafted app name, when dependencies are fetched with rebar3 get-deps or another command that fetches dependencies. hex.pm rejects such names when a release is published since 2026-10-12, and every requirement app name in hex.pm's public registry on 2026-10-06 was a letter followed by letters, digits, _ and -. Packages on other Hex repositories are affected when the repository operator signs such metadata."}],"cpeApplicability":[{"nodes":[{"cpeMatch":[{"criteria":"cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*","versionEndExcluding":"3.27.2","versionStartIncluding":"3.7.0-rc1","vulnerable":true}],"negate":false,"operator":"OR"}],"operator":"AND"}],"credits":[{"lang":"en","type":"finder","value":"Peter Ullrich"},{"lang":"en","type":"remediation developer","value":"Eric Meadows-Jönsson"},{"lang":"en","type":"analyst","value":"Jonatan Männchen / EEF"}],"dateAssigned":"2026-10-07T22:04:17.000Z","descriptions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project's <code>_build</code> directory and extract package files into it when a developer fetches dependencies.</p>\n<p>A package release's dependencies in a Hex registry carry an <code>app</code> field that names the dependency's OTP application. rebar3 uses this field as the dependency's name without validating it (<code>rebar_packages:parse_deps/1</code>) and builds the dependency's directory from that name (<code>rebar_app_utils:dep_to_app/7</code>). <code>rebar_fetch:download_source_online/2</code> deletes that directory with <code>rm -rf</code> and moves the fetched package into it. hex.pm didn't restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as <code>../../../../target</code>. When a project depends on such a release directly or transitively, <code>rebar3 get-deps</code> deletes the directory the name resolves to and writes the dependency's files there. This happens before the dependency's own <code>rebar.config.script</code> is evaluated, and <code>get-deps</code> compiles nothing.</p>\n<p>This issue affects rebar3: from 3.7.0-rc1 before 3.27.2.</p>"},{"base64":false,"type":"text/markdown","value":"Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project's `_build` directory and extract package files into it when a developer fetches dependencies.\n\nA package release's dependencies in a Hex registry carry an `app` field that names the dependency's OTP application. rebar3 uses this field as the dependency's name without validating it (`rebar_packages:parse_deps/1`) and builds the dependency's directory from that name (`rebar_app_utils:dep_to_app/7`). `rebar_fetch:download_source_online/2` deletes that directory with `rm -rf` and moves the fetched package into it. hex.pm didn't restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as `../../../../target`. When a project depends on such a release directly or transitively, `rebar3 get-deps` deletes the directory the name resolves to and writes the dependency's files there. This happens before the dependency's own `rebar.config.script` is evaluated, and `get-deps` compiles nothing.\n\nThis issue affects rebar3: from 3.7.0-rc1 before 3.27.2."}],"value":"Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal') vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project's _build directory and extract package files into it when a developer fetches dependencies.\n\nA package release's dependencies in a Hex registry carry an app field that names the dependency's OTP application. rebar3 uses this field as the dependency's name without validating it (rebar_packages:parse_deps/1) and builds the dependency's directory from that name (rebar_app_utils:dep_to_app/7). rebar_fetch:download_source_online/2 deletes that directory with rm -rf and moves the fetched package into it. hex.pm didn't restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as ../../../../target. When a project depends on such a release directly or transitively, rebar3 get-deps deletes the directory the name resolves to and writes the dependency's files there. This happens before the dependency's own rebar.config.script is evaluated, and get-deps compiles nothing.\n\nThis issue affects rebar3: from 3.7.0-rc1 before 3.27.2."}],"impacts":[{"capecId":"CAPEC-126","descriptions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>A publisher of a package in a project's dependency tree can make <code>rebar3 get-deps</code> delete a directory outside the project's <code>_build</code> directory that the developer's account can write, such as the project directory or its parent, and replace its contents with a dependency package's files. Replacing a directory whose contents are later run or loaded, such as a project directory whose <code>rebar.config.script</code> the next rebar3 command there evaluates, can lead to code execution. It works when the package is a transitive dependency and needs no compilation. Developers, CI jobs and dependency update bots that fetch dependencies with rebar3 are affected.</p>"},{"base64":false,"type":"text/markdown","value":"A publisher of a package in a project's dependency tree can make `rebar3 get-deps` delete a directory outside the project's `_build` directory that the developer's account can write, such as the project directory or its parent, and replace its contents with a dependency package's files. Replacing a directory whose contents are later run or loaded, such as a project directory whose `rebar.config.script` the next rebar3 command there evaluates, can lead to code execution. It works when the package is a transitive dependency and needs no compilation. Developers, CI jobs and dependency update bots that fetch dependencies with rebar3 are affected."}],"value":"A publisher of a package in a project's dependency tree can make rebar3 get-deps delete a directory outside the project's _build directory that the developer's account can write, such as the project directory or its parent, and replace its contents with a dependency package's files. Replacing a directory whose contents are later run or loaded, such as a project directory whose rebar.config.script the next rebar3 command there evaluates, can lead to code execution. It works when the package is a transitive dependency and needs no compilation. Developers, CI jobs and dependency update bots that fetch dependencies with rebar3 are affected."}]}],"metrics":[{"cvssV4_0":{"Automatable":"NOT_DEFINED","Recovery":"NOT_DEFINED","Safety":"NOT_DEFINED","attackComplexity":"LOW","attackRequirements":"PRESENT","attackVector":"NETWORK","baseScore":7.5,"baseSeverity":"HIGH","privilegesRequired":"NONE","providerUrgency":"NOT_DEFINED","subAvailabilityImpact":"NONE","subConfidentialityImpact":"NONE","subIntegrityImpact":"NONE","userInteraction":"ACTIVE","valueDensity":"NOT_DEFINED","vectorString":"CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:A/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-22","description":"CWE-22 Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')","lang":"en","type":"CWE"}]}],"providerMetadata":{"dateUpdated":"2026-10-12T15:43:57.636Z","orgId":"6b3ad84c-e1a6-4bf7-a703-f496b71e49db","shortName":"EEF"},"references":[{"name":"GitHub Advisory","tags":["related","vendor-advisory"],"url":"https://github.com/erlang/rebar3/security/advisories/GHSA-6ggr-r7mh-2mv8"},{"name":"EEF CNA record for CVE-2026-103060","tags":["related"],"url":"https://cna.erlef.org/cves/CVE-2026-103060.html"},{"name":"OSV record EEF-CVE-2026-103060","tags":["related"],"url":"https://osv.dev/vulnerability/EEF-CVE-2026-103060"},{"name":"Introducing commit 56b7d88 in erlang/rebar3","tags":["related"],"url":"https://github.com/erlang/rebar3/commit/56b7d88975aa8da6446857cfd92de0825024bf63"},{"name":"Fix commit e35282b in erlang/rebar3","tags":["patch"],"url":"https://github.com/erlang/rebar3/commit/e35282b26e499b1d9e8f30bba9dccd71cd8b02ba"}],"solutions":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>Upgrade rebar3 to 3.27.2 or later, for example with <code>rebar3 local upgrade</code>. rebar3 then fails with an error when a dependency's name, including the <code>app</code> name a package release gives a dependency, isn't a letter followed by letters, digits, <code>_</code> and <code>-</code>.</p>"},{"base64":false,"type":"text/markdown","value":"Upgrade rebar3 to 3.27.2 or later, for example with `rebar3 local upgrade`. rebar3 then fails with an error when a dependency's name, including the `app` name a package release gives a dependency, isn't a letter followed by letters, digits, `_` and `-`."}],"value":"Upgrade rebar3 to 3.27.2 or later, for example with rebar3 local upgrade. rebar3 then fails with an error when a dependency's name, including the app name a package release gives a dependency, isn't a letter followed by letters, digits, _ and -."}],"source":{"discovery":"UNKNOWN"},"title":"Path traversal in rebar3 via unvalidated dependency app names","x_proofOfConcept":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<p>Reproduced with rebar3 3.27.0 on Erlang/OTP 29 against a local Hex repository, with origin and signature verification left on:</p>\n<ol>\n<li>Build a signed local Hex repository serving <code>parent</code> 1.0.0, whose dependency is <code>#{package =&gt; &lt;&lt;\"evildep\"&gt;&gt;, requirement =&gt; &lt;&lt;\"1.0.0\"&gt;&gt;, optional =&gt; false, app =&gt; &lt;&lt;\"../../../../CANARY_OUTSIDE\"&gt;&gt;}</code>, and <code>evildep</code> 1.0.0.</li>\n<li>Create a project with <code>{deps, [{parent, \"1.0.0\"}]}</code> that uses the local repository, and a file <code>CANARY_OUTSIDE/precious.txt</code> one level above the project root.</li>\n<li>Run <code>rebar3 get-deps</code>. It prints <code>Fetching parent v1.0.0</code> and then <code>Fetching ../../../../CANARY_OUTSIDE v1.0.0</code>.</li>\n<li><code>CANARY_OUTSIDE/precious.txt</code> is gone and <code>CANARY_OUTSIDE/</code> holds <code>evildep</code>'s extracted files (<code>hex_metadata.config</code>, <code>src/evildep.app.src</code>). No scripts, plugins or hooks are in either package.</li>\n</ol>"},{"base64":false,"type":"text/markdown","value":"Reproduced with rebar3 3.27.0 on Erlang/OTP 29 against a local Hex repository, with origin and signature verification left on:\n\n1. Build a signed local Hex repository serving `parent` 1.0.0, whose dependency is `#{package => <<\"evildep\">>, requirement => <<\"1.0.0\">>, optional => false, app => <<\"../../../../CANARY_OUTSIDE\">>}`, and `evildep` 1.0.0.\n2. Create a project with `{deps, [{parent, \"1.0.0\"}]}` that uses the local repository, and a file `CANARY_OUTSIDE/precious.txt` one level above the project root.\n3. Run `rebar3 get-deps`. It prints `Fetching parent v1.0.0` and then `Fetching ../../../../CANARY_OUTSIDE v1.0.0`.\n4. `CANARY_OUTSIDE/precious.txt` is gone and `CANARY_OUTSIDE/` holds `evildep`'s extracted files (`hex_metadata.config`, `src/evildep.app.src`). No scripts, plugins or hooks are in either package."}],"value":"Reproduced with rebar3 3.27.0 on Erlang/OTP 29 against a local Hex repository, with origin and signature verification left on:\n\n* Build a signed local Hex repository serving parent 1.0.0, whose dependency is #{package => <<\"evildep\">>, requirement => <<\"1.0.0\">>, optional => false, app => <<\"../../../../CANARY_OUTSIDE\">>}, and evildep 1.0.0.\n* Create a project with {deps, [{parent, \"1.0.0\"}]} that uses the local repository, and a file CANARY_OUTSIDE/precious.txt one level above the project root.\n* Run rebar3 get-deps. It prints Fetching parent v1.0.0 and then Fetching ../../../../CANARY_OUTSIDE v1.0.0.\n* CANARY_OUTSIDE/precious.txt is gone and CANARY_OUTSIDE/ holds evildep's extracted files (hex_metadata.config, src/evildep.app.src). No scripts, plugins or hooks are in either package."}],"x_technicalAnalysis":[{"lang":"en","supportingMedia":[{"base64":false,"type":"text/html","value":"<ol>\n<li>hex.pm's registry record lists each release's dependencies as maps with <code>package</code>, <code>requirement</code>, <code>optional</code>, <code>app</code> and <code>repository</code>. The <code>app</code> value comes from the publisher's package metadata and hex.pm doesn't restrict it to a directory name. The record is signed with the repository's key after the publisher has chosen the value, so the signature doesn't constrain it.</li>\n<li><code>r3_hex_registry:decode_package/3</code> decodes each dependency, including <code>app</code>.</li>\n<li><code>rebar_packages:parse_deps/1</code> sets the dependency's name to the <code>app</code> value, falling back to the package name: <code>{maps:get(app, D, Name), {pkg, Name, Constraint, undefined, undefined}}</code>. The name is stored in <code>#package.dependencies</code> and passed through <code>rebar_app_utils:update_source/3</code> and <code>rebar_prv_install_deps:handle_dep/6</code>.</li>\n<li><code>rebar_app_utils:dep_to_app/7</code> builds the fetch directory with <code>filename:join([DepsDir, Name])</code> and no check.</li>\n<li><code>rebar_fetch:download_source_online/2</code> runs <code>rebar_file_utils:rm_rf(filename:absname(FetchDir))</code> and then <code>rebar_file_utils:mv(TmpDir, filename:absname(FetchDir))</code>. <code>filename:absname/1</code> doesn't collapse <code>..</code> segments, and on Unix <code>rm_rf/1</code> runs <code>rm -rf</code> through the shell with <code>rebar_utils:escape_chars/1</code>, which doesn't escape <code>/</code> or <code>.</code>, so the <code>..</code> segments resolve outside the deps directory.</li>\n<li>The delete and overwrite happen for the dependency named by <code>app</code> before its own <code>rebar.config.script</code> is consulted (later in <code>rebar_fetch:download_source/2</code>), and <code>get-deps</code> compiles nothing. rebar3 already runs publisher code during <code>get-deps</code> by evaluating a fetched package's <code>rebar.config.script</code> and installing a dependency's plugins; the <code>app</code> name adds a delete and overwrite outside <code>_build</code> for a dependency named by its parent package.</li>\n</ol>"},{"base64":false,"type":"text/markdown","value":"1. hex.pm's registry record lists each release's dependencies as maps with `package`, `requirement`, `optional`, `app` and `repository`. The `app` value comes from the publisher's package metadata and hex.pm doesn't restrict it to a directory name. The record is signed with the repository's key after the publisher has chosen the value, so the signature doesn't constrain it.\n2. `r3_hex_registry:decode_package/3` decodes each dependency, including `app`.\n3. `rebar_packages:parse_deps/1` sets the dependency's name to the `app` value, falling back to the package name: `{maps:get(app, D, Name), {pkg, Name, Constraint, undefined, undefined}}`. The name is stored in `#package.dependencies` and passed through `rebar_app_utils:update_source/3` and `rebar_prv_install_deps:handle_dep/6`.\n4. `rebar_app_utils:dep_to_app/7` builds the fetch directory with `filename:join([DepsDir, Name])` and no check.\n5. `rebar_fetch:download_source_online/2` runs `rebar_file_utils:rm_rf(filename:absname(FetchDir))` and then `rebar_file_utils:mv(TmpDir, filename:absname(FetchDir))`. `filename:absname/1` doesn't collapse `..` segments, and on Unix `rm_rf/1` runs `rm -rf` through the shell with `rebar_utils:escape_chars/1`, which doesn't escape `/` or `.`, so the `..` segments resolve outside the deps directory.\n6. The delete and overwrite happen for the dependency named by `app` before its own `rebar.config.script` is consulted (later in `rebar_fetch:download_source/2`), and `get-deps` compiles nothing. rebar3 already runs publisher code during `get-deps` by evaluating a fetched package's `rebar.config.script` and installing a dependency's plugins; the `app` name adds a delete and overwrite outside `_build` for a dependency named by its parent package."}],"value":"* hex.pm's registry record lists each release's dependencies as maps with package, requirement, optional, app and repository. The app value comes from the publisher's package metadata and hex.pm doesn't restrict it to a directory name. The record is signed with the repository's key after the publisher has chosen the value, so the signature doesn't constrain it.\n* r3_hex_registry:decode_package/3 decodes each dependency, including app.\n* rebar_packages:parse_deps/1 sets the dependency's name to the app value, falling back to the package name: {maps:get(app, D, Name), {pkg, Name, Constraint, undefined, undefined}}. The name is stored in #package.dependencies and passed through rebar_app_utils:update_source/3 and rebar_prv_install_deps:handle_dep/6.\n* rebar_app_utils:dep_to_app/7 builds the fetch directory with filename:join([DepsDir, Name]) and no check.\n* rebar_fetch:download_source_online/2 runs rebar_file_utils:rm_rf(filename:absname(FetchDir)) and then rebar_file_utils:mv(TmpDir, filename:absname(FetchDir)). filename:absname/1 doesn't collapse .. segments, and on Unix rm_rf/1 runs rm -rf through the shell with rebar_utils:escape_chars/1, which doesn't escape / or ., so the .. segments resolve outside the deps directory.\n* The delete and overwrite happen for the dependency named by app before its own rebar.config.script is consulted (later in rebar_fetch:download_source/2), and get-deps compiles nothing. rebar3 already runs publisher code during get-deps by evaluating a fetched package's rebar.config.script and installing a dependency's plugins; the app name adds a delete and overwrite outside _build for a dependency named by its parent package."}]}},"cveMetadata":{"assignerOrgId":"6b3ad84c-e1a6-4bf7-a703-f496b71e49db","assignerShortName":"EEF","cveId":"CVE-2026-103060","datePublished":"2026-10-12T15:43:57.636Z","dateReserved":"2026-10-06T08:45:01.521Z","dateUpdated":"2026-10-12T15:43:57.636Z","state":"PUBLISHED"},"dataType":"CVE_RECORD","dataVersion":"5.2"}