{"affected":[{"ranges":[{"events":[{"introduced":"56b7d88975aa8da6446857cfd92de0825024bf63"},{"fixed":"e35282b26e499b1d9e8f30bba9dccd71cd8b02ba"}],"repo":"https://github.com/erlang/rebar3","type":"GIT"}]}],"aliases":["GHSA-6ggr-r7mh-2mv8","CVE-2026-103060"],"credits":[{"name":"Peter Ullrich","type":"FINDER"},{"name":"Eric Meadows-Jönsson","type":"REMEDIATION_DEVELOPER"},{"name":"Jonatan Männchen / EEF","type":"ANALYST"}],"database_specific":{"capec_ids":["CAPEC-126"],"cpe_ids":["cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"],"cwe_ids":["CWE-22"]},"details":"## Summary\n\nImproper 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.\n\n## Details\n\n1. 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.\n\n## Proof of concept\n\nReproduced 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.\n\n## Impact\n\nA 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.\n\n## Configurations\n\nAffects 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.\n\n## Solutions\n\nUpgrade 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 `-`.","id":"EEF-CVE-2026-103060","modified":"2026-10-12T15:43:57.991526Z","published":"2026-10-12T15:43:57.636Z","references":[{"type":"ADVISORY","url":"https://github.com/erlang/rebar3/security/advisories/GHSA-6ggr-r7mh-2mv8"},{"type":"WEB","url":"https://cna.erlef.org/cves/CVE-2026-103060.html"},{"type":"WEB","url":"https://github.com/erlang/rebar3/commit/56b7d88975aa8da6446857cfd92de0825024bf63"},{"type":"FIX","url":"https://github.com/erlang/rebar3/commit/e35282b26e499b1d9e8f30bba9dccd71cd8b02ba"}],"related":[],"schema_version":"1.7.3","severity":[{"score":"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","type":"CVSS_V4"}],"summary":"Path traversal in rebar3 via unvalidated dependency app names","upstream":[]}