CVE-2026-47078 Overview
CVE-2026-47078 is a relative path traversal vulnerability [CWE-23] in the Erlang/OTP stdlib zip module. The flaw affects the zip:unzip/1,2 and zip:extract/1,2 functions in lib/stdlib/src/zip.erl. A crafted zip archive containing entries with .. components can write files outside the intended extraction directory. The validation logic in check_dir_level/2 tracks directory depth as a running integer counter, rejecting only paths whose final counter value is negative. Paths that transiently go negative then recover, such as ../x/y, pass the check.
Critical Impact
An attacker who supplies a malicious zip archive to an Erlang application can write arbitrary files outside the extraction directory, potentially overwriting configuration, code, or user data.
Affected Products
- Erlang/OTP from OTP 27.1 before OTP 29.0.4
- Erlang/OTP 28.5.0.4
- Erlang/OTP 27.3.4.15 (stdlib 6.1 before 8.0.3, 7.3.0.1, and 6.2.2.4)
Discovery Timeline
- 2026-07-27 - CVE-2026-47078 published to NVD
- 2026-07-30 - Last updated in NVD database
Technical Details for CVE-2026-47078
Vulnerability Analysis
The vulnerability resides in lib/stdlib/src/zip.erl, which handles zip archive extraction in Erlang/OTP. When extracting an archive, the module calls check_dir_level/2 to validate each entry path against directory traversal. The check walks path components left to right, decrementing a counter on .., incrementing on normal names, and skipping .. A path is rejected only if the final counter is less than zero.
This logic misses traversal sequences that dip below zero mid-path but recover before the final component. An entry such as ../x/y decrements to -1, then increments back to 1, passing validation. When the caller joins the path with the current working directory through add_cwd, the resolved location falls outside the extraction root.
Root Cause
The root cause is an incorrect invariant in path validation. The counter-based algorithm tracks only the ending depth, not whether the path ever escaped the extraction root during traversal. Any positive component after a .. that escapes the root masks the escape from the check.
Attack Vector
Exploitation requires an application to extract an attacker-controlled zip archive using zip:unzip or zip:extract. User interaction is typically needed to supply or trigger extraction of the archive. The attacker crafts archive entries whose names contain .. sequences balanced by additional path components, causing files to be written to attacker-chosen locations relative to the process working directory.
%% Source: https://github.com/erlang/otp/commit/8a933c9c7835b06776d31d17b79b7336627d887a
%% Security patch in lib/stdlib/src/zip.erl - zip: Fix relative path traversal checking
end,
%% check for directory traversal exploit
{IsValid, Name} =
- case check_dir_level(filename:split(FileName), 0) of
- {FileOrDir,Level} when Level < 0 ->
+ case check_dir_level(FileName) of
+ {FileOrDir, invalid} ->
CWD1 = if CWD == "" -> "./";
true -> CWD
end,
error_logger:format("Illegal path: ~ts, extracting in ~ts~n",
[add_cwd(CWD,FileName),CWD1]),
{false, FileOrDir};
- _ ->
+ {_FileOrDir, valid} ->
{true, FileName}
end,
{IsValid, string:trim(Name, trailing, "/") ++ TrailingSlash}.
-check_dir_level([FileOrDir], Level) ->
- {FileOrDir,Level};
-check_dir_level(["." | Parts], Level) ->
- check_dir_level(Parts, Level);
-check_dir_level([".." | Parts], Level) ->
- check_dir_level(Parts, Level-1);
-check_dir_level([_Dir | Parts], Level) ->
- check_dir_level(Parts, Level+1).
+check_dir_level(FileName) ->
+ %% Normalize the path by rejecting `.`. If we have an invalid path like
+ %% "../hello/.", we will then present it to the filter as "hello".
The patch replaces the counter-based check with a normalized path evaluation that returns a valid/invalid verdict, rejecting any path that escapes the extraction root at any point during traversal.
Detection Methods for CVE-2026-47078
Indicators of Compromise
- Files written outside the intended extraction directory following an Erlang application's zip extraction.
- Zip archive entries whose names contain .. sequences combined with additional path components (for example, ../etc/config, ../../app/priv/file).
- error_logger entries containing the string Illegal path: on patched versions, indicating rejected traversal attempts.
Detection Strategies
- Statically inspect zip archives before extraction for entries with .. components using tools such as unzip -l.
- Instrument or wrap zip:unzip and zip:extract calls to log resolved absolute paths for each extracted entry.
- Compare extracted file paths against the expected extraction root and alert on any deviation.
Monitoring Recommendations
- Monitor file creation events under process working directories of Erlang/OTP applications for writes outside expected extraction paths.
- Track deployment inventory of Erlang/OTP versions and flag hosts running stdlib versions below 8.0.3, 7.3.0.1, or 6.2.2.4.
- Alert on error_logger messages emitted by zip.erl that reference illegal paths.
How to Mitigate CVE-2026-47078
Immediate Actions Required
- Upgrade Erlang/OTP to 29.0.4, 28.5.0.4, or 27.3.4.15, or later, depending on the deployed release line.
- Audit application code that calls zip:unzip/1,2 or zip:extract/1,2 on untrusted input and identify all extraction targets.
- Restrict the operating system permissions of Erlang processes so that traversal outside the extraction root has limited impact.
Patch Information
The fix is committed in lib/stdlib/src/zip.erl at commit 8a933c9c7835b06776d31d17b79b7336627d887a. See the GitHub Security Advisory GHSA-rf72-wp7h-jg3x and the Erlang Ecosystem Foundation CNA advisory for full details.
Workarounds
- Validate zip entry names before calling zip:unzip or zip:extract and reject any entry containing .. path components.
- Extract archives into an isolated temporary directory and move only expected files to the final destination.
- Run extraction in a sandbox or chroot with least-privilege file system access to contain any traversal.
# Verify installed Erlang/OTP version and stdlib version
erl -eval 'io:format("~s ~s~n", [erlang:system_info(otp_release), begin {ok, V} = application:get_key(stdlib, vsn), V end]), halt().' -noshell
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

