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>
This commit is contained in:
25
CHANGELOG.md
25
CHANGELOG.md
@@ -4,6 +4,31 @@ All notable changes to this project are documented here. The format is based on
|
||||
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
||||
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.5.0] - Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
- **Strict anti-leak mode (on by default).** New `strict` option on `Anonymizer`. When enabled, the anonymized
|
||||
output is checked after detection to ensure no mapped value still appears as a whole token (ignoring
|
||||
placeholders — a placeholder's context field may legitimately echo a value, e.g. `B+` in `[PER_1.SANG:B+]`).
|
||||
This catches a model that redacts one mention of a value but leaves another in clear — a case the previous
|
||||
bidirectional validation (placeholder ⇄ mapping-key) did not detect. On a suspected leak it throws
|
||||
`AnonymizationError` naming only the non-secret placeholder key. Fail-closed: it runs on the final result
|
||||
and is **not** swallowed into the regex fallback (which can't fix a name leak). **Defaults to `true`** — set
|
||||
`strict: false` to restore the previous behaviour (or if a false positive rejects an otherwise-fine result).
|
||||
- **`prefilter` option** — decouples the cheap PII pre-filter (skip the LLM when no PII is heuristically
|
||||
detected) from the presence of a regex fallback. Default `true`; set `false` to always consult the LLM
|
||||
while still keeping the fallback for LLM failures (maximum recall with graceful degradation).
|
||||
|
||||
### Changed
|
||||
|
||||
- **Boundary-aware value substitution.** Known-value reuse (`applyKnown`, used by `anonymizeChunks` and
|
||||
`anonymizeTurn`) now matches values only as whole tokens (Unicode letter/digit boundaries) instead of raw
|
||||
substrings, so a short value like `"Ann"` is no longer replaced inside `"Anna"`, and `"jean@exemple.ch"`
|
||||
no longer matches inside `"jean@exemple.church"`. Accented and non-Latin names are preserved. The strict
|
||||
leak check uses the same boundary logic, so detection and substitution agree.
|
||||
- `deanonymize` now restores longest placeholder keys first (defensive against prefix overlaps).
|
||||
|
||||
## [0.4.0] - Unreleased
|
||||
|
||||
### Added
|
||||
|
||||
Reference in New Issue
Block a user