A codec that only round-trips against itself hasn’t been tested — it’s been rehearsed.
Today we released OxiArc 0.3.6 — a release that bundles two hardening passes from the same development cycle: a 2026-07-08 security-hardening and API-stabilization pass, and a 2026-07-13 reference-interoperability campaign that ran 22 auditors across zstd, brotli, LZMA/XZ, bzip2, SZIP, LZW/TIFF, DEFLATE wrappers, LZ4, Snappy, LZHUF, and every archive container format — and found that several codecs weren’t merely buggy, they were private dialects: internally consistent, perfectly self-round-tripping, and almost completely unable to talk to the reference implementation they claimed to support.
No C, no Fortran, no zlib, no libarchive, no external shared libraries. Just clean, memory-safe, blazing-fast archiving and compression that compiles to a single static binary, targets WASM, and runs everywhere. OxiArc is the Pure Rust replacement for the zip, tar, gzip, zstd, and 7-zip tools — and for the Rust crates zip, flate2, zstd, bzip2, lz4, tar, snap, brotli, and miniz_oxide.
Why OxiArc 0.3.6 is a game changer
“We pass our own test suite” has always been a weaker claim than it sounds. Self round-trips alone cannot prove interoperability — an encoder and decoder that share the same deviation from a spec will round-trip perfectly while being incompatible with everything else. A 22-auditor differential investigation found exactly that hiding in five codecs — zstd, brotli, LZMA2/XZ, TIFF-LZW, and SZIP each passed their own tests while failing against the real reference implementation in both directions. The resulting 75-item remediation plan (P0-P3, all 75 shipped) includes:
- zstd went from 0/64 to 64/64. The FSE backward bitstream was read FIFO/LSB-first instead of RFC 8878’s LIFO/MSB-first — invisible to self-round-trips because OxiArc’s own writer made the identical mistake. Real zstd frames triggered 62 panics and 2 silent corruptions out of 64; this is also the OxiGDAL-reported FSE decoder panic. Now: 64/64 corpus plus 101/101 wide reference frames decode byte-identical, 85/85 oxiarc frames are accepted by
zstd -d, and 60,000 fuzz cases produce 0 panics. - brotli’s decoder and encoder were rewritten wholesale against RFC 7932. Baseline: 141/172 reference-stream decode failures (plus 21 silently wrong results), and
brotli -drejected 441/441 oxiarc outputs. After the rewrite — block-type switching, the exact §7.1 context tables, the full distance-code space, and the byte-exact 122,784-byte Appendix A static dictionary — it’s 608/608 decode byte-identical and 588/588 oxiarc streams accepted bybrotli -d. - LZMA2/XZ multi-chunk streams are decodable again. The decoder reset its uncompressed-position tracking on every chunk, desyncing
pos_state/lit_stateon any real.xzfile spanning more than one chunk — which is most of them. Verified againstxz 5.8.3: 60/60 decode and 8/8 encode, byte-identical. - bzip2 no longer silently drops concatenated streams. Multi-stream
.bz2(frompbzip2,lbzip2, or a plaincat a.bz2 b.bz2) was truncated to the first stream with a successfulOkreturn. Every stream now decodes, and the encoder’s new libbz2-style multi-Huffman-table clustering reaches 99.9% of the reference compression ratio. Verified againstbzip2 1.0.8: 324/324 both directions. - SZIP now actually speaks CCSDS-121.0-B-2. The AEC framing placed the RSI reference sample outside the block’s option-ID field, breaking libaec interop in both directions — including silent wrong output on genuine libaec streams. Rewritten per spec section 5.2 and verified against live libaec 1.1.4: 2450/2450 decode and 4900/4900 encode, byte-identical.
--memory-limit, the advertised decompression-bomb defense, is now actually enforced. It was silently unenforced for file-based gzip/xz/bzip2 extraction — a 50 MB bomb expanded fully under--memory-limit 1Mand exited0. It’s now checked during decode for every format; a brotli bomb under the same 1 MB limit now peaks at 3.3 MB RSS instead of 72.9 MB, and exits non-zero.
Technical Deep Dive
OxiArc’s four-layer architecture is what let a 22-auditor campaign attack the whole codebase systematically instead of chasing bugs one file at a time:
- L1 —
oxiarc-core: the primitives everything else trusts.BitReader/BitWriter(LSB-first for DEFLATE-family formats, MSB-first viamsb_bitstreamfor LZH/LHA),RingBuffer, and slicing-by-8 CRC-16/32/64 with aarch64 PMULL / x86_64 PCLMULQDQ+SSE4.1 SIMD paths. 0.3.6 fixes a CRC SIMD diagnostics misreport (the dispatched code path was already correct; onlyis_simd_available()/implementation_name()lied about which one), adds non-panickingRingBuffer::try_new/OutputRingBuffer::try_newfor untrusted window sizes, and makesBitReader::fill_bufferloop on short reads instead of erroring on valid pipe/socket streams. - L2 — ten codec crates, where the private dialects lived.
oxiarc-zstd,oxiarc-brotli,oxiarc-lzma,oxiarc-bzip2,oxiarc-szip,oxiarc-lzw,oxiarc-lz4,oxiarc-lzhuf,oxiarc-snappy, andoxiarc-deflateeach implement one format’s algorithm in isolation — exactly where a self-consistent-but-wrong bitstream can hide indefinitely. Every one of these crates is now gated by a reference-differential oracle feature. - L3 —
oxiarc-archive: the container layer where hostile input meets the parser. Thirteen formats (ZIP, TAR, GZIP, LZH, XZ, 7z, CAB, LZ4, Zstd, Bzip2, Brotli, Snappy, ISO 9660) share this crate, and it absorbed most of the memory-safety fixes: a ZIP AES integer-underflow panic, a DOS-datemonth=0underflow, spanned-ZIP rejection, a TAR PAX slice-index panic, an ISO 9660 cyclic-directory DoS, a 7z unbounded-varint allocation, and a ZIP-encryption-detection fix that now reads the actual general-purpose bit 0 instead of a homegrown marker only OxiArc’s own writer ever produced. - L4 —
oxiarc-cli: where--memory-limithas to mean what it says. The CLI is the last mile between an untrusted file and a bounded process — the one flag whose entire job is standing between a user and a decompression bomb, which is why closing its silent-no-op gap mattered as much as any codec fix in this release.
Getting Started
cargo install oxiarc-cli
cargo add [email protected]
use oxiarc_archive::ZipReader;
use std::fs::File;
fn read_zip() -> oxiarc_core::error::Result<()> {
let file = File::open("archive.zip")?;
let mut zip = ZipReader::new(file)?;
for entry in zip.entries() {
println!("{}: {} bytes (compressed: {})",
entry.name, entry.size, entry.compressed_size);
}
if let Some(entry) = zip.entry_by_name("readme.txt").cloned() {
let data = zip.extract(&entry)?;
println!("Content: {}", String::from_utf8_lossy(&data));
}
Ok(())
}
Nothing about this API changed in 0.3.6 — what changed is what’s on the other side of it. A ZIP built by 7-Zip, WinRAR, or zip -e, and a .xz/.bz2/.zst/.br/.tiff file produced by any standard reference tool, now round-trip through OxiArc exactly the way they round-trip through their own reference implementation.
What’s New in 0.3.6
- oxiarc-zstd: RFC 8878 FSE bitstream direction fix, 4-stream Huffman jump-table fix, Huffman weight-table validation with RFC 4.2.1.1 implied-last-weight deduction, FSE probability-sum validation, a truncated-FCS fix, and a dictionary-frame
Dictionary_IDfix. - oxiarc-brotli: full RFC 7932 decoder and encoder rewrite, including the byte-exact 122,784-byte Appendix A static dictionary (previously an empty stub) with all 121 transforms.
- oxiarc-lzma: persistent LZMA2 decoder position state fixes multi-chunk
.xzdecoding; new stateful chunked encoder;LzmaPropertiesnow validates lc/lp/pb instead of allocating multiple TiB on bad input. - oxiarc-bzip2: full multi-stream decode support, legacy randomized-block de-randomization, libbz2-style multi-Huffman-table encoder clustering, new
decompress_with_limit. - oxiarc-szip: CCSDS-121.0-B-2 §5.2-conformant RSI/option-ID framing, verified against live libaec.
- oxiarc-lzw: TIFF 6.0 Clear Code support, byte-identical to libtiff in both directions.
- oxiarc-deflate: streaming/trait wrapper fixes (
Inflater,Deflater,GzipDecoder) for correctness beyond 32 KiB and across concatenated multi-member gzip. - oxiarc-lz4: LASTLITERALS(5) end-of-block invariant fix and block-independence-aware frame decoding (
FrameDescriptor::with_block_independence). - oxiarc-lzhuf: truncated-stream detection across lh4-lh7 (4,442/4,442 truncation trials now correctly return
Err). - Security: Zip-Slip/path-traversal hardening, symlink-aware extraction, Windows long-path/reserved-name handling,
try_reserve-based allocation bounding across every header-driven allocation site, a genuine FIPS-197 AES implementation replacing a silent AES-128/192-to-256 downgrade, constant-time AE-2 tag comparison, CSPRNG-sourced salts, and a--memory-limitthat’s enforced during decode for every format instead of only some. - API stability (pre-1.0): 18 public enums marked
#[non_exhaustive]; 106#[must_use]attributes added on consuming builder setters;LzwConfig::newandLzmaPropertiesnow validate input and returnResultinstead of panicking. - New:
SECURITY.md,CONTRIBUTING.md, full CLI man pages,deny.toml, per-crateLICENSEfiles, and ten opt-in*-oracleCargo features for live reference-tool differential testing. - 2,426 tests passing, 0 failed, 0 ignored — up from 2,004 (+1 skipped) — across 100 binaries plus 137 doctests; zero clippy warnings; ~114,394 lines across 336 Rust files (tokei).
Tips
- Re-encode any SZIP or LZW streams produced by a pre-0.3.6 OxiArc. These two wire formats moved from private dialects to the real standards, so old and new bytes are not cross-compatible — intended, since the old bytes interoperated with nothing else either.
- zstd/brotli/LZMA2/bzip2/LZ4 output changes bytes too, but stays backward-readable. Old archives still decode; only the newly-produced bytes are also now accepted by the reference tools.
- Turn on the
*-oraclefeatures you have tools for.zstd-oracle,brotli-oracle,xz-oracle,bzip2-oracle,lz4-oracle,snappy-oracle,zlib-oracle,tiff-oracle,libaec-oracle, andzip-oracleeach shell out to the real reference tool and self-skip cleanly when it’s absent — safe to enable in any CI, hermetic by default. - If you rely on
--memory-limitin a script or service, re-verify it now actually holds for gzip/xz/bzip2 file extraction — pre-0.3.6, the flag looked enforced and silently wasn’t. - Audit any
matchonCompressionMethod,EntryType,ArchiveFormat,FlushMode, or the codec error enums. All 18 are now#[non_exhaustive]— add a wildcard arm if you haven’t already. LzwConfig::newandLzmaProperties::newnow returnResult. If you were calling either with hand-picked bit widths or lc/lp/pb values, handle the newErrpath instead of relying on the old panic-or-succeed behavior.
This is the foundation
Interoperability at the bitstream level is what lets OxiArc sit underneath the rest of the ecosystem without surprises. OxiGDAL is the direct beneficiary of the flagship zstd fix — the 64 real-world Zarr v3 zstd frames that triggered this entire campaign now decode 64/64 byte-identical through the CLI, clearing the way for OxiGDAL to bump its dependency and retire a long-standing #[ignore]. OxiRS routes its zstd-shim compatibility layer straight through oxiarc-zstd. FVRS, OxiMedia, SciRS2/NumRS2, RusMES, and ToRSh all pin oxiarc-* crates for the same archive and compression paths, wherever they read or write formats that also need to open correctly outside the COOLJAPAN ecosystem.
Repository: https://github.com/cool-japan/oxiarc
Star the repo if you want an archiver that doesn’t just trust its own round-trip tests, but proves it against the tools everyone else actually uses.
The era of the private dialect that quietly passes its own test suite is over. Pure Rust archiving that’s byte-for-byte compatible — audited by 22 people against every reference implementation that matters — is here.
— KitaSan at COOLJAPAN OÜ July 13, 2026