import { type FzConfig } from './config'; /** * Envless — `process.env` filled from the vault, and nothing written to disk. * * The point is that existing code does not change. A service reading * `process.env.DATABASE_URL` keeps reading it; what changes is where the value * came from and, crucially, that it never existed as a file. A `.env` on a box * is the single most common way a credential leaves a building: it survives the * process, it lands in backups, it gets copied to a laptop for debugging, and it * is readable by every other process running as that user. * * ## Three rules, all of them the reason this is a module and not four lines * * **Fail at boot, never at first use.** Every required secret is fetched before * the process is allowed to serve anything. The alternative is a container that * starts, passes its health check, and fails on whichever request first touches * the missing value — at 3am, on one endpoint, looking like an application bug. * * **Never at build time.** This is the rule that actually protects anything. A * bundler that inlines `process.env.X` bakes the value into a JavaScript file * that ships to a CDN. SvelteKit's `$env/static/private` and Next's * `next.config.js` `env` block both do exactly that. So `hydrateEnv` refuses to * run when it can tell it is inside a build, rather than trusting the caller to * only wire it up in the right place. * * **Report what was shadowed.** By default an existing variable wins, matching * dotenv, so a developer can override one value for an afternoon. The failure * mode of that default is a stale `export DATABASE_URL` in a shell profile * silently beating production — so anything shadowed is named in the result and * logged. A quiet precedence rule is the hardest bug in this area. */ export declare class EnvError extends Error { readonly code: 'MISSING_SECRET' | 'BUILD_TIME_REFUSED' | 'FETCH_FAILED'; readonly missing?: readonly string[] | undefined; constructor(code: 'MISSING_SECRET' | 'BUILD_TIME_REFUSED' | 'FETCH_FAILED', message: string, missing?: readonly string[] | undefined); } /** Just enough of `ForgeZero` to fetch. Structural, so tests need no client. */ export interface SecretReader { get(entry: string): Promise; } export interface HydrateOptions { config: FzConfig; vault: SecretReader; /** Defaults to `globalThis.process.env`. */ env?: Record; /** Escape hatch for a caller that genuinely knows better. See `isBuildTime`. */ allowDuringBuild?: boolean; onReport?: (report: HydrateReport) => void; } export interface HydrateReport { /** Names now set from the vault. */ loaded: readonly string[]; /** * Names the vault had a value for, which an existing variable beat. * * Never empty silently — this is the list somebody needs when production is * reading the wrong database. */ shadowed: readonly string[]; /** Optional secrets that were absent. Required ones throw instead. */ skipped: readonly string[]; environment: string; deploymentKey: string; } /** * Whether we are inside a build rather than a running server. * * Detected from the signals each framework actually sets, because the * consequence of getting it wrong is a secret in a client bundle — a failure * that is silent, permanent once published, and not fixed by rotating alone. * * Deliberately conservative: any one signal is enough to refuse. A false * positive costs a developer one explicit flag; a false negative costs a * credential. */ export declare function isBuildTime(env?: Record): boolean; /** * Fetch every declared secret and put it in the environment. * * Fetched in parallel and applied together: a partial application leaves the * process holding half its configuration, which is strictly worse than holding * none because it will start. */ export declare function hydrateEnv(options: HydrateOptions): Promise; /** * The default boot line. * * Names counts and variable NAMES, never a value or any part of one. A log line * that helpfully prints the first four characters of a secret is a log line that * puts a secret in a log aggregator with weaker access rules than the vault. */ export declare function describeReport(report: HydrateReport): string; /** * Remove what we set. * * For a test that must not leak state into the next one, and for a process * dropping privileges before exec'ing a child that should not inherit them. */ export declare function clearEnv(report: HydrateReport, env?: Record): void;