Tiny TypeScript wrapper for values that should not leak through logs, JSON, string coercion, or Node.js inspection.
Inspired by Dillon Mulroy's Redacted module and Effect's Redacted.
It helps prevent accidental exposure. It is not encryption, a secret manager, a sandbox, or a security boundary. Code that can call revealRedacted() can read the value.
npm install @svene/redactimport { redact, revealRedacted } from "@svene/redact";
const apiKey = redact("sk_live_123");
console.log(apiKey); // <redacted>
String(apiKey); // <redacted>
JSON.stringify({ apiKey }); // {"apiKey":"<redacted>"}
const raw = revealRedacted(apiKey); // sk_live_123This package is small. If you only need it in one app, copying the code into your own utils is fine.
Install it if you want the tested version, the types, the examples, and the same behavior across projects.
Secrets leak through boring paths: debug logs, request dumps, JSON snapshots, test failures, console.log, and util.inspect.
@svene/redact makes the safe path the default. Raw access stays explicit.
The value is stored in an internal WeakMap, not on the wrapper object.
import { redact } from "@svene/redact";
const token = redact("secret");
Object.keys(token); // []
Reflect.ownKeys(token); // []
Object.getOwnPropertyDescriptors(token); // {}Use Brand when two secrets share the same runtime type but must not be mixed.
import {
createRedacted,
revealRedacted,
type Brand,
type Redacted,
} from "@svene/redact";
type ApiKey = string & Brand<"ApiKey">;
type DbPassword = string & Brand<"DbPassword">;
const apiKey = "sk_live_123" as ApiKey;
const dbPassword = "password" as DbPassword;
const redactedApiKey: Redacted<ApiKey> = createRedacted(apiKey);
const redactedDbPassword: Redacted<DbPassword> = createRedacted(dbPassword);
const fetchUser = (key: Redacted<ApiKey>) => {
const raw: ApiKey = revealRedacted(key);
return raw;
};
fetchUser(redactedApiKey); // ok
fetchUser(redactedDbPassword); // TypeScript errorDisposal removes the wrapper from the internal registry. After that, revealRedacted() throws.
import { disposeRedacted, redact, revealRedacted } from "@svene/redact";
const secret = redact("sensitive");
revealRedacted(secret); // sensitive
disposeRedacted(secret);
revealRedacted(secret); // throws TypeErrorIn runtimes where Symbol.dispose exists, wrappers also expose a runtime dispose hook. The public Redacted<T> type does not require ESNext.Disposable; ordinary TypeScript consumers can use disposeRedacted().
Disposal does not wipe memory. JavaScript does not provide reliable memory wiping for strings, and other references to the original value may still exist.
Both ESM and CommonJS entrypoints are published. In one JavaScript realm, wrappers created through import and require share the same registry, so either entrypoint can reveal or dispose wrappers created by the other.
Creates a Redacted<T> wrapper. Alias of createRedacted(value).
const secret = redact("value");Creates a Redacted<T> wrapper.
const secret = createRedacted("value");Returns the wrapped value. Throws TypeError if the wrapper is invalid or disposed.
const raw = revealRedacted(secret);Returns true for live wrappers created by this module. Disposed wrappers return false.
if (isRedacted(value)) {
revealRedacted(value);
}Removes the wrapper from the internal registry.
disposeRedacted(secret);Builds equality for redacted values from equality for the wrapped value.
import { createEqRedacted, redact, type Brand } from "@svene/redact";
type ApiKey = string & Brand<"ApiKey">;
const eqApiKey = createEqRedacted<ApiKey>((a, b) => a === b);
const a = redact("x" as ApiKey);
const b = redact("x" as ApiKey);
eqApiKey(a, b); // truetype Redacted<T> // opaque wrapper for T
type Brand<Name extends string | symbol> // type-only nominal marker
type Eq<T> = (self: T, that: T) => boolean
type Unredact<T> // extracts T from Redacted<T>| path | output |
|---|---|
String(secret) |
<redacted> |
`${secret}` |
<redacted> |
JSON.stringify(secret) |
"<redacted>" |
JSON.stringify({ secret }) |
{"secret":"<redacted>"} |
console.log(secret) in Node.js |
<redacted> |
util.inspect(secret) in Node.js |
<redacted> |
| Object spread | no secret |
Reflect.ownKeys(secret) |
[] |
This package does not protect against code that can reveal the value.
const secret = redact("sk_live_123");
revealRedacted(secret); // sk_live_123It also does not replace logger-level redaction. Keep redaction rules for authorization headers, cookies, tokens, database URLs, and request bodies.
console.dir() in Node.js bypasses custom inspect hooks. The raw value still is not stored on the wrapper, but Node controls the output shape.
MIT