Archive Adapters

Source-bytes adapter contract for the b.archive.read family. Unifies how bytes flow into the reader regardless of where they live — a local file, an object-store bucket, an HTTP endpoint with Range support, an in-memory Buffer, or a trusted Readable.

Two contract shapes, picked by the caller's use case:

- **Random-access** — { size, range(offset, length) → Buffer } Required for the read primitive's CD-walk path (the canonical adversarial-safe ZIP read). The reader fetches the EOCD trailer first (last ~64 KiB), walks the central directory, then per-entry seeks the LFH + compressed bytes. Defends the LFH/CD-skew + Zip-Slip + zip-bomb classes by validating every claim before decompressing.

- **Trusted sequential** — { readable: } Forward-scan-only fallback for operators who control both ends (e.g. piping the framework's own b.archive.zip().toStream() back into a reader 30 seconds later). The reader walks local file headers in order; the CD/LFH skew defense + the "entries hidden from LFH but present in CD" attack class are OFF in this mode because there's no central directory to compare against. The trust boundary is in the API surface name — operators reaching for trustedStream are declaring they own the producer.

AbortSignal is propagated end-to-end: every adapter accepts an opts.signal parameter; in-flight range calls abort when the caller cancels. Adapters refuse to return short reads silently — a 5-byte request that fulfills 3 bytes throws adapter/short-read so the reader can decide whether to refuse the archive or surface the truncation.

Shipped adapters:

b.archive.adapters.fs(path, opts?) — local file b.archive.adapters.buffer(buf, opts?) — in-memory b.archive.adapters.objectStore(client, key, opts?) — composes b.objectStore Range-GET path b.archive.adapters.http(url, opts?) — composes b.httpClient with Range: bytes= … b.archive.adapters.trustedStream(readable, opts?) — Readable fallback

objectStore + http are composition entry points — operators wire their own b.objectStore client / b.httpClient instance in so the adapter inherits the framework's SSRF guard / TLS posture / audit chain without duplicating that surface here.

b.archive.adapters.fs(path, opts?) #

stable0.12.7
{
  signal:   AbortSignal,        // propagates to in-flight read()s
}

Local-file random-access adapter. Opens a read-only file descriptor + fstats the size at adapter-create time so the reader's CD walk can begin with the trailer offset known up-front. Subsequent range(offset, length) calls reuse the same fd — operators extracting an archive don't pay a fresh open per range. close() is idempotent + safe to call after errors.

var adapter = b.archive.adapters.fs("/var/uploads/payload.zip");
try {
  var reader = b.archive.read.zip(adapter);
  var entries = await reader.inspect();
} finally {
  await adapter.close();
}

b.archive.adapters.buffer(buf, opts?) #

stable0.12.7
{
  signal:   AbortSignal,
}

In-memory random-access adapter — slices a Buffer on range(). Useful for tests, small operator-uploaded payloads already in memory, and round-tripping b.archive.zip().toBuffer() output back through the reader without touching disk.

var produced = b.archive.zip();
produced.addFile("readme.txt", "Hello\n");
var bytes = produced.toBuffer();
var reader = b.archive.read.zip(b.archive.adapters.buffer(bytes));
var entries = await reader.inspect();

b.archive.adapters.objectStore(client, key, opts?) #

stable0.12.7hipaapci-dssgdprsoc2
{
  size:    number,         // override size (skips head() call)
  signal:  AbortSignal,
  audit:   b.audit,        // forwarded to client.get
}

Random-access adapter backed by an operator-supplied b.objectStore client. The adapter calls client.get(key, { range: [start, end] }) for every range() request and reads the response body into a Buffer. Composes the framework's existing SSRF guard / TLS posture / audit chain — adapter behaviour follows whatever the client was configured with.

The client is expected to expose: client.head(key) → { size: } (or similar size accessor) client.get(key, opts) → AsyncIterable | { body: Readable } (Range opt honored)

Operators using bucket implementations that don't expose .head() pass opts.size explicitly.

var client  = { get: async function () { return Buffer.alloc(0); }, head: async function () { return { size: 0 }; } };
var adapter = b.archive.adapters.objectStore(client, "incoming/payload.zip");
var reader  = b.archive.read.zip(adapter);
var policy  = b.guardArchive.zipBombPolicy({ maxTotalDecompressedBytes: 268435456 });
void reader; void policy;

b.archive.adapters.http(url, opts?) #

stable0.12.7gdprhipaapci-dss
{
  client:  b.httpClient,     // override the default (must already exist)
  headers: { ... },
  timeoutMs: number,         // per-request
  signal:  AbortSignal,
  audit:   b.audit,
}

Random-access adapter backed by HTTP Range requests. Composes the framework's b.httpClient (SSRF guard + TLS posture + audit chain + PQC-hybrid agent) so the adapter inherits the operator's network surface configuration without duplicating it here.

First call issues a HEAD to determine size + verify the server accepts Range requests (Accept-Ranges: bytes). Servers without Range support are refused with adapter/no-range — operators downloading the full byte stream first and feeding b.archive. adapters.buffer is the appropriate fallback in that case.

var adapter = b.archive.adapters.http("https://artifact-host.example.com/release.zip", {
  timeoutMs: 60_000,
});
var reader = b.archive.read.zip(adapter);
var entries = await reader.inspect();

b.archive.adapters.trustedStream(readable, opts?) #

stable0.12.7
{
  signal:  AbortSignal,
}

Forward-scan-only adapter for trusted Readable sources. The reader walks local file headers in order; the CD/LFH skew defense and the "entries hidden from LFH but present in CD" attack class are OFF in this mode because there's no central directory to compare against. Operators reaching for this primitive are declaring they own the producer (e.g. piping their own b.archive.zip().toStream() output back into a reader 30 seconds later for round-trip verification).

Adversarial input MUST use b.archive.adapters.fs / buffer / objectStore / http — the random-access path is the only adversarial-safe one.

var produced = fs.createReadStream("./own-export.zip");
var reader   = b.archive.read.zip.fromTrustedStream(produced);
var entries  = [];
for await (var e of reader.entries()) entries.push(e);

b.archive.adapters.isRandomAccessAdapter(a) #

stable0.12.7

Type-predicate: returns true when a is the random-access shape ({ kind: "random-access", range, ... }) produced by fs / buffer / objectStore / http. Operators routing through b.archive.read.zip compose this to refuse trusted-stream adapters at the wrong entry point.

var ok = b.archive.adapters.isRandomAccessAdapter(adapter);
if (!ok) throw new Error("need random-access adapter");

b.archive.adapters.isTrustedStreamAdapter(a) #

stable0.12.7

Type-predicate: returns true when a is the trusted-sequential shape ({ kind: "trusted-sequential", readable, ... }) produced by trustedStream. Operators routing through b.archive.read.zip. fromTrustedStream compose this to refuse random-access adapters at the wrong entry point.

var ok = b.archive.adapters.isTrustedStreamAdapter(adapter);
if (!ok) throw new Error("need trusted-stream adapter");

Last updated 2026-08-08T16:39:49.652Z by seeder.