jenkinsci/jenkins · critical · IOException
Zip ${zipFile.getPath()} contains illegal file name that bre
Error message
Zip ${zipFile.getPath()} contains illegal file name that breaks out of the target directory: ${e.getName()} What it means
Thrown by FilePath.unzip as a zip-slip guard: for each entry, Jenkins computes the target file's canonical path and requires it to start with the destination directory's canonical path. If an entry name like ../evil escapes the target, extraction aborts before writing outside the directory. This is a security control, not a convenience check.
Source
Thrown at core/src/main/java/hudson/FilePath.java:715
// TODO why does this not simply use ZipInputStream?
IOUtils.copy(in, tmpFile);
unzip(dir, tmpFile);
}
finally {
Files.delete(Util.fileToPath(tmpFile));
}
}
private static void unzip(File dir, File zipFile) throws IOException {
dir = dir.getAbsoluteFile(); // without absolutization, getParentFile below seems to fail
try (ZipFile zip = new ZipFile(zipFile)) {
Enumeration<ZipEntry> entries = zip.getEntries();
while (entries.hasMoreElements()) {
ZipEntry e = entries.nextElement();
File f = new File(dir, e.getName());
if (!f.getCanonicalFile().toPath().startsWith(dir.getCanonicalPath())) {
throw new IOException(
"Zip " + zipFile.getPath() + " contains illegal file name that breaks out of the target directory: " + e.getName());
}
if (e.isDirectory()) {
mkdirs(f);
} else {
File p = f.getParentFile();
if (p != null) {
mkdirs(p);
}
try (InputStream input = zip.getInputStream(e)) {
IOUtils.copy(input, f);
}
try {
FilePath target = new FilePath(f);
int mode = e.getUnixMode();
if (mode != 0) // Ant returns 0 if the archive doesn't record the access mode
target.chmod(mode);
} catch (InterruptedException | NoSuchFileException ex) {View on GitHub (pinned to 2e228ff40b)
Solutions
- Do not unpack untrusted archives with FilePath.unzip; pre-validate or repackage with relative entry names.
- Inspect the archive entry names with `unzip -l` and remove/normalize any '..' or absolute paths.
- Repack the archive so all entries are relative and contained.
- If the source is trusted but sloppy, normalize entry names in a repackaging step before install.
Example fix
null
Defensive patterns
Strategy: validation
Validate before calling
static boolean isSafeArchive(File zipFile) throws IOException {
Path base = zipFile.getParentFile().getCanonicalFile().toPath();
try (ZipFile zf = new ZipFile(zipFile)) {
Enumeration<? extends ZipEntry> en = zf.entries();
while (en.hasMoreElements()) {
Path target = new File(base.toFile(), en.nextElement().getName()).getCanonicalFile().toPath();
if (!target.startsWith(base)) return false;
}
}
return true;
} Type guard
null
Try / catch
try {
fp.unzipFrom(stream);
} catch (IOException e) {
if (e.getMessage().contains("breaks out of the target directory")) {
// reject the archive — do not attempt to sanitize path traversal
throw new SecurityException("Refusing unsafe archive", e);
}
throw e;
} Prevention
- Never unpack archives from untrusted sources without pre-validation.
- Repackage untrusted archives to normalize entry names before install.
- Treat this exception as a security incident, inspect the offending entry name.
When it happens
Trigger: A zip/tar entry name contains '..' segments that resolve above the destination dir; an absolute entry path resolving outside dir; symbolic-link-heavy archive resolving outside; archive from an untrusted or compromised source.
Common situations: Unpacking a tool/archive downloaded from an untrusted URL via install/unpack; a vendor zip with malformed entry names; crafted archive in a supply-chain attack; archive produced by a tool that emits absolute paths.
Related errors
- Failed to unpack %s (%d bytes read of total %d)
- Failed to unpack %s (%d bytes read)
- invalid iconSize
- SHA-256 could not be instantiated, but is required to be imp
- DataInputStream unexpectedly returned negative integer
AI-assisted analysis of jenkinsci/jenkins@2e228ff40b (2026-08-14).
Data as JSON: /api/errors/bd8d529af636d01c.
Report an issue: GitHub.