/** * `SecretBox` — the seal/open adapter the connection store rests on (#101, * design/connections.md §3.3). * * **Bucket 2 in D-18's triage**, which names the KMS as an adapter explicitly. * The kernel decides that per-tenant credentials are encrypted at rest and that * plaintext never touches the directory; *what* does the encrypting is swappable * — Web Crypto locally, a Cloudflare Secrets Store binding or an external KMS * when hosted. * * Before this existed there was no encryption primitive in the codebase at all: * every `crypto.subtle` call was a one-way digest, and every secret was a * plaintext Worker binding. Nothing was per-tenant, nothing was rotatable. */ import { SubstratError } from '@substrat-run/contracts'; /** * Sealed bytes plus the id of the key that sealed them. * * `keyId` is what makes rotation possible: a new key seals new writes while old * blobs stay openable, so re-sealing is a background sweep rather than a * flag-day. A `SecretBox` that cannot name its key can only ever have one. */ export interface SealedSecret { keyId: string; /** Opaque to everything above this interface. Base64 for the Web Crypto impl. */ ciphertext: string; } export interface SecretBox { seal(plaintext: string): Promise; /** Throws if the blob was sealed by a key this box does not hold. */ open(sealed: SealedSecret): Promise; } /** * Exported for `subject-keys.ts`, which has to move raw key BYTES through a string-shaped * `SecretBox` (a DEK is 32 bytes; `seal` takes text). Deliberately not a general-purpose * utility export — nothing else should need to reach below this file's abstraction. */ export declare const toBase64: (bytes: Uint8Array) => string; /** The inverse; see `toBase64` above for why it is exported. */ export declare const fromBase64: (b64: string) => Uint8Array; /** * AES-256-GCM over Web Crypto — the default for dev, CI and self-hosting. * * `key` is 32 raw bytes. A fresh 96-bit IV per seal is prepended to the * ciphertext, which is what makes it safe to seal the same credential twice; * GCM with a reused IV is catastrophic rather than merely weak. * * **Fails closed when unconfigured.** A host built without a `SecretBox` cannot * store a credential at all, rather than storing one in the clear. That is the * rule `assertPlatformCall` already states — *"an unset secret is a failure, not * a bypass"* — applied to the thing it most obviously protects. Note the router * secret does the opposite; it is not a precedent to copy here. */ export declare function webCryptoSecretBox(keyId: string, key: Uint8Array): SecretBox; /** * The refusal a host without a `SecretBox` raises (#603). * * Typed rather than a plain `Error` because it is a **deployment fact**, not a * fault in the request: the caller sent a well-formed credential to a host that * was started without a seal key. A transport that cannot tell the two apart * answers 500 and sends the operator hunting through a worker tail for a fact * the process knew at boot. Every seam that can raise it maps it to a typed * 503 naming the missing key. */ export declare class SecretBoxUnconfiguredError extends SubstratError { constructor(message: string); } /** * The box a host gets when none was configured: every call throws. * * Deliberately not "store it in the clear" and not "silently disable * connections" — either would make a misconfiguration invisible until a * credential leaked. Reading the error tells an operator exactly what to set. */ export declare const unconfiguredSecretBox: SecretBox; /** * Whether a host can seal at all — the question a caller asks BEFORE doing work * whose only purpose is to produce something to store (#603). * * Identity against `unconfiguredSecretBox` rather than a flag on the interface: * "unconfigured" is exactly the one box this module hands out, and keeping the * `SecretBox` interface at two methods means an external KMS adapter has nothing * extra to implement. */ export declare const isSecretBoxConfigured: (box: SecretBox | undefined) => boolean; //# sourceMappingURL=secret-box.d.ts.map