/** * secrets-keyfile.ts, keyfile lifecycle + envelope crypto for SecretsManager. * * Extracted from secrets.ts and hardened after a real incident (2026-07): * a keyfile went missing, several long-lived processes each minted their own * replacement key (non-exclusive generation, last write wins), and the losers * kept encrypting stores with cached keys nobody else had, surfacing days * later as generic GCM auth failures with no way to tell which key wrote the * store. Three guards close that class: * * 1. Exclusive generation, the keyfile is created with the `wx` flag; a * process that loses the creation race adopts the winner's key instead of * keeping a private in-memory one. * 2. Pre-write revalidation, every store write re-reads the keyfile and * refuses to encrypt with a cached key that no longer matches (and * restores a missing keyfile from the cached key, which at that point is * the only surviving copy). * 3. Key fingerprints, every envelope records a short hash of the key that * wrote it, so a mismatched read reports "written with key X, current is * Y" instead of a bare authentication failure. The fingerprint reveals * nothing about the key (8 hex chars of a SHA-256). */ /** * On-disk envelope for the encrypted store. `version` was introduced together * with keyfile-derived encryption; files without a `version` field are legacy * stores encrypted with a key derived from the machine's hostname + username, * and are migrated to the current format on first successful read. `keyId` is * additive and optional: stores written before it existed decrypt exactly as * before, and older SDK versions ignore it (the version number is unchanged). */ export interface EncryptedStoreEnvelope { version?: number; iv: string; tag: string; data: string; /** Fingerprint of the key that wrote this store (8 hex chars of SHA-256; reveals nothing). */ keyId?: string; } export declare const SECRETS_STORE_FORMAT_VERSION = 2; /** * Thrown when a secrets store file exists on disk but cannot be read back * (wrong key, tampered content, malformed JSON, or an unknown future format). * Writes to that store are refused so its contents are never overwritten. */ export declare class SecretStoreUnreadableError extends Error { constructor(message: string); } /** Short public fingerprint of an encryption key: 8 hex chars of its SHA-256. */ export declare function keyFingerprint(key: Buffer): string; /** * Key derivation used by stores written before keyfile-derived encryption. * Kept solely so those stores can be decrypted once and migrated; never used * for new writes. */ export declare function deriveLegacyEncryptionKey(identity?: { readonly hostname?: string; readonly username?: string; }): Buffer; export declare function encryptStore(plaintext: string, key: Buffer): EncryptedStoreEnvelope; export declare function decryptStore(store: EncryptedStoreEnvelope, key: Buffer): string; /** * Load the key from the keyfile, generating a fresh random key on first need. * Generation is EXCLUSIVE (`wx`): when two processes race to create the file, * exactly one key wins, and the loser adopts it instead of keeping a private * in-memory key that would write stores nobody else can read. The keyfile is * 0600 inside a 0700 directory; the key never derives from host identity, so * a store directory copied to another machine keeps decrypting. */ export declare function loadOrCreateKeyfile(keyFilePath: string): Buffer; /** * Guard a store write against the on-disk key state. A cached key that no * longer matches the keyfile must never encrypt a store, every other process * (and this one, after restart) would be unable to read it. A MISSING keyfile * is restored from the cached key: at that moment the cache is the only * surviving copy, and persisting it keeps every store written so far readable. */ export declare function assertCachedKeyIsCurrent(keyFilePath: string, cachedKey: Buffer): void; //# sourceMappingURL=secrets-keyfile.d.ts.map