Files
anonymizer/src/presets.ts
Mobiletic 81b6b03239 feat: harden generic preset, add LLM retry, and finish tooling/docs
- PatternDef.validate + Luhn-gated credit-card detection; de-overlap the
  generic phone/date/IP patterns; presets.swiss unchanged (production behavior)
- strip g/y flags from nameHint so .test() is stateless (latent footgun)
- openAICompatibleProvider: bounded retry on transient failures (network /
  timeout / 429 / 5xx), configurable via retries + retryDelayMs
- eslint + prettier + vitest coverage (97%); CI runs lint/format/coverage
- docs: README badges + new-option docs, SECURITY.md, issue/PR templates

26 tests passing; build emits ESM+CJS+types.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 17:03:20 +01:00

56 lines
2.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import type { PatternDef } from './types.js';
/**
* Swiss preset — the structured identifiers most relevant to the Swiss nLPD.
* Order matters: specific patterns (AVS, IBAN) come before generic ones.
*/
const swiss: PatternDef[] = [
{ tag: 'AVS', re: /\b756\.\d{4}\.\d{4}\.\d{2}\b/g }, // Swiss social security (AVS/AHV)
{ tag: 'IBAN', re: /\bCH\d{2}[0-9A-Z]{17}\b/gi }, // Swiss IBAN
{ tag: 'EMAIL', re: /\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g },
{ tag: 'TEL', re: /(?:\+41|0)(?:[\s.-]?\d){9}\b/g }, // Swiss phone number
{ tag: 'DATE', re: /\b\d{1,2}[./-]\d{1,2}[./-]\d{2,4}\b/g },
];
/** Luhn check on a credit-card candidate (ignores spaces/dashes); 1319 digits. */
function luhnValid(value: string): boolean {
const digits = value.replace(/\D/g, '');
if (digits.length < 13 || digits.length > 19) return false;
let sum = 0;
let double = false;
for (let i = digits.length - 1; i >= 0; i--) {
let d = digits.charCodeAt(i) - 48; // '0' === 48
if (double) {
d *= 2;
if (d > 9) d -= 9;
}
sum += d;
double = !double;
}
return sum % 10 === 0;
}
/**
* Generic preset — locale-agnostic identifiers useful as a starting point
* anywhere. Best-effort: extend or compose with your own {@link PatternDef}s.
*
* Order matters (patterns run in sequence on the already-anonymized text):
* specific identifiers claim their matches before broader ones, and dates are
* claimed before phone numbers so they don't get mis-tagged as `TEL`.
*/
const generic: PatternDef[] = [
{ tag: 'EMAIL', re: /\b[\w.+-]+@[\w-]+\.[\w.-]+\b/g },
{ tag: 'IBAN', re: /\b[A-Z]{2}\d{2}[0-9A-Z]{11,30}\b/g },
{ tag: 'IPV4', re: /\b(?:\d{1,3}\.){3}\d{1,3}\b/g },
// 1319 digits in optional space/dash groups, gated by Luhn to avoid eating
// arbitrary long digit runs (account numbers, ids).
{ tag: 'CREDIT_CARD', re: /\b\d(?:[ -]?\d){12,18}\b/g, validate: luhnValid },
{ tag: 'DATE', re: /\b\d{1,2}[./-]\d{1,2}[./-]\d{2,4}\b/g },
// Requires grouped/separated digits (or a leading +country) so plain integers
// aren't mistaken for phone numbers.
{ tag: 'TEL', re: /(?:\+\d{1,3}[\s.-]?)?(?:\(?\d{2,4}\)?[\s.-]){2,3}\d{2,4}/g },
];
/** Built-in pattern sets for the regex fallback. */
export const presets = { swiss, generic };