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>
This commit is contained in:
30
README.md
30
README.md
@@ -1,5 +1,9 @@
|
||||
# @mobiletic/anonymizer
|
||||
|
||||
[](https://github.com/mobiletic/anonymizer/actions/workflows/ci.yml)
|
||||
[](https://www.npmjs.com/package/@mobiletic/anonymizer)
|
||||
[](./LICENSE)
|
||||
|
||||
Framework-agnostic **PII anonymization & pseudonymization** for TypeScript/JavaScript.
|
||||
|
||||
It replaces personal data in text with stable placeholders before the text leaves your trust boundary
|
||||
@@ -64,6 +68,11 @@ const { anon, mapping } = await anonymizer.anonymize('Le dossier de Alain Jaccar
|
||||
If the LLM call fails, times out, or returns an invalid shape, the anonymizer automatically falls back to
|
||||
the regex engine — it never throws on a provider failure.
|
||||
|
||||
`openAICompatibleProvider` retries **transient** failures (network error, timeout, HTTP 429/5xx) before
|
||||
giving up; 4xx and malformed responses are not retried. Tune with `timeoutMs` (per attempt, default 3000),
|
||||
`retries` (default 1 → 2 attempts), and `retryDelayMs` (linear backoff, default 250). Worst-case latency
|
||||
is `(retries + 1) × timeoutMs`, so keep `retries` low on latency-sensitive paths.
|
||||
|
||||
### Streaming de-anonymization
|
||||
|
||||
When you stream an LLM answer back to a user, restore real values without ever emitting a half-written
|
||||
@@ -102,7 +111,7 @@ new Anonymizer({
|
||||
```ts
|
||||
import { presets } from '@mobiletic/anonymizer';
|
||||
|
||||
presets.swiss; // AVS, IBAN CH, EMAIL, Swiss phone, DATE
|
||||
presets.swiss; // AVS, IBAN CH, EMAIL, Swiss phone, DATE
|
||||
presets.generic; // EMAIL, IBAN, credit card, IPv4, phone, DATE
|
||||
|
||||
// Compose / extend:
|
||||
@@ -112,6 +121,17 @@ const patterns = [
|
||||
];
|
||||
```
|
||||
|
||||
Each pattern may carry an optional `validate(match) => boolean` second stage — a match is only redacted
|
||||
if it passes. `presets.generic` uses it for a [Luhn](https://en.wikipedia.org/wiki/Luhn_algorithm) check
|
||||
so arbitrary long digit runs aren't mistaken for credit cards:
|
||||
|
||||
```ts
|
||||
{ tag: 'CREDIT_CARD', re: /\b\d(?:[ -]?\d){12,18}\b/g, validate: luhnValid }
|
||||
```
|
||||
|
||||
> The `generic` preset is a **best-effort** starting point — broad patterns (phone, date, card) can
|
||||
> overlap. For production use, prefer a locale-specific preset (`presets.swiss`) or your own patterns.
|
||||
|
||||
### Custom LLM provider
|
||||
|
||||
Implement `LlmProvider` to use any backend (Anthropic, a local model, a rules engine…):
|
||||
@@ -121,8 +141,12 @@ import type { LlmProvider } from '@mobiletic/anonymizer';
|
||||
|
||||
const myProvider: LlmProvider = {
|
||||
isConfigured: () => true,
|
||||
async anonymize(text) { /* return { anon, mapping } */ },
|
||||
async anonymizeBatch(texts, usedIds) { /* return { segments, mapping } */ },
|
||||
async anonymize(text) {
|
||||
/* return { anon, mapping } */
|
||||
},
|
||||
async anonymizeBatch(texts, usedIds) {
|
||||
/* return { segments, mapping } */
|
||||
},
|
||||
};
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user