Skip to content

feat: add mask spec option to redact env values in error output - #260

Draft
vergiLgood1 wants to merge 2 commits into
af:mainfrom
vergiLgood1:feat/validator-mask
Draft

vergiLgood1 wants to merge 2 commits into
af:mainfrom
vergiLgood1:feat/validator-mask

Conversation

@vergiLgood1

Copy link
Copy Markdown

Draft / R&D. Follow-up to #258, based on @af's suggestion to explore a general masking approach. Not necessarily shippable yet — mostly here so the idea can be looked at and reshaped.

Context

#258 stops json() from echoing the raw value. Aaron pointed out secrets are more commonly held in str, and suggested a general mask option (possibly with Proxy traps). This is my attempt at that.

Finding on the Proxy idea

Proxy traps only work on objects — new Proxy('x', {}) throws TypeError. Since str/num/bool return primitives, a Proxy can't wrap them, and str is where secrets usually live. So I went a different way and may have missed something obvious — happy to be corrected.

What this does

A mask spec option, applied where the library formats error messages:

str({ mask: 'last4' })   // Not a string: "****wxyz"
json({ mask: 'all' })    // Invalid json: "****"
str({ mask: (v) => `${String(v).length} chars` })
  • Presets 'all' | 'last4', plus a custom (value) => string renderer.
  • Validators render through a helper (displayValue / displayMasked), so redaction is exact — no string replacement. (My first attempt did msg.replace(raw, masked) and corrupted messages when the value was short: an input of "a" ate the a in "Invalid".)
  • No breaking changes: without mask, messages are byte-identical, and the public makeValidator signatures are unchanged. url/json stay value-free by default but show a masked value when mask is set.
  • Custom validators: quoted values in thrown errors are redacted automatically.
  • example is masked too (it shows up in missing-var errors).

Stacked on #258

This branch is based on fix/json-error-leaks-value, so the diff currently shows 2 commits. Only the second (e38e28b) is new; the first is #258 itself and will drop out once #258 merges.

Verification

  • npm run test:ci — 80 tests pass
  • npm run ts and npm run lint — clean
  • mask.ts and makers.ts at 100% coverage

Open questions

  1. Keep it opt-in, or make it safe-by-default (drop the raw value when no mask is set, like the url/json commits)? It's a one-line change in displayValue.
  2. Should the render helper be exported so custom validators can render masked values exactly, or leave them on the quoted-value fallback?

Leaving this as a Draft for now — I'm not sure it's the shape you had in mind, so happy to redo or drop it.

The raw value could contain sensitive data (API keys, tokens, service
credentials) and would end up in logs via the error output. Follows
1f67e8d, which made the same change for the url validator.
Draft/R&D follow-up to af#258. Secrets are commonly held in `str`, so this adds a
general `mask` option applied where the library formats error messages, instead
of patching a single validator.

- `mask: 'all' | 'last4' | (value) => string`
- Validators render through a helper, so redaction is exact (no string replace)
- No breaking changes: without `mask`, messages are byte-identical
  - url/json stay value-free by default, but show a masked value when `mask` is set
- Custom validators: quoted values in thrown errors are redacted automatically
- `example` is masked too (it appears in missing-var errors)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant