PII detectors in detail
All nine embedded pii.* detectors — what each one covers and, at equal length, what it misses.
On this page
Nine embedded detectors, all embedded locality, all declaring input, output and stream.
Every one of them runs incrementally on the streaming lane. The
family-wide bounds —
per-field scanning, the 128 KiB refusal, the de-obfuscation ceilings, the stream ladder — apply to
all nine and are not repeated here.
pii.card — payment card PANs
Covers. 13–19 digit PANs that pass both the ISO/IEC 7812-1 Luhn check digit and a
published issuer range at a length that range issues (Visa, Mastercard including 2221–2720, Amex,
Discover, JCB, Diners, UnionPay, Maestro 16–19). Spaced or hyphenated in any grouping when nothing
else shares that run; an unbroken run whatever abuts it; and a 4-4-4-4 or 4-6-5 grouping
followed by at most one 1–3 digit group that ends the run — the expiry and trailing-CVV forms.
Not covered.
- Efficacy rests on 12 positives (11 of 12 caught, 0 false positives on 101 hard negatives and 0 on 42 clean controls). Below the quotable floor, so no decimal is offered.
- Anything that fails Luhn or falls outside the issuer allowlist — including a mistyped or truncated real PAN, which is missed rather than reported. A deliberate precision trade.
- The precision floor is about 3 in 100 per candidate. An arbitrary 16-digit run passes
Luhn+IIN 3.145% of the time (629/20000, measured). Read
basis: exactas a deterministic checksum passed — which is all it has ever meant — not as this is a card. - Non-canonical groupings sharing a run with another number.
4111 1111 1111 1111 12/25is caught;41111 111111 11111 12/25(5-6-5) is a miss. A spaced PAN followed by more than one short group (… 12 25 123) is given up on purpose — ending the tail at any non-digit would report a partial PAN as a complete one, which is worse than a miss. - Never spends a calibrated bound, on either lane: every rule is an exact match.
- The expiry, the CVV, the cardholder name and the billing address. It finds the PAN and nothing else about the card.
pii.ssn — US Social Security Numbers
Covers. The 3-2-4 shape separated by a hyphen or a space, group not 00 and serial not
0000; the same number obfuscated with zero-width or homoglyph characters; and a nine-digit run
recognized on the split-token projection when split across whitespace, a hyphenated line wrap or a
markup boundary.
Not covered.
- Efficacy rests on 10 positives (10 of 10, 0 false positives on both negative sets). Ten is below the floor; the harness refuses to render that as a decimal and neither does this page.
- US-only. No EU national identifiers, no Canadian SIN, no UK NINO.
pii.nationalidcovers a handful of other countries. - The SSA never-issued area rules (000, 666, 900–999) are deliberately NOT applied. A
structurally never-issued area is still reported, because this is a redaction gate:
over-inclusion on the area field costs a placeholder, under-inclusion costs the leak. The
group-
00and serial-0000rules are applied, and they refuse a blank-form template. - A bare nine-digit run on the primary view.
SSN 123456789written that way in prose is a miss — only the split-token projection recognizes an unseparated SSN. A precision choice: a nine-digit run is an order id far more often than an SSN. - ITINs, EINs, a nine-digit run inside a longer run, and the name or date of birth that would make an SSN identifying.
pii.email — email addresses
Covers. [email protected] where the domain has at least one dot and a 2–24 letter final label,
including plus-tagged locals and addresses inside RFC 5322 headers. A split-and-rejoined address
is reported with a lower raw score (0.70) than one typed intact (0.85).
Not covered. Efficacy rests on 12 positives (11 of 12). At-sign tokens with no dotted
domain (user@host, a CI runner name) are dropped — so a real address at a dotless intranet
domain is missed. Reserved and documentation TLDs (.invalid, .example, .test, .localhost,
.local, .internal, .lan, .corp, the bare .tld/.domain placeholders) are refused as
template text. Display names are excluded from the span. IDN/punycode domains and non-ASCII local
parts fold toward Latin and are then likely refused.
pii.ip — IP addresses
Covers. IPv4 as four dot-separated octets 0–255 with no leading zeros. IPv6 that carries real
structure: a :: marker, or exactly eight colon-separated hex groups.
Not covered.
- It cannot tell an IPv4 address from a four-part software version number, because they are the
same string.
1.24.3.7is reported as an address. Measured: 4 false positives on the 101 hard negatives — an FP rate of 0.0396, all four the version-quad case. That denominator clears the floor, so unlike its recall the figure is quotable as a rate. Its raw score is set to 0.60, below every other shape in the detector, so a binding can threshold above it deliberately. - Three-to-seven-group uncompressed IPv6 — a
11:04:22timestamp and a MAC address have that shape — and with it any genuinely truncated IPv6 fragment. CIDR suffixes, zone identifiers and ports sit outside the span. The IPv6 split-token pass is not run. - Whether an address is a person. An IP is PII in some jurisdictions and infrastructure metadata in others; the detector reports the shape and leaves the judgment to the binding.
pii.iban — international bank account numbers
Covers. The 79 country codes in this build’s length table (78 from the ISO 13616 registry plus
XK), unspaced or in the conventional 4-character grouping, any case, at that code’s exact length
and passing the ISO 7064 MOD-97-10 check.
Not covered. Efficacy rests on 6 positives (6 of 6). A mistyped or truncated real IBAN is
missed rather than reported. The length table is a hand-maintained allowlist, so a newly registered
country is a miss until it is added. BIC/SWIFT codes, sort codes, routing numbers and domestic
account numbers are not reported — BIC=COBADEFFXXX beside a detected IBAN is not flagged.
Separators other than a single space between groups are not matched. And mod-97 is a structural
check: a well-formed IBAN for a closed or never-issued account is reported exactly like a live one.
pii.phone — telephone numbers
Covers. E.164 international form only: a leading +, an assigned ITU-T country calling code,
8–15 total digits with at least five subscriber digits, with or without single spaces, hyphens or
dots.
Not covered. Efficacy rests on 6 positives (6 of 6). Any number without a leading + —
(202) 555-0142, 202-555-0142, 020 7946 0958 — is missed. That is a deliberate precision
scope: a bare ten-digit pattern fires on every ten-digit id column, and the clean controls this
build is measured against are CSV exports and JSON logs. Also missed: the parenthesised area-code
form even with a + (+1 (202) 555-0142 stops the match at the ( ), extensions, vanity
numbers, short codes, and the country the number belongs to.
pii.nationalid — six national-ID schemes
Covers. Spain DNI and NIE (control letter verified), France INSEE NIR (mod-97 key, Corsica 2A/2B, birth-month range), Brazil CPF (both mod-11 digits, repdigits refused), India Aadhaar (UIDAI Verhoeff, leading digit 2–9), Netherlands BSN (elfproef, repdigits and values under 10,000,000 excluded), and the UK NINO — shape only, because a NINO carries no check digit.
Not covered.
- Efficacy is unmeasured. The shared corpus contains no non-US national-ID positives and no national-ID hard negatives. The only evidence is unit fixtures built from each scheme’s published check-digit specification.
- Six countries out of roughly two hundred. Germany’s Steuer-ID, Italy’s Codice Fiscale, Poland’s PESEL, Sweden’s personnummer, Japan’s My Number, China’s resident identity number and every other national scheme are misses.
- The Canadian SIN and the South African ID number are deliberately absent. Both are bare digit runs gated by Luhn alone, and Luhn passes one arbitrary run in ten — that would report roughly one in every ten nine-digit order numbers as a national identifier. Excluded rather than shipped weak.
- The NINO’s correctness. No checksum exists to verify, so any string of that shape with an
issuable prefix is reported. It mints a
reportedconfidence, notexact, and the difference is visible on the row. - Passport, driving-license, health-service and other tax identifiers.
pii.crypto — cryptocurrency addresses
Covers. Legacy Bitcoin-family addresses (P2PKH 1…, P2SH 3…) whose base58check checksum
verifies over a 25-byte payload, and segwit addresses with a bc/tb/ltc human-readable part
whose BIP-173 bech32 or BIP-350 bech32m checksum verifies, with the witness-version-to-variant rule
enforced.
Not covered.
- Efficacy is unmeasured — no crypto positives and no crypto hard negatives in the shared corpus. Its only evidence is unit fixtures over published BIP-173 and Bitcoin Core test vectors.
- Ethereum and every other
0x-prefixed 20-byte address.0xplus 40 hex is also every transaction-hash prefix in a blockchain log; the only thing separating them is the EIP-55 mixed-case checksum, which is defined over Keccak-256 — not in the Go standard library, and this build admits no dependency for it. A real gap, not a scoping preference. - Monero, Ripple, Cardano, Solana, Tron and every chain whose format is neither base58check-over-25-bytes nor bech32 with those prefixes; extended public keys, BIP-39 seed phrases and WIF private keys; and whether an address belongs to a person, an exchange or a contract.
- The split-token pass is not run: re-joining fragments would change the bytes the checksum covers.
pii.mac — MAC addresses
Covers. Six two-hex-digit octets separated consistently by : or -, excluding the all-zero
and broadcast addresses.
Also not covered. Its efficacy is otherwise unmeasured (no MAC positives in the corpus). The
dotted Cisco notation (0000.1b44.113a), EUI-64 and 20-octet InfiniBand forms. A MAC split across
whitespace is a deliberate miss — twelve hex digits have no checksum and no self-anchoring
structure, so a re-joined coincidence would pass every gate the rule has. And whether the address
identifies a person, a randomized privacy MAC, or a router in a datacenter.