Files
skills/docs/security-stack/security/server-keys/src/commands/files/files.md
2026-05-29 08:20:56 +01:00

4.2 KiB

commands/files/files

(no submodules)

Purpose

Local-packer file-transport commands: prepareFileCommand, validateFileCommand, getFileStatsCommand, validatePacketCommand. These four functions implement the operator-facing surface every file- transport bin in @suaveplan/server-keys calls into. Reconstruction lives in the sibling serve-file.ts. The implementation is fully local — no HTTP, no remote file server — backed by @suaveplan/packer's whole-file Packer / Unpacker and plain Node fs.

The choice of a local packer (rather than an HTTP client to a remote file-server, which the upstream package shipped) was made deliberately: the upstream HTTP design forces operators to stand up a separate file-server before they can prepare or serve a file, and the binary contract is dominated by the packet wire format anyway. Owning the format here keeps the suaveplan test surface auditable without a network hop and removes a non-trivial deployment dependency.

Features

  • Fixed-size packetisation (packetSize, default 1 MiB) with a JSON envelope per packet on disk.
  • ed25519 signature per packet using the vault's sign slot secret; recipient verifies against the manifest's signerPublicKey.
  • Manifest is a single JSON document that lists every packet's index, checksum, filename, and originalSize.
  • Cooperative abort at every packet boundary via options.signal.
  • Typed ServerKeysError for every failure (INVALID_INPUT, VAULT_DECRYPT_FAILED, PACKET_INTEGRITY_FAILED, FILE_NOT_FOUND, ABORTED).

Usage: Basic

import {
    prepareFileCommand,
    validateFileCommand,
    getFileStatsCommand,
} from "@suaveplan/server-keys/commands/files/files";

const stats = await getFileStatsCommand("/tmp/data.bin", {
    packetSize: 256 * 1024,
});
console.log(stats.estimatedPackets);

await prepareFileCommand(
    "/tmp/data.bin",
    "/tmp/keys/server.vault",
    "serverpw1234",
    { output: "/tmp/prepared", packetSize: 256 * 1024 },
);

const result = await validateFileCommand("/tmp/prepared");
expect(result.isComplete).toBe(true);
expect(result.invalidPackets).toEqual([]);

Usage: Advanced

Single-packet validation against an explicit hex public key (useful for spot-checking a remote-supplied packet without trusting the colocated manifest):

import { validatePacketCommand } from "@suaveplan/server-keys/commands/files/files";

const r = await validatePacketCommand(
    "/tmp/prepared/packet.000000.bin",
    "abcd...ed25519-public-key-hex",
);
expect(r.isValid).toBe(true);

Detect a tampered packet:

import { promises as fs } from "node:fs";
const target = "/tmp/prepared/packet.000003.bin";
const original = await fs.readFile(target, "utf-8");
const obj = JSON.parse(original);
const bytes = Buffer.from(obj.data, "hex");
bytes[10] ^= 0xff;
obj.data = bytes.toString("hex");
await fs.writeFile(target, JSON.stringify(obj));
const r = await validateFileCommand("/tmp/prepared");
expect(r.isComplete).toBe(false);
expect(r.invalidPackets).toContain(3);

API Reference

  • prepareFileCommand(filePath, vaultPath, password, options?) => Promise<FileManifest>
  • validateFileCommand(packetDir, options?) => Promise<ValidateFileResult>
  • getFileStatsCommand(filePath, options?) => Promise<FileStatsResult>
  • validatePacketCommand(packetPath, verifyKeyHex, options?) => Promise<ValidatePacketResult>

All take FileCommandOptions = { output?, packetSize?, logger?, signal? }.

Implementation Notes

prepareFileCommand constructs a new Packer per packet so the underlying event bus stays scoped to a single pack call — the abstract Packer is an EventEmitter and we don't want pack events leaking across packets. Each packet stores its signature, data, checksum, and originalSize in a small JSON envelope ({ data, signature, checksum, originalSize }), all hex-encoded — operators inspecting packets by hand can paste them into a hex viewer without further tooling. The intentional compressionLevel: 0 (no gzip) keeps each packet's plaintext slice byte-identical to the input region; without this, signing operates over the gzip stream and reconstruction requires inverting it, which is an avoidable layer for the local transport.