]> git.99rst.org Git - sgasser-llm-shield.git/commitdiff
Rename masking whitelist config to allowlist (#104)
authorStefan Gasser <redacted>
Tue, 23 Jun 2026 06:34:57 +0000 (08:34 +0200)
committerGitHub <redacted>
Tue, 23 Jun 2026 06:34:57 +0000 (08:34 +0200)
Rename the masking whitelist config key and its associated schema,
types, and helper (filterAllowlistedEntities) to use allowlist
terminology. Updates docs and the example config to match.

13 files changed:
config.example.yaml
docs/concepts/mask-mode.mdx
docs/configuration/pii-detection.mdx
src/config.test.ts
src/config.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/api.test.ts
src/routes/api.ts
src/routes/codex.test.ts

index e4a8cdba463e45f3a1bdc76d7f0801a26421afa1..426f122234f24b57f348aa1b9a91d957fbfa9b9a 100644 (file)
@@ -51,7 +51,7 @@ masking:
   marker_text: "[protected]"
 
   # Text patterns that are never masked (protects against false positives)
-  # whitelist:
+  # allowlist:
   #   - pattern: "Company Name Inc."
   #   - pattern: 'TEST-\d+'
   #     regex: true
index 8558b5da04e71c451530eacccadbf2d3f75b2c7d..7524160ff8e91d0b9ce2d0b801ea6b9171f2f77b 100644 (file)
@@ -46,7 +46,7 @@ providers:
 masking:
   show_markers: false
   marker_text: "[protected]"
-  whitelist:
+  allowlist:
     - pattern: "Company Name Inc."
     - pattern: 'TEST-\d+'
       regex: true
@@ -62,7 +62,7 @@ masking:
 |--------|---------|-------------|
 | `show_markers` | `false` | Add visual markers around unmasked values |
 | `marker_text` | `[protected]` | Marker text if enabled |
-| `whitelist` | `[]` | Text patterns that are never masked; set `regex: true` for regex patterns |
+| `allowlist` | `[]` | Text patterns that are never masked; set `regex: true` for regex patterns |
 | `denylist` | `[]` | Text patterns that are always masked with the configured `type`; set `regex: true` for regex patterns |
 
 ## Response Headers
index 92cda1e82d9fae49d3ee1ccde7fd5e7e4db4e8d7..11e9dbc556daab6cd491f08811e8ccd4f677e5d8 100644 (file)
@@ -85,13 +85,13 @@ environment variables on the detector service — e.g. `DETECTOR_FLOOR_PERSON=0.
 (fewer person false positives) or `DETECTOR_FLOOR_LOCATION=0.4` (more location
 recall). Street addresses are detected by the model and reported as `LOCATION`.
 
-## Whitelist
+## Allowlist
 
 Exclude specific text patterns from PII masking. Useful for preventing false positives on company names or product identifiers.
 
 ```yaml
 masking:
-  whitelist:
+  allowlist:
     - pattern: "Acme Corp"
     - pattern: "Product XYZ"
     - pattern: 'TEST-\d+'
index 1b62691f81a66aec1a76cccef37b2650072c5d73..cd83f5460f5ed6a086daff62e94bfd00d88114d8 100644 (file)
@@ -56,14 +56,14 @@ pii_detection:
     }
   });
 
