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

@lazymac/korean-pii-validate

v0.2.0

Published

Zero-dependency Korean PII checksum validator — RRN (주민등록번호), BRN (사업자등록번호), Korean mobile phone, masked-RRN. Pure JS, works in Node 18+, Bun, Deno, and modern browsers.

Downloads

283

Readme

@lazymac/korean-pii-validate

Zero-dependency Korean PII checksum validator. Catches 주민등록번호 (RRN), 사업자등록번호 (BRN), and Korean mobile phone numbers before you log them, send them to an LLM, paste them into a chat, or commit them to a public repo.

Pure JavaScript. Works in Node 18+ (CJS interop on Node 22+), Bun, Deno, and modern browsers. No telemetry, no network calls, ~6 KB unpacked, ~1 ms per 1000 validations.

npm license types

Why this exists

Naive regex-based PII scanners (/\d{6}-\d{7}/) hit obvious shapes — and false-positive constantly on order numbers, tracking codes, anonymized samples. The fix is not more regex: it's the official NTS / RRN checksum layered on top.

This package does the layered detection: shape match → checksum validate → return what you can mask vs. what you can leave alone.

Install

npm install @lazymac/korean-pii-validate

Usage

Validate a single value

import { validateRRN, validateBRN, validateKoreanPhone } from '@lazymac/korean-pii-validate';

validateRRN('901101-1234564');
// { valid: true, gender: 'M', foreign: false, birthYear: 1990, birthDate: '1990-11-01' }

validateBRN('220-81-62517');
// { valid: true, formatted: '220-81-62517' }

validateKoreanPhone('010-1234-5678');
// { valid: true, kind: 'mobile', legacy: false, prefix: '010', formatted: '010-1234-5678' }

Validate a masked RRN (개인정보보호법 §29)

Korean privacy law mandates that display systems hide everything past the century/gender flag — 901101-1****** instead of 901101-1234564. You usually can't recompute the checksum but you can still confirm the date + flag are sane before trusting the row.

import { validateMaskedRRN } from '@lazymac/korean-pii-validate';

validateMaskedRRN('901101-1******');
// { valid: true, masked: true, gender: 'M', foreign: false, birthYear: 1990, birthDate: '1990-11-01' }

validateMaskedRRN('901301-1******'); // bad month
// { valid: false, reason: 'invalid birth date: 1990-13-01' }

Accepts *, x/X, ?, , _ as mask characters.

Detect and redact across a document

import { detectAll, redactValid } from '@lazymac/korean-pii-validate';

const text = 'Customer 김철수 (901101-1234564), phone 010-1234-5678, vendor BRN 220-81-62517.';

detectAll(text);
// [
//   { kind: 'rrn',   start: 14, end: 28, raw: '901101-1234564', valid: true,  detail: {...} },
//   { kind: 'phone', start: 36, end: 49, raw: '010-1234-5678',  valid: true,  detail: {...} },
//   { kind: 'brn',   start: 62, end: 74, raw: '220-81-62517',   valid: true,  detail: {...} },
// ]

redactValid(text, '[REDACTED]');
// {
//   redacted: 'Customer 김철수 ([REDACTED]), phone [REDACTED], vendor BRN [REDACTED].',
//   matches:  [...]
// }

redactValid only masks checksum-valid matches. False positives (regex-shaped strings that don't pass the official checksum) are left intact — they're almost always order numbers, tracking IDs, or test fixtures.

CLI

# single value
$ npx korean-pii-validate "220-81-62517"
{ "valid": true, "formatted": "220-81-62517", "kind": "brn" }

# pipe a document, get JSON matches
$ cat customer_dump.txt | npx korean-pii-validate --scan
[ { "kind": "rrn", "start": 142, "end": 156, "raw": "...", "valid": true, ... } ]

Exit code 0 if valid, 1 if not. Easy to drop into pre-commit hooks.

What it validates

| Type | Format | Algorithm | Status | |---|---|---|---| | RRN (주민등록번호) | YYMMDD-CXXXXXC₂ | Weighted sum [2,3,4,5,6,7,8,9,2,3,4,5], (11 − sum%11) % 10 | ✅ checksum + date + century/gender flag | | BRN (사업자등록번호) | XXX-XX-XXXXX | NTS algorithm: weighted [1,3,7,1,3,7,1,3,5] + ⌊d₈·5/10⌋, (10 − sum%10) % 10 | ✅ checksum | | Korean mobile | 010/011/.../019-XXX(X)-XXXX | Shape only (no carrier checksum exists) | ✅ shape + prefix | | VOIP | 070-XXXX-XXXX | Shape only | ✅ classified separately |

What it does NOT do

  • No carrier liveness check010-1234-5678 can be syntactically valid but unallocated. Real activation status requires a paid KISA lookup.
  • No business-active check for BRN — checksum-valid does not mean the company is currently registered. Use the NTS homepage.html API for that (rate-limited, requires your own key).
  • No name-validation — there is no checksum on Korean names. We don't try.
  • No log / no upload / no analytics. This runs locally. Verify by opening DevTools → Network and watching nothing happen.

API reference

validateRRN(input) → RRNResult (discriminated union — narrows after if (r.valid))

validateMaskedRRN(input) → MaskedRRNResult

validateBRN(input) → BRNResult

validateKoreanPhone(input) → PhoneResult

findRRNCandidates(text) / findMaskedRRNCandidates(text) / findBRNCandidates(text) / findPhoneCandidates(text)

detectAll(text) → PIIMatch[] (sorted by position, non-overlapping)

redactValid(text, mask='•••••') → { redacted, matches }

Hyphenated and non-hyphenated forms are both accepted. validateRRN, validateBRN, validateKoreanPhone return { valid: false, reason: '...' } with a human-readable reason on rejection.

Tests

npm test    # 27 cases, zero deps

License

MIT © Daniel Choi