go · streaming · no cgo
Walk every file inside an archive.
Point it at an APK, a .deb, or a firmware.tar.gz and get an io.Reader for every contained file — descending through nested containers automatically, with decompression-bomb guards.
MIT licensed · pure Go, no cgo · descends nested containers
archives.Walk(ctx, "pkg.deb", archives.Options{}, func(ctx context.Context, path string, r io.Reader) error { n, _ := io.Copy(io.Discard, r) fmt.Printf("%-32s %d bytes\n", path, n) // pkg.deb!/data.tar!/usr/bin/htop 123456 bytes return nil })
what you get
Every leaf, streamed and bounded.
One recursive walker that descends into nested containers, hands you each file as a stream, and refuses to be blown up by hostile input.
Streaming
Files are handed to your callback as io.Readers; the whole archive is never loaded into memory.
Recurses automatically
Descends nested containers — ar → tar.xz → ELF, zip → dex — down to every leaf.
Virtual paths
Each leaf gets a !/-separated path so you can locate findings deep inside nested containers.
Magic-byte detection
Format is detected from content, not extension; non-archives simply emit as a single leaf.
Bomb-guarded
MaxDepth (8) and MaxBytes (2 GiB) bound untrusted input; it stops gracefully, not with an error.
Pure Go
Stdlib plus pure-Go zstd/xz decoders, no cgo; context cancellation is observed mid-stream.
usage
Basic walk, or bounded and instrumented.
Start with a zero-value Options{} and a callback. Opt into depth/size limits and a logger when the input is untrusted.
// Basic — walk every leaf
err := archives.Walk(ctx, path, archives.Options{}, fn)
// Safety + opt-in diagnostics err := archives.Walk(ctx, path, archives.Options{ MaxDepth: 8, MaxBytes: 2 << 30, Logger: slog.Default(), }, fn)
- zip
- apk
- jar
- tar
- gzip
- bzip2
- xz
- zstd
- deb
install
Add it to your module.
One import, pure Go, no cgo.
go get github.com/richardwooding/archives
import "github.com/richardwooding/archives"
Powers txtr's --recurse. Full API on the Go Reference.