Vault Passphrase Source
Where the vault's passphrase comes from, and how it stops being in memory. Three sources: an environment variable, a file the variable points at, or a TTY prompt. Which one is used is a deployment decision, so it is read from the environment rather than written into the application.
A passphrase arrives as a Buffer rather than a string, because a string cannot be zeroed: the bytes stay reachable until the collector decides otherwise, and a heap dump finds them. The caller is handed a Buffer and is expected to zero it once the derivation is done.
Reading from the environment strips the variable, so a child process started afterwards does not inherit the passphrase and nothing reading process.env later finds it. It does not erase the environment block the process was started with: on Linux a variable set at exec time stays readable through /proc/<pid>/environ for as long as the process lives, whatever this module does afterwards. A deployment that needs the passphrase kept away from a local reader passes it as a file or over a TTY rather than in the environment.
Stripping would then leave a retry with nothing to read, so the retry-aware callers hold the resolved copy for exactly as long as the operation that read it.
b.vaultPassphraseSource.fromEnv(opts?) #
{
envVars: object, // override the variable names
trimTrailingNewlines: boolean, // default false; strip trailing CR and LF
}
Read the passphrase from BLAMEJS_VAULT_PASSPHRASE and answer a Buffer.
The variable is stripped as it is read, so nothing the process starts later inherits it. That also means the read happens once: a second call finds nothing.
Stripping does not reach the environment block the process started with. A passphrase that was in the environment at exec time stays readable through /proc/ on Linux for the life of the process, so treat the strip as protection against inheritance, not against a local reader.
The value is used as the variable holds it. A secret delivered through a shell heredoc or a $(cat file) often carries a final newline, and that byte is part of the passphrase here: a vault sealed through this source with the newline needs the newline to open. fromFile is the other way round and always strips trailing CR and LF, so the same secret reaches the two sources differently.
trimTrailingNewlines makes this source strip them as well. It is off by default because turning it on changes the derived key: a vault sealed through this source from a value ending in a newline stops opening once it is set. Set it on a new vault, or re-seal an existing one first with b.vaultPassphraseOps.rotate.
An empty or oversized value is refused, and so is one that is nothing but newlines once trimmed.
var b = require("@blamejs/core");
process.env.BLAMEJS_VAULT_PASSPHRASE = "correct horse battery";
var pass = await b.vaultPassphraseSource.fromEnv();
process.env.BLAMEJS_VAULT_PASSPHRASE; // → undefined, it was stripped
pass.fill(0);
b.vaultPassphraseSource.fromFile(filePath, opts?) #
{
envVars: object, // override the variable names used in the messages
}
Read the passphrase from a file and answer a Buffer. This is the source for a secret delivered as a mounted file, which is how most orchestrators hand one over.
The read is bounded at the passphrase limit and goes through a descriptor that cannot be swapped between the check and the read, so a path pointing at something enormous is refused rather than loaded.
Trailing newlines are removed, since an editor or a secret store usually adds one. A missing path raises naming the variable that should have held it, and a read failure reports the file and the system error.
// requires: a readable passphrase file at the path below
var b = require("@blamejs/core");
var pass = await b.vaultPassphraseSource.fromFile("/run/secrets/vault-passphrase");
pass.fill(0);
b.vaultPassphraseSource.fromStdin(promptText?) #
Prompt for the passphrase on the terminal and answer a Buffer. The prompt defaults to Vault passphrase: .
It requires a TTY and raises otherwise, naming the interactive flag, because reading a passphrase from a non-TTY stdin would take whatever happened to be piped in. The typed characters are not echoed.
This is the source for an operator unsealing a vault by hand. A service starting unattended reads a variable or a file instead.
// requires: a TTY on stdin
var b = require("@blamejs/core");
var pass = await b.vaultPassphraseSource.fromStdin("Unseal passphrase: ");
pass.fill(0);
b.vaultPassphraseSource.sourceKind(opts?) #
{
envVars: object, // override the variable names
}
Answer which source would be used: "env", "file", "stdin", or null when none is available. It reads the environment and does not consume the passphrase, so a boot check can report what will happen before trying it.
BLAMEJS_VAULT_PASSPHRASE_SOURCE forces a source. Left at auto, a passphrase file wins over an inline variable, and a TTY is the last resort. A value other than auto, env, file or stdin raises rather than falling back, since a typo there would otherwise pick a source the operator did not choose.
var b = require("@blamejs/core");
b.vaultPassphraseSource.sourceKind(); // → "env", "file", "stdin" or null
b.vaultPassphraseSource.getPassphrase(opts?) #
{
prompt: string, // text shown when reading from a TTY
envVars: object, // override the variable names
Reading here consumes the source, so a caller that resolves the passphrase
itself has to use the Buffer it was handed. `b.vault.init` resolves its own,
which is why a deployment booting the vault does not call this first: doing
so strips the variable and leaves `init` with no source.
}
Resolve the passphrase from whichever source the environment selects, and answer a Buffer the caller zeroes when it is done.
With no source available it raises, naming the variables to set and the TTY option, because a vault that silently falls back to no passphrase is worse than one that will not open.
prompt is the text shown when the source is a TTY. The environment variable names are overridable, which is how the audit-signing key uses the same resolution with its own variables.
// requires: one of the passphrase env vars set, or a TTY on stdin
var b = require("@blamejs/core");
var pass = await b.vaultPassphraseSource.getPassphrase({ prompt: "Vault passphrase: " });
try {
await b.vaultPassphraseOps.seal({ dataDir: "./data", passphrase: pass });
} finally {
pass.fill(0);
}