Restore Bundle
Backup-bundle reader — verify the manifest signature, list bundle contents without decrypting, and cherry-pick a restore subset to a staging directory the caller atomically swaps into place.
The mirror of b.backupBundle. b.restoreBundle.inspect reads manifest.json and returns the parsed object — useful for dashboards and pre-flight UI that want to list files, sizes, timestamps, and kinds before prompting the operator for the passphrase. b.restoreBundle.extract decrypts each per-file blob via b.backup/crypto, verifies the SHA3-512 plaintext checksum against the manifest, and writes the recovered files into a fresh stagingDir. The bundle directory itself stays read-only throughout.
extract always recovers the wrapped vault key (decrypted JSON returned on vaultKeyJson) so the operator can unseal columns from a partial restore. The filter predicate lets the caller pull a subset — only the DB, only TLS keys, only the consent log — without producing every blob.
Defense surface:
- Wrong passphrase / tampered blob → AEAD tag failure → restore-bundle/decrypt-failed (no plaintext leak, no staging left behind) - Pre-decrypt encryptedSize mismatch → restore-bundle/ size-mismatch - Post-decrypt SHA3-512 ≠ manifest checksum → restore-bundle/checksum-mismatch - Missing blob file → restore-bundle/missing-blob - Bad manifest signature → restore-bundle/bad-signature; requireSignature: true upgrades a missing signature to restore-bundle/missing-signature - On any failure the partially-built stagingDir is removed so a subsequent retry is not blocked by a stale directory
b.restoreBundle.extract(opts) #
{
bundleDir: string, // read-only bundle dir (required)
stagingDir: string, // fresh output dir (required, must not exist)
passphrase: Buffer | string, // unwrap key (required)
filter: function (entry): boolean,// subset predicate
progressCallback: function (ev): void, // phase events: read_manifest / decrypt / done
verifySignature: boolean, // default: true
requireSignature: boolean, // fail closed on a missing signature
expectedFingerprint: string, // trust only the key with this fingerprint
}
Decrypt every blob the manifest references (or the subset opts.filter accepts), verify each plaintext's checksum, and write the recovered files into opts.stagingDir. Returns { manifest, vaultKeyJson, fileCount, totalBytes, stagingDir, durationMs }.
stagingDir MUST NOT exist: extract throws restore-bundle/staging-exists rather than merge into an existing directory, so a half-finished prior restore can never get silently overlaid. On any failure the partial stagingDir is removed.
Before reading anything it throws restore-bundle/no-bundle when bundleDir is absent or does not exist, restore-bundle/no-staging when stagingDir is absent or empty, and restore-bundle/no-passphrase when passphrase is neither a Buffer nor a string.
Signature handling: when the manifest carries a signature it is verified with b.backupManifest.verifySignature. The signing key must match expectedFingerprint, or be the active or a rotated b.auditSign key; a signature under any other key fails with restore-bundle/bad-signature. A process that restores without running b.auditSign.init() passes expectedFingerprint. Pass requireSignature: true to fail closed on a bundle missing a signature, which then throws restore-bundle/missing-signature. verifySignature: false skips the check, and requireSignature: true with verifySignature: false throws restore-bundle/bad-opts.
When the manifest records keyScheme: "argon2id-hkdf-sha3-512-path-digest", each blob is encrypted under a key derived from that bundle's bundleSalt, so a blob copied in from another bundle fails with restore-bundle/decrypt-failed whether or not the manifest is signed. A manifest without keyScheme is read with one Argon2id key per file.
Reading the manifest throws restore-bundle/bad-manifest when it is unreadable or over its size limit. Each decrypted file is then checked against what the manifest recorded: restore-bundle/checksum-mismatch when the plaintext hashes differently and restore-bundle/size-mismatch when its length differs, so a tampered blob never reaches the staging directory. A manifest entry naming a blob the bundle does not hold, or one it holds but cannot read, throws restore-bundle/missing-blob. A manifest whose vault key cannot be recovered throws restore-bundle/vault-key-recovery-failed.
try {
var report = await b.restoreBundle.extract({
bundleDir: "/srv/backups/2026-04-27.bundle",
stagingDir: "/srv/restore/data.staging",
passphrase: Buffer.from("operator-passphrase"),
requireSignature: true,
filter: function (entry) { return entry.kind === "db"; },
});
report.fileCount; // → 1
typeof report.vaultKeyJson; // → "string"
} catch (e) {
e.code; // → "restore-bundle/decrypt-failed"
}
b.restoreBundle.inspect(opts) #
{
bundleDir: string, // bundle directory (required, must exist)
}
Read manifest.json from opts.bundleDir and return the parsed object — files, sizes, timestamps, kinds, signature presence — without prompting for the passphrase or decrypting anything. Useful for dashboards, pre-flight UI, and "what's in this bundle?" checks before kicking off a long extract.
Throws RestoreBundleError("restore-bundle/no-bundle") when bundleDir is missing, and RestoreBundleError("restore-bundle/missing-manifest") when the directory exists but has no manifest.json (the bundle is incomplete or not a blamejs bundle). A manifest.json that is unreadable, or larger than the limit b.backupManifest.serialize enforces, throws restore-bundle/bad-manifest.
try {
var manifest = b.restoreBundle.inspect({
bundleDir: "/srv/backups/2026-04-27.bundle",
});
manifest.files.length; // → 12
typeof manifest.signature; // → "string"
} catch (e) {
e.code; // → "restore-bundle/missing-manifest"
}