{"id":"EEF-CVE-2026-103060","summary":"Path traversal in rebar3 via unvalidated dependency app names","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 =\u003e \u003c\u003c\"evildep\"\u003e\u003e, requirement =\u003e \u003c\u003c\"1.0.0\"\u003e\u003e, optional =\u003e false, app =\u003e \u003c\u003c\"../../../../CANARY_OUTSIDE\"\u003e\u003e}`, 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 `-`.","aliases":["CVE-2026-103060","GHSA-6ggr-r7mh-2mv8"],"modified":"2026-10-12T15:56:09.609223277Z","published":"2026-10-12T15:43:57.636Z","database_specific":{"cwe_ids":["CWE-22"],"capec_ids":["CAPEC-126"],"cpe_ids":["cpe:2.3:a:erlang:rebar3:*:*:*:*:*:*:*:*"]},"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"}],"affected":[{"ranges":[{"type":"GIT","repo":"https://github.com/erlang/rebar3","events":[{"introduced":"56b7d88975aa8da6446857cfd92de0825024bf63"},{"fixed":"e35282b26e499b1d9e8f30bba9dccd71cd8b02ba"}]}],"versions":["3.27.1","3.27.0","3.26.0","3.25.1","3.25.0","3.24.0","3.23.0","3.22.1","3.22.0","3.21.0","3.20.0","3.19.0","3.18.0","3.17.0","3.16.1","3.15.1","3.16.0","3.15.0","3.14.4","3.14.2","3.14.1","3.14.0","3.14.0-rc2","3.14.0-rc1","3.13.0","3.12.0","3.11.1","3.11.0","3.9.1","3.9.0","3.8.0","3.7.5","3.7.4","3.7.3","3.7.2","3.7.1","3.7.0","3.7.0-rc2","3.7.0-rc1"],"database_specific":{"source":"https://cna.erlef.org/osv/EEF-CVE-2026-103060.json"}}],"schema_version":"1.9.0","severity":[{"type":"CVSS_V4","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"}],"credits":[{"name":"Peter Ullrich","type":"FINDER"},{"name":"Eric Meadows-Jönsson","type":"REMEDIATION_DEVELOPER"},{"name":"Jonatan Männchen / EEF","type":"ANALYST"}]}