Commit Graph

14 Commits

Author SHA1 Message Date
Oussama Knouz
7fb76e89f6 feat(anonymizer): strict anti-leak mode (default on), prefilter decoupling, boundary-aware substitution
Harden the core privacy guarantee:

- Add `strict` mode (default true): after detection, verify no mapped value
  survives as a whole token in the output (ignoring placeholders, whose context
  may legitimately echo a value like `B+`). Catches a model that redacts one
  mention of a value but leaves another in clear — which the placeholder/mapping
  bijection check missed. Fail-closed: throws AnonymizationError naming only the
  non-secret placeholder key, and runs on the final result so it is not swallowed
  into the regex fallback (which can't fix a name leak). Set strict:false to opt out.
- Add `prefilter` option (default true): decouple the PII pre-filter from the
  presence of a regex fallback. Set false to always consult the LLM while keeping
  the fallback for LLM failures (max recall + graceful degradation).
- Boundary-aware value substitution: applyKnown and the leak check now match
  values only as whole tokens (Unicode letter/digit boundaries, regex-escaped),
  so "Ann" no longer replaces inside "Anna" and "jean@x.ch" no longer matches
  inside "jean@x.church"; accented/non-Latin names preserved.
- deanonymize restores longest placeholder keys first (prefix-overlap defense).

Restructures anonymize/anonymizeChunks/anonymizeTurn to a single exit so the
leak check runs once on the final result. Behavior is unchanged for callers that
were already leak-free.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 18:00:06 +01:00
Mobiletic
9941a21dd6 chore: add prepare script so the package builds on git install
Enables consuming the package as a git dependency (e.g. from Gitea): npm runs
'prepare' on install, which builds dist/ (git-ignored).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 17:03:59 +01:00
Mobiletic
144e0c885a docs: add mapping tables to README examples + credit Infomaniak
Each example now shows the secret `mapping` (placeholder → real value) as a
table alongside the `legend`, and the intro states the results were produced
via Infomaniak's Swiss AI API (Gemma 4).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:57:29 +01:00
Mobiletic
41d8993137 docs: add legend tables to the two README examples
Each example now shows the shareable `legend` as a table (Abbreviation →
meaning), marking 🆕 the abbreviations the model coined for attributes absent
from the base prompt (ASSUR_ID, PERMIS, PASSPORT, RCC, BIO_ID, SANG, DOSSIER…).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:26:44 +01:00
Mobiletic
dcb0a3a448 docs: add examples/ (live demo + RESULTS) and slim the README examples
- examples/demo.mjs: live demo (5 single messages up to 5 people with rare
  auto-coined attributes + 5 escalating multi-turn conversations with
  long-range back-references); self-references the package via exports
- examples/RESULTS.md: its generated output (10 examples)
- examples/.env.example + examples/README.md; `npm run example` script
- README: replace the long examples section with two representative examples
  (dense 5-person message + 6-turn conversation) and a badge/link to
  examples/RESULTS.md for the full set
- eslint ignores examples/**, prettier ignores generated examples/RESULTS.md,
  .gitignore un-ignores .env.example

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 15:22:00 +01:00
Mobiletic
d119c366b7 test: add 5 multi-turn conversation examples
Scripted, deterministic conversations (fictional Swiss e-learning chats) via a
mock provider, each asserting exact per-turn round-trips + stable entity ids:
login support (attributes attach to PER_1), two learners (applyKnown reuse of a
re-mentioned name), old/new IBAN kept distinct, parent+minor+doctor (3 people),
and HR (manager+org reused, two employees). 46 tests total.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:36:36 +01:00
Mobiletic
5ed8001101 feat: multi-turn conversation support (0.4.0)
anonymizeTurn(text, session) threads a serializable AnonymizerSession
({mapping, legend, history}) so one entity keeps one id across a whole chat
(applyKnown reuse + usedIds + de-collision merge). Adds conversation()
in-memory wrapper and an optional LlmProvider.anonymizeInConversation(text, ctx)
for rich cross-turn context; providers without it fall back to the batch path.

openAICompatibleProvider gains includeMappingInContext (default false — only
send real values to a trusted anonymizer endpoint) + historyMaxTurns (default
10). Verified live vs Gemma 4: Nora stays PER_1 across 4 turns (name/email/AVS/
IBAN), Yanis = PER_2; 41 tests, 98% coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 14:26:41 +01:00
Mobiletic
bc34d9470e docs: add live Gemma-4 e-learning examples to README
Summary table of 5 escalating cases + two inline worked examples (baseline
signup, HR multi-person coreference) + collapsible details (login username
coinage, two-IBAN distinction, minor+health dense coreference). Real
(fictional-data) output, all 5/5 round-trips.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 11:03:00 +01:00
Mobiletic
2584bd8c95 feat: prompt rules for lossless round-trips (0.3.1)
Two coherence rules added to DEFAULT_SYSTEM_PROMPT:
- different values of the same type get distinct placeholders (context),
  e.g. old/new IBAN -> [PER_1.IBAN:Ancien] / [PER_1.IBAN:Nouveau]
- never absorb adjacent punctuation/separators into a placeholder

Took the live Gemma-4 (Infomaniak) e-learning demo from 4/5 to 5/5 exact
round-trips. 36 tests passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 09:58:56 +01:00
Mobiletic
f5c9f0fbd4 feat: make openAICompatibleProvider work with Infomaniak out of the box (0.3.0)
- response_format is omitted by default (Infomaniak rejects the legacy
  json_object → HTTP 422); opt in via the new `responseFormat` option
  ({type:'json_object'} or a json_schema object). BREAKING for endpoints
  that relied on the forced json_object.
- parse LLM JSON leniently (tolerate markdown fences / surrounding prose)
- fold strict-coherence rules into DEFAULT_SYSTEM_PROMPT (exact
  placeholder<->mapping-key identity, values are originals, strict format,
  mask value not adjacent label) → reliable output across models

Verified live against Gemma 4 (google/gemma-4-31B-it, Infomaniak v2): all
demo phrases anonymize with clean round-trips, no custom provider needed.
36 tests passing.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 09:43:19 +01:00
Mobiletic
669794522e feat!: optional fail-closed fallback, legend output, richer prompt & docs
BREAKING CHANGE: regex fallback is now opt-in (patterns no longer defaults
to presets.swiss). With no fallback an LLM failure throws AnonymizationError
(fail-closed) and the pre-filter is bypassed; at least one of llm/patterns is
required. AnonymizationResult gains a required `legend`; anonymizeChunks seed
is now { mapping, legend? } and returns legend.

- prompt: model may coin new UPPERCASE abbreviations and returns a 'legende'
  explaining every abbreviation used (French); backfilled by DEFAULT_LEGEND
- PatternDef.meaning surfaces in the legend; swiss/generic presets get meanings
- AnonymizationError (exported) wraps the cause on fail-closed
- README: drop the chatbot provenance line; add 'How it works' + nLPD sections
- 34 tests / 99% coverage; bump to 0.2.0

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 15:28:04 +01:00
Mobiletic
08f84ae14a chore: point repo/badge URLs at git.mobiletic.net Gitea
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-24 17:09:07 +01:00
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
Mobiletic
f09b917f1a feat: initial release of @mobiletic/anonymizer
Framework-agnostic PII anonymization extracted from Mobiletic's chatbot.
Pluggable LLM detection + configurable regex fallback, deterministic
coreference, and streaming-safe de-anonymization.

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