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

  1. If the data is legitimate: split the export into multiple smaller zips and import them separately
  2. 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)
  3. Reduce decompressed size upstream: simplify/reduce GPX sampling before zipping
  4. 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

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


AI-assisted analysis of Freika/dawarich@97fad417c5 (2026-08-21). Data as JSON: /api/errors/7653475736ffeaff. Report an issue: GitHub.