Skip to content

Stream image layers instead of extracting to disk - #6

Merged
BinanIbrahim merged 1 commit into
mainfrom
feat/stream-layers
Jun 25, 2026
Merged

Stream image layers instead of extracting to disk#6
BinanIbrahim merged 1 commit into
mainfrom
feat/stream-layers

Conversation

@BinanIbrahim

Copy link
Copy Markdown
Owner

Why

Scanning unpacked the whole docker save archive to a temp directory and then walked the layers a second time to locate the Alpine package database — two full passes and a fully materialized image tree on disk for one file's worth of data. This lands Phase 1 #3 from the roadmap: read that file directly from the saved tar, so a multi-GB image no longer has to be exploded to disk, and most scans need no unpacked tree at all.

Approach

The analyzer only needs lib/apk/db/installed as it exists in the final merged filesystem. We keep the single image.tar docker save writes but stop unpacking it:

  • extractor.SaveImage replaces ExtractImage — pulls + saves, returns the tar path (no untar).
  • extractor.StreamWalker walks a tar stream applying the same per-entry and whole-image cumulative size caps and traversal guard as Untar, via a callback instead of writing to disk.
  • analyzer.BuildSBOM streams: pass 1 locates manifest.json (position in the archive isn't guaranteed), pass 2 walks layers, scans each for the apk DB, and resolves the winner in manifest order — including OCI whiteout handling so a deleted DB in a later layer doesn't surface a stale one.

The bounded-extraction guards from the prior tar-bomb work are preserved on the streaming path, and Untar is retained as a fallback.

Tests

  • New StreamWalker tests (guards + the shared cumulative budget spanning nested walks).
  • New analyzer_test.go (the package's first): nested in-memory tar fixtures covering gzip/plain layers, manifest-vs-tar ordering, last-write-wins, whiteout + opaque-whiteout deletion, re-add after whiteout, empty-DB → not-found, and the size cap. All race-clean, no network.

A high-effort review pass caught and fixed an empty/corrupt-DB case that returned an empty SBOM instead of an error.

🤖 Generated with Claude Code

The scanner unpacked the entire docker-save archive to a temp dir and
then walked the layers a second time to find the apk database — paying
for full extraction and a separate read pass. Read the one file we need
(lib/apk/db/installed, as merged across layers) directly from the saved
tar instead: lower disk footprint, no unpacked tree, and a single
in-memory walk guarded by the same tar-bomb size caps.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@BinanIbrahim
BinanIbrahim merged commit 9fa4404 into main Jun 25, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant