getgrav/grav · error · RuntimeException
ZipArchiver: refused to extract {archive_file}. Entry "{name
Error message
ZipArchiver: refused to extract {archive_file}. Entry "{name}" exceeds the maximum nesting depth ({maxDepth}). What it means
As part of the pre-extraction safety pass, ZipArchiver::extract() counts path segments of each entry (splitting on slashes/backslashes) and rejects archives containing entries nested deeper than the configured maximum (system.gpm.archive.max_depth, default 48; 0 disables) with RuntimeException naming the entry and limit. Deeply nested trees are blocked because recursive cleanup and traversal can overflow the stack (CWE-674) — the same class of defense as the file-count and size caps.
Source
Thrown at system/src/Grav/Common/Filesystem/ZipArchiver.php:73
}
$totalSize = 0;
for ($i = 0; $i < $numFiles; $i++) {
$name = $zip->getNameIndex($i);
if ($name === false) {
continue;
}
if (!$this->isSafeEntryPath($name)) {
$zip->close();
throw new RuntimeException('ZipArchiver: refused to extract ' . $this->archive_file . '. Entry "' . $name . '" would escape the destination directory (Zip Slip).');
}
if ($maxDepth > 0) {
$depth = count(array_filter(preg_split('#[\\\\/]+#', trim($name, '/\\'))));
if ($depth > $maxDepth) {
$zip->close();
throw new RuntimeException('ZipArchiver: refused to extract ' . $this->archive_file . '. Entry "' . $name . '" exceeds the maximum nesting depth (' . $maxDepth . ').');
}
}
if ($maxSize > 0) {
// Advisory only: statIndex()['size'] is the uncompressed size
// declared in the central directory, which the archive author
// controls and can forge small (GHSA-8h9x-89f2-m7x3). It gives
// an early reject for honest oversized archives, but the real
// enforcement happens during streamed extraction below, against
// the bytes actually inflated.
$stat = $zip->statIndex($i);
if (is_array($stat) && isset($stat['size'])) {
$totalSize += (int) $stat['size'];
if ($totalSize > $maxSize) {
$zip->close();
throw new RuntimeException('ZipArchiver: refused to extract ' . $this->archive_file . '. Archive exceeds the maximum uncompressed size (' . $maxSize . ' bytes).');
}
}View on GitHub (pinned to 6040efed04)
Solutions
- Inspect the offending entry named in the message: unzip -l package.zip | awk '{print $4}' | awk -F/ 'NF>40' to see the deep chains
- If legitimate, raise the cap in user/config/system.yaml: gpm: archive: max_depth: 64
- If the depth is unexpected/artificial, reject the archive rather than raising the limit — deep nesting is a known attack shape
- Re-package flattened (strip empty path segments) when you control the archive's creation
Example fix
# user/config/system.yaml — before (default)
gpm:
archive:
max_depth: 48
# after — allow a trusted package with deeper trees
gpm:
archive:
max_depth: 64 Defensive patterns
Strategy: validation
Validate before calling
$maxDepth = (int) Grav::instance()['config']->get('system.gpm.archive.max_depth', 48);
$zip = new ZipArchive();
if ($zip->open($path) === true) {
for ($i = 0; $i < $zip->count(); $i++) {
$name = (string) $zip->getNameIndex($i);
$depth = count(array_filter(preg_split('#[\\\\/]+#', trim($name, '/\\'))));
if ($depth > $maxDepth) {
$zip->close();
throw new RuntimeException("Entry {$name} nested {$depth} deep (limit {$maxDepth})");
}
}
$zip->close();
} Try / catch
try {
(new ZipArchiver($path))->extract($destination);
} catch (RuntimeException $e) {
if (str_contains($e->getMessage(), 'nesting depth')) {
// inspect the named entry; raise max_depth only for trusted, legitimately deep packages
}
} Prevention
- Keep gpm.archive.max_depth documented alongside your package sources so config matches reality
- When generating archives yourself, flatten empty directory chains and vendor trees
- Unexpected depth in a third-party archive is suspicious — verify before raising the cap
When it happens
Trigger: Extracting a package that legitimately contains very deep trees (nested vendor or node_modules chains, generated asset folders) beyond 48 levels; a crafted archive with artificially deep paths aimed at recursive-delete overflow; max_depth lowered in config below what your packages contain.
Common situations: Installing skeletons/plugins that bundle dependency trees; build pipelines that generated absurdly deep output paths; security tightening that set a lower max_depth than real packages need.
Related errors
- ZipArchiver: refused to extract {archive_file}. Archive exce
- ZipArchiver: refused to extract {archive_file}. Entry "{name
- ZipArchiver: refused to extract {archive_file}. Archive exce
- Backup location not allowed (outside site root): {$backup_ro
- ZipArchiver: ZIP failed to extract {archive_file} to {destin
AI-assisted analysis of getgrav/grav@6040efed04 (2026-08-17).
Data as JSON: /api/errors/0f350adc6f8a8ca2.
Report an issue: GitHub.