]> git.99rst.org Git - sgasser-llm-shield.git/commit
Add configurable masking denylist and regex whitelist (#101)
authorStefan Gasser <redacted>
Tue, 23 Jun 2026 06:04:47 +0000 (08:04 +0200)
committerGitHub <redacted>
Tue, 23 Jun 2026 06:04:47 +0000 (08:04 +0200)
commit4e0f56aab51ba42688096886bc6c9713f78de667
tree347eab1ed97547a1644bf704fdf0e46caa921305
parentecaabef150f63841ed0a975a429b7718994c6cf0
Add configurable masking denylist and regex whitelist (#101)

* Add configurable masking denylist

* Align whitelist pattern config

* Harden denylist/whitelist masking

- Merge denylist matches additively so they never shrink detector coverage
- Skip denylist matches inside existing placeholders to avoid corrupting secret/PII masks
- Anchor regex whitelist to the full entity so a partial match can't unmask larger PII
- Skip detection when PII detection is off and no denylist is configured
- Reject regex patterns that match the empty string at config load
- Reuse the conflict-resolver overlap helper; update docs and tests

* Fix streaming unmask when a placeholder delimiter splits across chunks

findPartialPlaceholderStart only buffered when the full "[[" delimiter appeared
within one chunk. If a stream chunk ended with a lone "[" (the first half of
"[["), it was emitted as safe, so the placeholder was never reassembled and
leaked to the client un-restored. Buffer a trailing partial of the start
delimiter too. Restores PII and secrets placeholders that the upstream model
tokenizes across the "[[" boundary.

* Remove ReDoS caveat from PII detection docs

* Simplify partial-placeholder detection and cover the closing-delimiter split

Replace the single-iteration loop in findPartialPlaceholderStart with a direct
trailing-bracket check, and add tests for a placeholder whose closing "]]" is
split across stream chunks.

* Exclude denylist matches by known placeholders; de-magic the match score

Replace the placeholder-shape regex heuristic with exact exclusion against the
real placeholders carried over from secrets masking: secretPlaceholders() is
threaded through detectPII/analyzeRequest and passed in /api/mask, so a denylist
pattern can no longer match (and corrupt) the internals of an existing
secret/PII placeholder. Also replace the vestigial denylist match score 2 with a
named DENYLIST_MATCH_SCORE constant.

* Require knownPlaceholders on detectPII so routes can't skip placeholder protection

* Clarify whitelist/denylist docs and drop Claude Code wording from the default
20 files changed:
config.example.yaml
docs/concepts/mask-mode.mdx
docs/configuration/pii-detection.mdx
src/config.test.ts
src/config.ts
src/masking/conflict-resolver.ts
src/masking/placeholders.test.ts
src/masking/placeholders.ts
src/pii/detect.test.ts
src/pii/detect.ts
src/pii/mask.test.ts
src/providers/anthropic/stream-transformer.test.ts
src/providers/openai/stream-transformer.test.ts
src/routes/anthropic.ts
src/routes/api.test.ts
src/routes/api.ts
src/routes/codex.ts
src/routes/openai.ts
src/services/pii.ts
src/services/secrets.ts
git clone https://git.99rst.org/PROJECT