CVE-2026-103060 (GCVE-0-2026-103060)
Vulnerability from cvelistv5 – Published: 2026-10-12 15:43 – Updated: 2026-10-12 15:43
VLAI
EPSS
VEX
Title
Path traversal in rebar3 via unvalidated dependency app names
Summary
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.
A 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.
This issue affects rebar3: from 3.7.0-rc1 before 3.27.2.
Severity
CWE
- CWE-22 - Improper Limitation of a Pathname to a Restricted Directory ('Path Traversal')
Assigner
References
5 references
| URL | Tags |
|---|---|
| https://github.com/erlang/rebar3/security/advisor… | relatedvendor-advisory |
| https://cna.erlef.org/cves/CVE-2026-103060.html | related |
| https://osv.dev/vulnerability/EEF-CVE-2026-103060 | related |
| https://github.com/erlang/rebar3/commit/56b7d8897… | related |
| https://github.com/erlang/rebar3/commit/e35282b26… | patch |
Impacted products
{
"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\u0026vcs_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": "\u003cp\u003eAffects projects whose dependency tree, directly or transitively, contains a package release whose registry metadata gives a dependency a crafted \u003ccode\u003eapp\u003c/code\u003e name, when dependencies are fetched with \u003ccode\u003erebar3 get-deps\u003c/code\u003e or another command that fetches dependencies. hex.pm rejects such names when a release is published since 2026-10-12, and every requirement \u003ccode\u003eapp\u003c/code\u003e name in hex.pm\u0027s public registry on 2026-10-06 was a letter followed by letters, digits, \u003ccode\u003e_\u003c/code\u003e and \u003ccode\u003e-\u003c/code\u003e. Packages on other Hex repositories are affected when the repository operator signs such metadata.\u003c/p\u003e"
},
{
"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\u0027s 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\u0027s 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\u00f6nsson"
},
{
"lang": "en",
"type": "analyst",
"value": "Jonatan M\u00e4nnchen / EEF"
}
],
"dateAssigned": "2026-10-07T22:04:17.000Z",
"descriptions": [
{
"lang": "en",
"supportingMedia": [
{
"base64": false,
"type": "text/html",
"value": "\u003cp\u003eImproper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project\u0027s \u003ccode\u003e_build\u003c/code\u003e directory and extract package files into it when a developer fetches dependencies.\u003c/p\u003e\n\u003cp\u003eA package release\u0027s dependencies in a Hex registry carry an \u003ccode\u003eapp\u003c/code\u003e field that names the dependency\u0027s OTP application. rebar3 uses this field as the dependency\u0027s name without validating it (\u003ccode\u003erebar_packages:parse_deps/1\u003c/code\u003e) and builds the dependency\u0027s directory from that name (\u003ccode\u003erebar_app_utils:dep_to_app/7\u003c/code\u003e). \u003ccode\u003erebar_fetch:download_source_online/2\u003c/code\u003e deletes that directory with \u003ccode\u003erm -rf\u003c/code\u003e and moves the fetched package into it. hex.pm didn\u0027t restrict the field to a directory name before 2026-10-12, so a publisher could set it to a value such as \u003ccode\u003e../../../../target\u003c/code\u003e. When a project depends on such a release directly or transitively, \u003ccode\u003erebar3 get-deps\u003c/code\u003e deletes the directory the name resolves to and writes the dependency\u0027s files there. This happens before the dependency\u0027s own \u003ccode\u003erebar.config.script\u003c/code\u003e is evaluated, and \u003ccode\u003eget-deps\u003c/code\u003e compiles nothing.\u003c/p\u003e\n\u003cp\u003eThis issue affects rebar3: from 3.7.0-rc1 before 3.27.2.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project\u0027s `_build` directory and extract package files into it when a developer fetches dependencies.\n\nA package release\u0027s dependencies in a Hex registry carry an `app` field that names the dependency\u0027s OTP application. rebar3 uses this field as the dependency\u0027s name without validating it (`rebar_packages:parse_deps/1`) and builds the dependency\u0027s 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\u0027t 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\u0027s files there. This happens before the dependency\u0027s 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 (\u0027Path Traversal\u0027) vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project\u0027s _build directory and extract package files into it when a developer fetches dependencies.\n\nA package release\u0027s dependencies in a Hex registry carry an app field that names the dependency\u0027s OTP application. rebar3 uses this field as the dependency\u0027s name without validating it (rebar_packages:parse_deps/1) and builds the dependency\u0027s 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\u0027t 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\u0027s files there. This happens before the dependency\u0027s 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": "\u003cp\u003eA publisher of a package in a project\u0027s dependency tree can make \u003ccode\u003erebar3 get-deps\u003c/code\u003e delete a directory outside the project\u0027s \u003ccode\u003e_build\u003c/code\u003e directory that the developer\u0027s account can write, such as the project directory or its parent, and replace its contents with a dependency package\u0027s files. Replacing a directory whose contents are later run or loaded, such as a project directory whose \u003ccode\u003erebar.config.script\u003c/code\u003e 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.\u003c/p\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "A publisher of a package in a project\u0027s dependency tree can make `rebar3 get-deps` delete a directory outside the project\u0027s `_build` directory that the developer\u0027s account can write, such as the project directory or its parent, and replace its contents with a dependency package\u0027s 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\u0027s dependency tree can make rebar3 get-deps delete a directory outside the project\u0027s _build directory that the developer\u0027s account can write, such as the project directory or its parent, and replace its contents with a dependency package\u0027s 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 (\u0027Path Traversal\u0027)",
"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": "\u003cp\u003eUpgrade rebar3 to 3.27.2 or later, for example with \u003ccode\u003erebar3 local upgrade\u003c/code\u003e. rebar3 then fails with an error when a dependency\u0027s name, including the \u003ccode\u003eapp\u003c/code\u003e name a package release gives a dependency, isn\u0027t a letter followed by letters, digits, \u003ccode\u003e_\u003c/code\u003e and \u003ccode\u003e-\u003c/code\u003e.\u003c/p\u003e"
},
{
"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\u0027s name, including the `app` name a package release gives a dependency, isn\u0027t 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\u0027s name, including the app name a package release gives a dependency, isn\u0027t 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": "\u003cp\u003eReproduced with rebar3 3.27.0 on Erlang/OTP 29 against a local Hex repository, with origin and signature verification left on:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eBuild a signed local Hex repository serving \u003ccode\u003eparent\u003c/code\u003e 1.0.0, whose dependency is \u003ccode\u003e#{package =\u0026gt; \u0026lt;\u0026lt;\"evildep\"\u0026gt;\u0026gt;, requirement =\u0026gt; \u0026lt;\u0026lt;\"1.0.0\"\u0026gt;\u0026gt;, optional =\u0026gt; false, app =\u0026gt; \u0026lt;\u0026lt;\"../../../../CANARY_OUTSIDE\"\u0026gt;\u0026gt;}\u003c/code\u003e, and \u003ccode\u003eevildep\u003c/code\u003e 1.0.0.\u003c/li\u003e\n\u003cli\u003eCreate a project with \u003ccode\u003e{deps, [{parent, \"1.0.0\"}]}\u003c/code\u003e that uses the local repository, and a file \u003ccode\u003eCANARY_OUTSIDE/precious.txt\u003c/code\u003e one level above the project root.\u003c/li\u003e\n\u003cli\u003eRun \u003ccode\u003erebar3 get-deps\u003c/code\u003e. It prints \u003ccode\u003eFetching parent v1.0.0\u003c/code\u003e and then \u003ccode\u003eFetching ../../../../CANARY_OUTSIDE v1.0.0\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003eCANARY_OUTSIDE/precious.txt\u003c/code\u003e is gone and \u003ccode\u003eCANARY_OUTSIDE/\u003c/code\u003e holds \u003ccode\u003eevildep\u003c/code\u003e\u0027s extracted files (\u003ccode\u003ehex_metadata.config\u003c/code\u003e, \u003ccode\u003esrc/evildep.app.src\u003c/code\u003e). No scripts, plugins or hooks are in either package.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"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 =\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`\u0027s 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 =\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.\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\u0027s 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": "\u003col\u003e\n\u003cli\u003ehex.pm\u0027s registry record lists each release\u0027s dependencies as maps with \u003ccode\u003epackage\u003c/code\u003e, \u003ccode\u003erequirement\u003c/code\u003e, \u003ccode\u003eoptional\u003c/code\u003e, \u003ccode\u003eapp\u003c/code\u003e and \u003ccode\u003erepository\u003c/code\u003e. The \u003ccode\u003eapp\u003c/code\u003e value comes from the publisher\u0027s package metadata and hex.pm doesn\u0027t restrict it to a directory name. The record is signed with the repository\u0027s key after the publisher has chosen the value, so the signature doesn\u0027t constrain it.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003er3_hex_registry:decode_package/3\u003c/code\u003e decodes each dependency, including \u003ccode\u003eapp\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erebar_packages:parse_deps/1\u003c/code\u003e sets the dependency\u0027s name to the \u003ccode\u003eapp\u003c/code\u003e value, falling back to the package name: \u003ccode\u003e{maps:get(app, D, Name), {pkg, Name, Constraint, undefined, undefined}}\u003c/code\u003e. The name is stored in \u003ccode\u003e#package.dependencies\u003c/code\u003e and passed through \u003ccode\u003erebar_app_utils:update_source/3\u003c/code\u003e and \u003ccode\u003erebar_prv_install_deps:handle_dep/6\u003c/code\u003e.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erebar_app_utils:dep_to_app/7\u003c/code\u003e builds the fetch directory with \u003ccode\u003efilename:join([DepsDir, Name])\u003c/code\u003e and no check.\u003c/li\u003e\n\u003cli\u003e\u003ccode\u003erebar_fetch:download_source_online/2\u003c/code\u003e runs \u003ccode\u003erebar_file_utils:rm_rf(filename:absname(FetchDir))\u003c/code\u003e and then \u003ccode\u003erebar_file_utils:mv(TmpDir, filename:absname(FetchDir))\u003c/code\u003e. \u003ccode\u003efilename:absname/1\u003c/code\u003e doesn\u0027t collapse \u003ccode\u003e..\u003c/code\u003e segments, and on Unix \u003ccode\u003erm_rf/1\u003c/code\u003e runs \u003ccode\u003erm -rf\u003c/code\u003e through the shell with \u003ccode\u003erebar_utils:escape_chars/1\u003c/code\u003e, which doesn\u0027t escape \u003ccode\u003e/\u003c/code\u003e or \u003ccode\u003e.\u003c/code\u003e, so the \u003ccode\u003e..\u003c/code\u003e segments resolve outside the deps directory.\u003c/li\u003e\n\u003cli\u003eThe delete and overwrite happen for the dependency named by \u003ccode\u003eapp\u003c/code\u003e before its own \u003ccode\u003erebar.config.script\u003c/code\u003e is consulted (later in \u003ccode\u003erebar_fetch:download_source/2\u003c/code\u003e), and \u003ccode\u003eget-deps\u003c/code\u003e compiles nothing. rebar3 already runs publisher code during \u003ccode\u003eget-deps\u003c/code\u003e by evaluating a fetched package\u0027s \u003ccode\u003erebar.config.script\u003c/code\u003e and installing a dependency\u0027s plugins; the \u003ccode\u003eapp\u003c/code\u003e name adds a delete and overwrite outside \u003ccode\u003e_build\u003c/code\u003e for a dependency named by its parent package.\u003c/li\u003e\n\u003c/ol\u003e"
},
{
"base64": false,
"type": "text/markdown",
"value": "1. hex.pm\u0027s registry record lists each release\u0027s dependencies as maps with `package`, `requirement`, `optional`, `app` and `repository`. The `app` value comes from the publisher\u0027s package metadata and hex.pm doesn\u0027t restrict it to a directory name. The record is signed with the repository\u0027s key after the publisher has chosen the value, so the signature doesn\u0027t constrain it.\n2. `r3_hex_registry:decode_package/3` decodes each dependency, including `app`.\n3. `rebar_packages:parse_deps/1` sets the dependency\u0027s 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\u0027t collapse `..` segments, and on Unix `rm_rf/1` runs `rm -rf` through the shell with `rebar_utils:escape_chars/1`, which doesn\u0027t 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\u0027s `rebar.config.script` and installing a dependency\u0027s plugins; the `app` name adds a delete and overwrite outside `_build` for a dependency named by its parent package."
}
],
"value": "* hex.pm\u0027s registry record lists each release\u0027s dependencies as maps with package, requirement, optional, app and repository. The app value comes from the publisher\u0027s package metadata and hex.pm doesn\u0027t restrict it to a directory name. The record is signed with the repository\u0027s key after the publisher has chosen the value, so the signature doesn\u0027t constrain it.\n* r3_hex_registry:decode_package/3 decodes each dependency, including app.\n* rebar_packages:parse_deps/1 sets the dependency\u0027s 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\u0027t collapse .. segments, and on Unix rm_rf/1 runs rm -rf through the shell with rebar_utils:escape_chars/1, which doesn\u0027t 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\u0027s rebar.config.script and installing a dependency\u0027s 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",
"vulnerability-lookup:meta": {
"nvd": {
"cve": {
"affected": [
{
"affectedData": [
{
"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\u0026vcs_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"
}
]
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db"
}
],
"cveTags": [],
"descriptions": [
{
"lang": "en",
"value": "Improper Limitation of a Pathname to a Restricted Directory (\u0027Path Traversal\u0027) vulnerability in erlang rebar3 allows a malicious package publisher to delete a directory outside the project\u0027s _build directory and extract package files into it when a developer fetches dependencies.\n\nA package release\u0027s dependencies in a Hex registry carry an app field that names the dependency\u0027s OTP application. rebar3 uses this field as the dependency\u0027s name without validating it (rebar_packages:parse_deps/1) and builds the dependency\u0027s 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\u0027t 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\u0027s files there. This happens before the dependency\u0027s 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."
}
],
"id": "CVE-2026-103060",
"lastModified": "2026-10-12T16:17:00.873",
"metrics": {
"cvssMetricV40": [
{
"cvssData": {
"Automatable": "NOT_DEFINED",
"Recovery": "NOT_DEFINED",
"Safety": "NOT_DEFINED",
"attackComplexity": "LOW",
"attackRequirements": "PRESENT",
"attackVector": "NETWORK",
"availabilityRequirement": "NOT_DEFINED",
"baseScore": 7.5,
"baseSeverity": "HIGH",
"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": "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/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"
}
]
},
"published": "2026-10-12T16:17:00.873",
"references": [
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://cna.erlef.org/cves/CVE-2026-103060.html"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/erlang/rebar3/commit/56b7d88975aa8da6446857cfd92de0825024bf63"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/erlang/rebar3/commit/e35282b26e499b1d9e8f30bba9dccd71cd8b02ba"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://github.com/erlang/rebar3/security/advisories/GHSA-6ggr-r7mh-2mv8"
},
{
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"url": "https://osv.dev/vulnerability/EEF-CVE-2026-103060"
}
],
"sourceIdentifier": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"vulnStatus": "Received",
"weaknesses": [
{
"description": [
{
"lang": "en",
"value": "CWE-22"
}
],
"source": "6b3ad84c-e1a6-4bf7-a703-f496b71e49db",
"type": "Secondary"
}
]
}
}
}
}
Loading…
Loading…
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.
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.
Loading…