Freika/dawarich · error · Archive::Unzipper::ArchiveTooLarge
entry exceeds #{MAX_EXTRACTED_SIZE} bytes
Error message
entry exceeds #{MAX_EXTRACTED_SIZE} bytes What it means
Archive::Unzipper enforces a decompression cap while streaming: each 64KB chunk increments bytes_written, and exceeding MAX_EXTRACTED_SIZE (ENV ZIP_MAX_EXTRACTED_SIZE, default 2 gigabytes) raises ArchiveTooLarge 'entry exceeds ... bytes'. The temp file is closed and unlinked before raising, and the ArchiveTooLarge re-raise is kept distinct from generic cleanup. This is a zip-bomb guard: the check is on decompressed bytes, not the compressed file size on disk.
Source
Thrown at app/services/archive/unzipper.rb:63
::Zip::File.open(path) do |zf|
entry = zf.entries.reject(&:directory?).first
raise ArgumentError, 'zip has no entries' unless entry
ext = File.extname(entry.name)
# Tempfile.create (not .new) returns a plain File without an
# ObjectSpace finalizer, so the path survives GC of the File object.
# The caller (Imports::Create) is responsible for unlinking.
inner = Tempfile.create(['unzipped', ext], binmode: true)
begin
bytes_written = 0
entry.get_input_stream do |stream|
while (chunk = stream.read(64 * 1024))
bytes_written += chunk.bytesize
if bytes_written > MAX_EXTRACTED_SIZE
inner.close
File.unlink(inner.path) if File.exist?(inner.path)
raise ArchiveTooLarge, "entry exceeds #{MAX_EXTRACTED_SIZE} bytes"
end
inner.write(chunk)
end
end
inner.close
inner.path
rescue ArchiveTooLarge
raise
rescue StandardError
inner.close unless inner.closed?
File.unlink(inner.path) if File.exist?(inner.path)
raise
end
end
end
def self.zip_magic?(path)View on GitHub (pinned to 97fad417c5)
Solutions
- If the data is legitimate: split the export into multiple smaller zips and import them separately
- Or raise the cap for your deployment: set ZIP_MAX_EXTRACTED_SIZE to a larger byte value in the environment (it is read with ENV.fetch at boot, so restart after changing)
- Reduce decompressed size upstream: simplify/reduce GPX sampling before zipping
- If unexpected: inspect the entry with unzip -l (compressed vs uncompressed sizes) to spot a zip bomb
Example fix
# before: single 5GB-decompressing entry in one zip # after: split exports so each archive's inner file stays under the cap # # docker-compose.yml # environment: # ZIP_MAX_EXTRACTED_SIZE: '5368709120' # 5 GiB, restart app after setting
Defensive patterns
Strategy: validation
Validate before calling
# Reject uploads whose decompressed payload would exceed the cap, before extracting
require 'zip'
def estimated_entry_size_ok?(path, cap = Archive::Unzipper::MAX_EXTRACTED_SIZE)
Zip::File.open(path) do |zf|
entry = zf.entries.reject(&:directory?).first
entry.nil? || entry.size <= cap # entry.size is the uncompressed size
end
rescue Zip::Error
false
end Try / catch
begin inner = Archive::Unzipper.extract_single(path) rescue Archive::Unzipper::ArchiveTooLarge render_error(:payload_too_large, 'Archive entry too large after extraction - split the export or raise ZIP_MAX_EXTRACTED_SIZE') end
Prevention
- Expose the effective MAX_EXTRACTED_SIZE in import UI copy so users know the ceiling
- Read entry.size (uncompressed) from the zip central directory before streaming as an early warning
- Split large history exports into multiple archives rather than raising the security cap permanently
When it happens
Trigger: Importing a zip whose single inner file decompresses beyond the cap - legitimately huge multi-year GPX exports, or malicious/archival high-ratio archives (a 50MB zip expanding past 2GB). The streamed check trips mid-extraction, after which the import is aborted.
Common situations: Bulk history exports from other tracking apps compressing to just under upload limits but expanding to multiple GB, zip bombs (intentional or incidental from redundant XML), operators lowering ZIP_MAX_EXTRACTED_SIZE for tighter limits without communicating it, high-precision traces with per-second sampling.
Related errors
- zip has no entries
- Unknown export format: neither manifest.json nor data.json f
- Could not classify zip contents -- file may be corrupted
- nonce mismatch
- Invalid URL scheme: %{scheme}
AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21).
Data as JSON: /api/errors/7653475736ffeaff.
Report an issue: GitHub.