Skip to content

[high] ENCRYPTION_KEY reachable from all handlers via AppContext — no serialisation guard #3

@aspiers

Description

@aspiers

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.

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions