{"record":{"id":"7653475736ffeaff","repo":"Freika/dawarich","slug":"entry-exceeds-max-extracted-size-bytes","errorCode":null,"errorMessage":"entry exceeds #{MAX_EXTRACTED_SIZE} bytes","messagePattern":"entry exceeds #(.+?) bytes","errorType":"exception","errorClass":"Archive::Unzipper::ArchiveTooLarge","httpStatus":null,"severity":"error","filePath":"app/services/archive/unzipper.rb","lineNumber":63,"sourceCode":"      ::Zip::File.open(path) do |zf|\n        entry = zf.entries.reject(&:directory?).first\n        raise ArgumentError, 'zip has no entries' unless entry\n\n        ext = File.extname(entry.name)\n        # Tempfile.create (not .new) returns a plain File without an\n        # ObjectSpace finalizer, so the path survives GC of the File object.\n        # The caller (Imports::Create) is responsible for unlinking.\n        inner = Tempfile.create(['unzipped', ext], binmode: true)\n\n        begin\n          bytes_written = 0\n          entry.get_input_stream do |stream|\n            while (chunk = stream.read(64 * 1024))\n              bytes_written += chunk.bytesize\n              if bytes_written > MAX_EXTRACTED_SIZE\n                inner.close\n                File.unlink(inner.path) if File.exist?(inner.path)\n                raise ArchiveTooLarge, \"entry exceeds #{MAX_EXTRACTED_SIZE} bytes\"\n              end\n\n              inner.write(chunk)\n            end\n          end\n          inner.close\n          inner.path\n        rescue ArchiveTooLarge\n          raise\n        rescue StandardError\n          inner.close unless inner.closed?\n          File.unlink(inner.path) if File.exist?(inner.path)\n          raise\n        end\n      end\n    end\n\n    def self.zip_magic?(path)","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/Freika/dawarich/blob/97fad417c5a11b0eb11157890635e015723a2e97/app/services/archive/unzipper.rb#L45-L81","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before: single 5GB-decompressing entry in one zip\n# after: split exports so each archive's inner file stays under the cap\n#\n# docker-compose.yml\n#  environment:\n#    ZIP_MAX_EXTRACTED_SIZE: '5368709120' # 5 GiB, restart app after setting","handlingStrategy":"validation","validationCode":"# Reject uploads whose decompressed payload would exceed the cap, before extracting\nrequire 'zip'\n\ndef estimated_entry_size_ok?(path, cap = Archive::Unzipper::MAX_EXTRACTED_SIZE)\n  Zip::File.open(path) do |zf|\n    entry = zf.entries.reject(&:directory?).first\n    entry.nil? || entry.size <= cap # entry.size is the uncompressed size\n  end\nrescue Zip::Error\n  false\nend","typeGuard":null,"tryCatchPattern":"begin\n  inner = Archive::Unzipper.extract_single(path)\nrescue Archive::Unzipper::ArchiveTooLarge\n  render_error(:payload_too_large, 'Archive entry too large after extraction - split the export or raise ZIP_MAX_EXTRACTED_SIZE')\nend","preventionTips":["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"],"tags":["zip","import","archive","security","limits"],"backgroundTag":"zip-bomb-protection","analyzedSha":"97fad417c5a11b0eb11157890635e015723a2e97","analyzedAt":"2026-08-21T17:04:17.778Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}