-  test("accepts masking whitelist and denylist patterns", () => {
+  test("accepts masking allowlist and denylist patterns", () => {
     const path = writeConfig(`
 mode: mask
 providers:
   openai: {}
   anthropic: {}
 masking:
-  whitelist:
+  allowlist:
     - "Acme Corp"
     - pattern: 'TEST-\\d+'
       regex: true
@@ -80,7 +80,7 @@ pii_detection:
     try {
       const config = loadConfig(path);
 
-      expect(config.masking.whitelist).toEqual([
+      expect(config.masking.allowlist).toEqual([
         { pattern: "You are Claude Code, Anthropic's official CLI for Claude.", regex: false },
         { pattern: "Acme Corp", regex: false },
         { pattern: "TEST-\\d+", regex: true },
@@ -94,14 +94,14 @@ pii_detection:
     }
   });
 
-  test("rejects invalid masking whitelist regex patterns", () => {
+  test("rejects invalid masking allowlist regex patterns", () => {
     const path = writeConfig(`
 mode: mask
 providers:
   openai: {}
   anthropic: {}
 masking:
-  whitelist:
+  allowlist:
     - pattern: "[Acme"
       regex: true
 pii_detection:
index 60bf1c4868d4923e63d0a31dba8018d7cb6e6d0c..a2e6673301a9a6a735b0a97b206c765c0e49bd81 100644 (file)
@@ -30,7 +30,7 @@ const CodexProviderSchema = z.object({
   base_url: z.string().url().default("https://chatgpt.com/backend-api/codex"),
 });
 
-const DEFAULT_WHITELIST = [
+const DEFAULT_ALLOWLIST = [
   { pattern: "You are Claude Code, Anthropic's official CLI for Claude.", regex: false },
 ];
 
@@ -64,7 +64,7 @@ function validateRegexPattern(
   }
 }
 
-const WhitelistPatternSchema = z.union([
+const AllowlistPatternSchema = z.union([
   z
     .string()
     .min(1)
@@ -75,7 +75,7 @@ const WhitelistPatternSchema = z.union([
       regex: z.boolean().default(false),
     })
     .superRefine((entry, ctx) => {
-      validateRegexPattern(entry.pattern, entry.regex, ctx, "Invalid whitelist regex pattern");
+      validateRegexPattern(entry.pattern, entry.regex, ctx, "Invalid allowlist regex pattern");
     }),
 ]);
 
@@ -92,10 +92,10 @@ const DenylistPatternSchema = z
 const MaskingSchema = z.object({
   show_markers: z.boolean().default(false),
   marker_text: z.string().default("[protected]"),
-  whitelist: z
-    .array(WhitelistPatternSchema)
+  allowlist: z
+    .array(AllowlistPatternSchema)
     .default([])
-    .transform((arr) => [...DEFAULT_WHITELIST, ...arr]),
+    .transform((arr) => [...DEFAULT_ALLOWLIST, ...arr]),
   denylist: z.array(DenylistPatternSchema).default([]),
 });
 
@@ -228,7 +228,7 @@ export type AnthropicProviderConfig = z.infer<typeof AnthropicProviderSchema>;
 export type CodexProviderConfig = z.infer<typeof CodexProviderSchema>;
 export type LocalProviderConfig = z.infer<typeof LocalProviderSchema>;
 export type MaskingConfig = z.infer<typeof MaskingSchema>;
-export type WhitelistPattern = z.infer<typeof WhitelistPatternSchema>;
+export type AllowlistPattern = z.infer<typeof AllowlistPatternSchema>;
 export type DenylistPattern = z.infer<typeof DenylistPatternSchema>;
 export type SecretsDetectionConfig = z.infer<typeof SecretsDetectionSchema>;
 export type ServerConfig = z.infer<typeof ServerSchema>;
index 7fb6e4c6e207f5346840f4e19fb6476ecc24f525..cc65f59f0d33da7e123fc9597f9e1e9fdbc30939 100644 (file)
@@ -3,7 +3,7 @@ import { getConfig } from "../config";
 import { openaiExtractor } from "../masking/extractors/openai";
 import type { OpenAIMessage, OpenAIRequest } from "../providers/openai/types";
 import {
-  filterWhitelistedEntities,
+  filterAllowlistedEntities,
   findDenylistedEntities,
   mergeDenylistEntities,
   PIIDetector,
@@ -311,70 +311,70 @@ describe("PIIDetector", () => {
     });
   });
 
-  describe("filterWhitelistedEntities", () => {
-    test("filters entities matching whitelist pattern", () => {
+  describe("filterAllowlistedEntities", () => {
+    test("filters entities matching allowlist pattern", () => {
       const text = "You are Claude Code, Anthropic's official CLI for Claude.";
       const entities = [{ entity_type: "PERSON", start: 8, end: 14, score: 0.9 }];
-      const whitelist = [
+      const allowlist = [
         { pattern: "You are Claude Code, Anthropic's official CLI for Claude.", regex: false },
       ];
 
-      const result = filterWhitelistedEntities(text, entities, whitelist);
+      const result = filterAllowlistedEntities(text, entities, allowlist);
 
       expect(result).toHaveLength(0);
     });
 
-    test("keeps entities not in whitelist", () => {
+    test("keeps entities not in allowlist", () => {
       const text = "Contact John Doe at john@example.com";
       const entities = [
         { entity_type: "PERSON", start: 8, end: 16, score: 0.9 },
         { entity_type: "EMAIL_ADDRESS", start: 20, end: 36, score: 0.95 },
       ];
-      const whitelist = [{ pattern: "Claude", regex: false }];
+      const allowlist = [{ pattern: "Claude", regex: false }];
 
-      const result = filterWhitelistedEntities(text, entities, whitelist);
+      const result = filterAllowlistedEntities(text, entities, allowlist);
 
       expect(result).toHaveLength(2);
     });
 
-    test("filters when entity text is contained in whitelist pattern", () => {
+    test("filters when entity text is contained in allowlist pattern", () => {
       const text = "Hello Claude, how are you?";
       const entities = [{ entity_type: "PERSON", start: 6, end: 12, score: 0.85 }];
-      const whitelist = [{ pattern: "You are Claude Code", regex: false }];
+      const allowlist = [{ pattern: "You are Claude Code", regex: false }];
 
-      const result = filterWhitelistedEntities(text, entities, whitelist);
+      const result = filterAllowlistedEntities(text, entities, allowlist);
 
       expect(result).toHaveLength(0);
     });
 
-    test("returns all entities when whitelist is empty", () => {
+    test("returns all entities when allowlist is empty", () => {
       const text = "Contact Claude at claude@example.com";
       const entities = [
         { entity_type: "PERSON", start: 8, end: 14, score: 0.9 },
         { entity_type: "EMAIL_ADDRESS", start: 18, end: 36, score: 0.95 },
       ];
 
-      const result = filterWhitelistedEntities(text, entities, []);
+      const result = filterAllowlistedEntities(text, entities, []);
 
       expect(result).toHaveLength(2);
     });
 
-    test("filters entities matching regex whitelist pattern", () => {
+    test("filters entities matching regex allowlist pattern", () => {
       const text = "Reference TEST-1234 is public";
       const entities = [{ entity_type: "CUSTOMER_ID", start: 10, end: 19, score: 0.9 }];
-      const whitelist = [{ pattern: "TEST-\\d+", regex: true }];
+      const allowlist = [{ pattern: "TEST-\\d+", regex: true }];
 
-      const result = filterWhitelistedEntities(text, entities, whitelist);
+      const result = filterAllowlistedEntities(text, entities, allowlist);
 
       expect(result).toHaveLength(0);
     });
 
-    test("does not filter when a regex whitelist only partially matches the entity", () => {
+    test("does not filter when a regex allowlist only partially matches the entity", () => {
       const text = "card 1234567890123456 end";
       const entities = [{ entity_type: "CREDIT_CARD", start: 5, end: 21, score: 0.99 }];
-      const whitelist = [{ pattern: "\\d{4}", regex: true }];
+      const allowlist = [{ pattern: "\\d{4}", regex: true }];
 
-      const result = filterWhitelistedEntities(text, entities, whitelist);
+      const result = filterAllowlistedEntities(text, entities, allowlist);
 
       expect(result).toHaveLength(1);
     });
index ee4410edddd5dfafb4885fe48439a699fcb258c9..0183961a74beb3bd1fabf8fcf51d28a1f078e5f7 100644 (file)
@@ -1,4 +1,4 @@
-import { type DenylistPattern, getConfig, type WhitelistPattern } from "../config";
+import { type AllowlistPattern, type DenylistPattern, getConfig } from "../config";
 import { HEALTH_CHECK_TIMEOUT_MS } from "../constants/timeouts";
 import { overlaps, resolveConflicts } from "../masking/conflict-resolver";
 import type { RequestExtractor } from "../masking/types";
@@ -110,16 +110,16 @@ export function mergeDenylistEntities(detected: PIIEntity[], denylisted: PIIEnti
   return result.map((r) => r.e);
 }
 
-export function filterWhitelistedEntities(
+export function filterAllowlistedEntities(
   text: string,
   entities: PIIEntity[],
-  whitelist: WhitelistPattern[],
+  allowlist: AllowlistPattern[],
 ): PIIEntity[] {
-  if (whitelist.length === 0) return entities;
+  if (allowlist.length === 0) return entities;
 
   return entities.filter((entity) => {
     const detectedText = text.slice(entity.start, entity.end);
-    return !whitelist.some(({ pattern, regex }) => {
+    return !allowlist.some(({ pattern, regex }) => {
       if (regex) {
         // Anchor to the whole entity so a partial match can't un-mask a larger detected span.
         return new RegExp(`^(?:${pattern})$`).test(detectedText);
@@ -236,7 +236,7 @@ export class PIIDetector {
     const scanRoles = config.pii_detection.scan_roles
       ? new Set(config.pii_detection.scan_roles)
       : null;
-    const whitelist = config.masking.whitelist;
+    const allowlist = config.masking.allowlist;
     const denylist = config.masking.denylist;
 
     const spanEntities: PIIEntity[][] = await Promise.all(
@@ -251,7 +251,7 @@ export class PIIDetector {
         const detectedEntities = config.pii_detection.enabled
           ? await this.detectPII(span.text, langResult.language)
           : [];
-        const filteredEntities = filterWhitelistedEntities(span.text, detectedEntities, whitelist);
+        const filteredEntities = filterAllowlistedEntities(span.text, detectedEntities, allowlist);
         return mergeDenylistEntities(filteredEntities, denylistedEntities);
       }),
     );
index da7ceff1e1a70303f169d86b4a404ae601a5d953..ae717121e0e055c71d969a21a1cc71f6bf7f6f23 100644 (file)
@@ -17,14 +17,14 @@ import {
 const defaultConfig: MaskingConfig = {
   show_markers: false,
   marker_text: "[protected]",
-  whitelist: [],
+  allowlist: [],
   denylist: [],
 };
 
 const configWithMarkers: MaskingConfig = {
   show_markers: true,
   marker_text: "[protected]",
-  whitelist: [],
+  allowlist: [],
   denylist: [],
 };
 
index 28bfe163a14fdbaa3ac94936857176b0cf82c1af..37908e233e6abfbec7e0c129034db605e73c7db8 100644 (file)
@@ -6,7 +6,7 @@ import { createAnthropicUnmaskingStream } from "./stream-transformer";
 const defaultConfig: MaskingConfig = {
   show_markers: false,
   marker_text: "[protected]",
-  whitelist: [],
+  allowlist: [],
   denylist: [],
 };
 
index d4dcd7317146423d9289b3d04fe7935f2cd2ec17..2566fcca4bfc5589ff83afc097cd3c3a2e7238c7 100644 (file)
@@ -6,7 +6,7 @@ import { createUnmaskingStream } from "./stream-transformer";
 const defaultConfig: MaskingConfig = {
   show_markers: false,
   marker_text: "[protected]",
-  whitelist: [],
+  allowlist: [],
   denylist: [],
 };
 
index ffdc738386da124372e5a2841518f97416fce4ad..a0747faeca18c8fa8c420141bee8d5e40937df8b 100644 (file)
@@ -1,7 +1,7 @@
 import { describe, expect, mock, test } from "bun:test";
 import { Hono } from "hono";
 import {
-  filterWhitelistedEntities,
+  filterAllowlistedEntities,
   findDenylistedEntities,
   mergeDenylistEntities,
   type PIIEntity,
@@ -16,7 +16,7 @@ mock.module("../pii/detect", () => ({
     detectPII: mockDetectPII,
     healthCheck: mock(() => Promise.resolve(true)),
   }),
-  filterWhitelistedEntities,
+  filterAllowlistedEntities,
   findDenylistedEntities,
   mergeDenylistEntities,
 }));
index 8c00f336cfbc5bde60543ff224aebd12f26e97d1..0bd26d12c400295d02b9fce41ada641fd5e69cdb 100644 (file)
@@ -10,7 +10,7 @@ import { z } from "zod";
 import { getConfig, type SecretsDetectionConfig } from "../config";
 import { createPlaceholderContext, type PlaceholderContext } from "../masking/context";
 import {
-  filterWhitelistedEntities,
+  filterAllowlistedEntities,
   findDenylistedEntities,
   getPIIDetector,
   mergeDenylistEntities,
@@ -208,11 +208,10 @@ apiRoutes.post("/mask", async (c) => {
         : [];
       scanTimeMs = Date.now() - piiStartTime;
 
-      // Apply whitelist filtering
-      const filteredEntities = filterWhitelistedEntities(
+      const filteredEntities = filterAllowlistedEntities(
         maskedText,
         piiEntities,
-        config.masking.whitelist,
+        config.masking.allowlist,
       );
       const entitiesToMask = mergeDenylistEntities(
         filteredEntities,
index c92763ee1a0f6b69b7cca2d8c9ebdc2f6f7b1857..db8e3704ac871d6a4d9744e608d9d0f3e7f0f5ef 100644 (file)
@@ -1,7 +1,7 @@
 import { afterEach, describe, expect, mock, test } from "bun:test";
 import { Hono } from "hono";
 import { getConfig } from "../config";
-import { filterWhitelistedEntities, type PIIDetectionResult, PIIDetector } from "../pii/detect";
+import { filterAllowlistedEntities, type PIIDetectionResult, PIIDetector } from "../pii/detect";
 
 const mockAnalyzeRequest = mock<() => Promise<PIIDetectionResult>>(() =>
   Promise.resolve({
@@ -17,7 +17,7 @@ const mockLogRequest = mock(() => {});
 
 mock.module("../pii/detect", () => ({
   PIIDetector,
-  filterWhitelistedEntities,
+  filterAllowlistedEntities,
   getPIIDetector: () => ({
     analyzeRequest: mockAnalyzeRequest,
     detectPII: mock(() => Promise.resolve([])),
git clone https://git.99rst.org/PROJECT