instructure/canvas-lms · error · Attachment::OverQuotaError

Zip contents exceed course quota limit

Error message

Zip contents exceed course quota limit

What it means

charge_quota in lib/unzip_attachment.rb:299 raises Attachment::OverQuotaError when an individual extracted entry's size exceeds the remaining quota tracked during extraction. Because zip central directories can lie about entry sizes, Canvas decrements @quota_remaining per entry to defend against zip-bomb denial-of-service attacks.

Solutions

  1. Treat the upload as suspicious and verify the zip source; use a zip-bomb scanner for untrusted uploads
  2. Free up quota in the target context and re-upload a smaller, verified archive
  3. Delete partially extracted files from the failed attempt before retrying
  4. Do not loosen this check — it is the anti-zip-bomb defense

Example fix

// before
# zip declares 100KB entries but inflates to 5GB each
UnzipAttachment.process(course, 'sneaky.zip')
// after
# pre-scan: reject zips with suspicious compression ratios or nested zips before processing
Defensive patterns

Strategy: try-catch

Validate before calling

raise SuspiciousZip if zip_compression_ratio(zip_path) > MAX_SAFE_RATIO

Try / catch

begin
  UnzipAttachment.process(context, zip)
rescue Attachment::OverQuotaError => e
  logger.warn("possible zip bomb: #{e.message}")
  mark_upload_flagged(upload)
end

Prevention

When it happens

Trigger: Extraction encounters an entry whose actual inflated size exceeds @quota_remaining (quota was set by validate_against); a maliciously crafted or corrupted zip whose declared sizes are smaller than the real content.

Common situations: Malicious zip bombs uploaded by users; zips with inaccurate central directory metadata; legitimately large files uploaded when the course was just under quota per nominal size.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/a0cf7da6b3ae5478. Report an issue: GitHub.

Appendix: source

Thrown at lib/unzip_attachment.rb:299

    # check whether the nominal size of the zip's contents would exceed
    # quota, and reject the zip immediately if so
    quota_hash = Attachment.get_quota(context)
    if quota_hash[:quota] > 0
      if (quota_hash[:quota_used] + total_size) > quota_hash[:quota]
        raise Attachment::OverQuotaError, "Zip file would exceed quota limit"
      end

      @quota_remaining = quota_hash[:quota] - quota_hash[:quota_used]
    end
  end

  # since the central directory can lie, track quota during extraction as well
  # to prevent zip bomb denial-of-service attacks
  def charge_quota(size)
    return if @quota_remaining.nil?
    if size > @quota_remaining
      raise Attachment::OverQuotaError, "Zip contents exceed course quota limit"
    end

    @quota_remaining -= size
  end

  def paths_with_positions(base)
    positions_hash = {}
    paths.sort.each_with_index { |p, idx| positions_hash[p] = idx + base }
    positions_hash
  end

  def percent_complete(current_index)
    (current_index + 1).to_f / file_count.to_f
  end

  private

  def process!

View on GitHub (pinned to 1c9f0bb801)