Summary
ENCRYPTION_KEY is the master AES-256-GCM key that protects every stored credential in the system — all PDS app passwords and recovery keys are encrypted with it. It is held as a plain hex string in config and exposed on the AppContext object passed to every route handler (src/config.ts, src/context.ts).
The exposure path is indirect (accidental serialisation rather than a deliberate leak), but the blast radius is total: a single accidental log line or debug endpoint that serialises ctx hands an attacker the ability to decrypt every credential in the database.
Details
AppContext holds config, making the raw key string reachable from every route handler and middleware.
- There is no private closure, class accessor, or redaction guard preventing accidental serialisation.
- Common trigger scenarios: a catch-all error logger that serialises the request context, a debug/health endpoint that dumps config, or a future middleware author who doesn't know the key is in scope.
Suggested Fix
Wrap the encryption key in a class with a private field so it cannot be accidentally serialised or logged:
class Encrypter {
#key: Buffer
constructor(hexKey: string) { this.#key = Buffer.from(hexKey, 'hex') }
encrypt(plaintext: string): string { ... }
decrypt(ciphertext: string): string { ... }
}
Expose only an Encrypter instance on AppContext, not the raw key. JSON.stringify and console logging of a class instance with a private field will not include #key.
Summary
ENCRYPTION_KEYis the master AES-256-GCM key that protects every stored credential in the system — all PDS app passwords and recovery keys are encrypted with it. It is held as a plain hex string inconfigand exposed on theAppContextobject passed to every route handler (src/config.ts,src/context.ts).The exposure path is indirect (accidental serialisation rather than a deliberate leak), but the blast radius is total: a single accidental log line or debug endpoint that serialises
ctxhands an attacker the ability to decrypt every credential in the database.Details
AppContextholdsconfig, making the raw key string reachable from every route handler and middleware.Suggested Fix
Wrap the encryption key in a class with a private field so it cannot be accidentally serialised or logged:
Expose only an
Encrypterinstance onAppContext, not the raw key.JSON.stringifyand console logging of a class instance with a private field will not include#key.