npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@pii-mask/core

v0.1.0

Published

The masking engine at the heart of the `@pii-mask` toolkit. Ships 22 built-in PII detectors, six masking modes, a detector registry, and both structured-data and freeform-text masking — all with zero required runtime dependencies beyond `libphonenumber-js

Downloads

79

Readme

@pii-mask/core

The masking engine at the heart of the @pii-mask toolkit. Ships 22 built-in PII detectors, six masking modes, a detector registry, and both structured-data and freeform-text masking — all with zero required runtime dependencies beyond libphonenumber-js.

Installation

pnpm add @pii-mask/core

For substitute mode (fake data generation), also install the optional peer dependency:

pnpm add @faker-js/faker

Quick Start

import { createMasker } from '@pii-mask/core';

const masker = createMasker(); // defaults to 'mask' mode

const { result, detections } = masker.maskString('[email protected]');
// result:     "jo***[email protected]"
// detections: ["email"]

API Reference

createMasker(options?)

Factory function that returns a masker instance. Call it once, reuse the returned object.

import { createMasker } from '@pii-mask/core';

const masker = createMasker({
  mode: 'redact',         // 'mask' | 'redact' | 'pseudonymize' | 'anonymize' | 'tokenize' | 'substitute'
  only: ['email', 'ssn-us'],  // run only these detector IDs or category names
  disable: ['phone'],          // disable specific detector IDs
  extend: [myCustomDetector],  // add custom detectors to the registry
  keyNameOnly: false,          // skip value-level regex; rely on key-name heuristics only
});

masker.maskString(input, key?)

Mask a single string value. First attempts atomic detection (is the whole string PII?), then falls back to freeform text scanning using detector pattern regexes.

// Atomic value
masker.maskString('[email protected]');
// → { result: '[REDACTED]', detections: ['email'], tokenMap: {} }

// Freeform text with inline PII
masker.maskString('Contact [email protected] or call 555-0123');
// → { result: 'Contact [REDACTED] or call [REDACTED]', detections: ['email', 'phone'], tokenMap: {} }

Parameters:

  • input — The string to mask
  • key (optional) — Object key or column header, used by key-name heuristic detectors

Returns: MaskResult

  • result — The masked string
  • tokenMap — In tokenize mode, maps token → original for restoration
  • detections — Array of detector IDs that fired

masker.maskObject(input)

Recursively walk a plain object, masking all string values.

const { result, detections } = masker.maskObject({
  user: {
    email: '[email protected]',
    ssn: '123-45-6789',
    age: 30,
  },
});

const parsed = JSON.parse(result);
// parsed.user.email → '[REDACTED]'
// parsed.user.ssn   → '[REDACTED]'
// parsed.user.age   → 30  (non-string, untouched)

Parameters:

  • input — A Record<string, unknown> to walk

Returns: MaskResult (result is a JSON string)

masker.maskArray(input)

Recursively walk an array of objects, masking all string values.

const { result } = masker.maskArray([
  { email: '[email protected]' },
  { email: '[email protected]' },
]);

const parsed = JSON.parse(result);
// parsed[0].email → '[REDACTED]'
// parsed[1].email → '[REDACTED]'

masker.restore(masked, tokenMap)

Reverse tokenization. Only works when masking was done in tokenize mode.

const masker = createMasker({ mode: 'tokenize' });
const { result, tokenMap } = masker.maskString('[email protected]');
// result: '<<PII_a1b2c3d4>>'

const original = masker.restore(result, tokenMap);
// original: '[email protected]'

Masking Modes

mask (default)

Partial obscure that preserves the type shape.

const masker = createMasker({ mode: 'mask' });
masker.maskString('[email protected]');    // → 'jo***[email protected]'
masker.maskString('4111111111111111');     // → '****-****-****-1111'
masker.maskString('123-45-6789');          // → '***-**-6789'

redact

Full replacement. Every PII value becomes [REDACTED].

const masker = createMasker({ mode: 'redact' });
masker.maskString('[email protected]');    // → '[REDACTED]'

pseudonymize / anonymize

Replaces PII with consistent labels per session. The same value always maps to the same label within a single maskObject/maskArray call.

const masker = createMasker({ mode: 'pseudonymize' });
masker.maskObject({
  primary: '[email protected]',
  backup: '[email protected]',
  confirm: '[email protected]',
});
// primary → 'EMAIL_1', backup → 'EMAIL_2', confirm → 'EMAIL_1'

tokenize

Generates cryptographically random tokens. Reversible via the returned token map.

const masker = createMasker({ mode: 'tokenize' });
const { result, tokenMap } = masker.maskString('[email protected]');
// result: '<<PII_a1b2c3d4>>'

masker.restore(result, tokenMap);
// → '[email protected]'

Tokens are generated using node:crypto randomBytes — never Math.random().

substitute

Generates plausible fake values using @faker-js/faker. Requires @faker-js/faker as a peer dependency.

const masker = createMasker({ mode: 'substitute' });
masker.maskString('[email protected]');    // → '[email protected]' (random)
masker.maskString('123-45-6789');          // → '987-65-4321' (random)

Filtering Detectors

Run only specific detectors

// By detector ID
const masker = createMasker({ only: ['email', 'ssn-us'] });

// By category — runs all detectors in that category
const masker = createMasker({ only: ['financial'] });

Disable specific detectors

const masker = createMasker({ disable: ['phone', 'dob'] });

Custom Detectors

Register additional detectors via the extend option:

import { createMasker, PIICategory, MaskMode } from '@pii-mask/core';
import type { PIIDetector } from '@pii-mask/core';

const myDetector: PIIDetector = {
  id: 'custom-id',
  label: 'My Custom PII',
  category: PIICategory.GOV_ID,
  regions: ['XX'],

  detect(value, key) {
    return /^\d{10}$/.test(value);
  },

  mask(value, mode, ctx) {
    if (mode === MaskMode.REDACT) return '[REDACTED]';
    if (mode === MaskMode.TOKENIZE) return getOrCreateToken(value, ctx);
    if (mode === MaskMode.PSEUDONYMIZE || mode === MaskMode.ANONYMIZE) {
      return getOrCreateLabel('CUSTOM', value, ctx);
    }
    if (mode === MaskMode.SUBSTITUTE) {
      return ctx.faker?.string.numeric(10) ?? '0000000000';
    }
    return `******${value.slice(-4)}`;
  },

  // Optional: enables freeform text scanning (must have 'g' flag)
  pattern: /\b\d{10}\b/g,
};

const masker = createMasker({ extend: [myDetector] });

pattern property

When a detector has a pattern regex (with the g flag), maskString() can find and replace occurrences within freeform text. Without pattern, the detector only works on atomic field values (whole-string matches via detect()).

The detect() function is still called as a secondary validation gate on each pattern match, so detectors with checksums (like credit cards, CPF, Aadhaar) still validate correctly.

Registry

The singleton registry holds all registered detectors. Detectors self-register on import.

import { registry } from '@pii-mask/core';

// List all registered detectors
const all = registry.list();
// → [{ id: 'email', ... }, { id: 'phone', ... }, ...]

// Resolve with filtering
const filtered = registry.resolve({ only: ['email'], disable: [] });

Override behaviour

// Accidental double-registration — warns, then replaces
registry.register(myDetector);
registry.register(myDetector); // ⚠ console.warn

// Intentional override — silent
registry.register(myDetector, { override: true }); // ✓ silent

Built-in Detectors

| ID | Category | Regions | Description | |----|----------|---------|-------------| | email | contact | — | Email addresses | | phone | contact | — | Phone numbers (libphonenumber-js) | | ssn-us | gov-id | US | Social Security Numbers | | nin-ng | gov-id | NG | Nigerian NIN (11 digits) | | bvn-ng | gov-id | NG | Nigerian BVN (11 digits, key-gated) | | aadhaar-in | gov-id | IN | Aadhaar numbers (Verhoeff-validated) | | pan-in | gov-id | IN | PAN cards (AAAAA0000A) | | said-za | gov-id | ZA | South African ID (Luhn-validated) | | nin-uk | gov-id | GB | National Insurance Numbers | | nik-id | gov-id | ID | Indonesian NIK (16 digits) | | cpf-br | gov-id | BR | Brazilian CPF (checksum-validated) | | credit-card | financial | — | Credit/debit cards (Luhn-validated) | | iban | financial | — | IBANs (ISO 13616) | | ip-address | network | — | IPv4 addresses | | ipv6 | network | — | IPv6 addresses | | dob | biometric | — | Dates of birth | | person-name | identity | — | Person names (key-name heuristic) | | address | identity | — | Physical addresses (key-name heuristic) | | secret-key | secret | — | API keys, passwords (key-name heuristic) | | jwt | secret | — | JSON Web Tokens | | bcrypt-hash | secret | — | bcrypt hashes | | hex-secret | secret | — | Long hex strings (32+ chars) |

Secret-category detectors always return [REDACTED] regardless of masking mode.

Types

import type {
  PIIDetector,
  MaskContext,
  MaskResult,
  MaskOptions,
} from '@pii-mask/core';

import { PIICategory, MaskMode } from '@pii-mask/core';

// Categories: 'gov-id' | 'financial' | 'contact' | 'secret' | 'biometric' | 'network' | 'identity'
// Modes: 'mask' | 'redact' | 'pseudonymize' | 'anonymize' | 'tokenize' | 'substitute'

Lexicon Data

Regional name lexicons for identity detectors:

import '@pii-mask/core/lexicon/africa';
import '@pii-mask/core/lexicon/south-asia';
import '@pii-mask/core/lexicon/east-asia';

License

MIT