Skip to content

pametan/pii-redact

Repository files navigation

pii-redact

Mask PANs, sort codes, account numbers, IBANs, emails, phone numbers, dates of birth, NI numbers and SSNs out of strings and log objects — before anything reaches your log sink.

TypeScript-first, zero runtime dependencies, with ready-made pino and winston adapters and a configurable masking style.

import { redact, redactString } from '@pametan/pii-redact';

redactString('charge 4111 1111 1111 1111 for a.b@example.com');
// 'charge [PAN] for [EMAIL]'

redact({ cardNumber: '4111111111111111', note: 'sort 12-34-56' });
// { cardNumber: '[PAN]', note: 'sort [SORT_CODE]' }

Why

Card numbers, account details and personal data leak into logs constantly — through error objects, request bodies and stringified payloads. This library strips them out at the boundary, so they never land in your log store, error tracker or analytics pipeline. It works two ways at once: it scans string content for known patterns, and it redacts object fields by key name (the exact, reliable path for structured logs).

Install

npm install @pametan/pii-redact

Requires Node 24+. Ships ESM with bundled type declarations.

Usage

Redact a value (string, object or array)

import { redact } from '@pametan/pii-redact';

redact({
  user: { email: 'a.b@example.com', dob: '1990-01-02' },
  payment: { cardNumber: '4111111111111111', cvv: '123', amount: 4200 },
});
// {
//   user: { email: '[EMAIL]', dob: '[DOB]' },
//   payment: { cardNumber: '[PAN]', cvv: '[CVV]', amount: 4200 },
// }

The input is never mutated, nesting and arrays are handled, and circular references become '[Circular]'.

Mask strategy

Choose how matched values are rewritten with the strategy option:

Strategy Result for a PAN Result for an email
token (default) [PAN] [EMAIL]
last4 ****1111 [REDACTED]
full [REDACTED] [REDACTED]
redactString('pan 4111 1111 1111 1111', { strategy: 'last4' }); // 'pan ****1111'

last4 keeps the trailing four digits of identifiers (PAN, account number, IBAN) and fully masks everything else. full uses redactedText (default [REDACTED]), which you can override.

Logger adapters

pino:

import pino from 'pino';
import { pinoOptions } from '@pametan/pii-redact';

const log = pino(pinoOptions());
log.info({ cardNumber: '4111111111111111' }, 'charged'); // cardNumber redacted

winston:

import winston from 'winston';
import { createWinstonFormat } from '@pametan/pii-redact';

const redactFormat = winston.format(createWinstonFormat());
const logger = winston.createLogger({
  format: winston.format.combine(redactFormat(), winston.format.json()),
  transports: [new winston.transports.Console()],
});

Custom detectors and keys

redact(payload, {
  extraDetectors: [{ name: 'order', label: 'ORDER', pattern: /ORD-\d+/g }],
  extraRedactKeys: ['customerRef', /token/i],
});

Use detectors / redactKeys to replace the built-in sets entirely, or the extra* variants to add to them. createRedactor(options) binds options once for hot paths.

What it detects

By value (string scanning): card PANs (Luhn-verified), IBANs, emails, US SSNs, UK sort codes (with separators), UK NI numbers, dates, and phone numbers.

By key name (exact, for objects): pan, cardNumber, cvv, sortCode, accountNumber, iban, email, phone, dob/dateOfBirth, nino, ssn, password, secret, token, authorization, and common variants (case-insensitive, separator-insensitive).

Best-effort vs exact. Pattern detection of loosely structured data (phone numbers, dates, space-grouped IBANs) is best-effort and can miss or over-match. Bare account numbers are intentionally not matched by value (no safe way to tell an 8-digit account from any other number) — they're caught by key name. For structured logs, prefer putting sensitive data in fields and relying on key-name redaction, which is exact.

API

Export Description
redact(value, options?) Deep-redact a string, object or array.
redactString(input, options?) Scan and mask a single string.
createRedactor(options?) Build a reusable redactor with options bound.
pinoOptions(options?) Options object to spread into pino().
createWinstonFormat(options?) Transform for winston.format.
DEFAULT_DETECTORS, DEFAULT_REDACT_KEYS, KEY_PROFILES The built-in rule sets.

Types Detector, RedactOptions and MaskStrategy are exported.

Handling sensitive data responsibly

This library reduces the chance of PII reaching your logs, but it is a safety net, not a guarantee — verify your detectors against your own data, and don't rely on redaction alone to meet PCI DSS or data-protection obligations. The best mitigation is still to avoid collecting or logging sensitive data in the first place.

Development

npm install
npm run typecheck
npm test          # 18 tests: detectors, strategies, deep objects, adapters
npm run build     # emit dist/

Disclaimer

Provided as an engineering aid, not legal, security or compliance advice. MIT licensed — see LICENSE.


Need the production version of this?

We're Pametan — a specialist fintech/regtech engineering agency working across UK, US and Canadian rails (FCA · CFPB · FCAC). We build the regulated, audited, in-market version of tools like this: tamper-evident audit logging, PCI-scoped data handling, and the systems around them.

Talk to us →

About

Mask PANs, sort codes, account numbers, IBANs, emails, phones, DOBs, NI numbers and SSNs out of strings and log objects. Configurable strategy, pino & winston adapters. TypeScript, zero-dependency.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors