marker_text: "[protected]"
# Text patterns that are never masked (protects against false positives)
- # whitelist:
+ # allowlist:
# - pattern: "Company Name Inc."
# - pattern: 'TEST-\d+'
# regex: true
masking:
show_markers: false
marker_text: "[protected]"
- whitelist:
+ allowlist:
- pattern: "Company Name Inc."
- pattern: 'TEST-\d+'
regex: true
|--------|---------|-------------|
| `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
(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+'
}
});
- 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
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 },
}
});
- 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:
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 },
];
}
}
-const WhitelistPatternSchema = z.union([
+const AllowlistPatternSchema = z.union([
z
.string()
.min(1)
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");
}),
]);
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([]),
});
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>;
import { openaiExtractor } from "../masking/extractors/openai";
import type { OpenAIMessage, OpenAIRequest } from "../providers/openai/types";
import {
- filterWhitelistedEntities,
+ filterAllowlistedEntities,
findDenylistedEntities,
mergeDenylistEntities,
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);
});
-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";
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);
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(
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);
}),
);
const defaultConfig: MaskingConfig = {
show_markers: false,
marker_text: "[protected]",
- whitelist: [],
+ allowlist: [],
denylist: [],
};
const configWithMarkers: MaskingConfig = {
show_markers: true,
marker_text: "[protected]",
- whitelist: [],
+ allowlist: [],
denylist: [],
};
const defaultConfig: MaskingConfig = {
show_markers: false,
marker_text: "[protected]",
- whitelist: [],
+ allowlist: [],
denylist: [],
};
const defaultConfig: MaskingConfig = {
show_markers: false,
marker_text: "[protected]",
- whitelist: [],
+ allowlist: [],
denylist: [],
};
import { describe, expect, mock, test } from "bun:test";
import { Hono } from "hono";
import {
- filterWhitelistedEntities,
+ filterAllowlistedEntities,
findDenylistedEntities,
mergeDenylistEntities,
type PIIEntity,
detectPII: mockDetectPII,
healthCheck: mock(() => Promise.resolve(true)),
}),
- filterWhitelistedEntities,
+ filterAllowlistedEntities,
findDenylistedEntities,
mergeDenylistEntities,
}));
import { getConfig, type SecretsDetectionConfig } from "../config";
import { createPlaceholderContext, type PlaceholderContext } from "../masking/context";
import {
- filterWhitelistedEntities,
+ filterAllowlistedEntities,
findDenylistedEntities,
getPIIDetector,
mergeDenylistEntities,
: [];
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,
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({
mock.module("../pii/detect", () => ({
PIIDetector,
- filterWhitelistedEntities,
+ filterAllowlistedEntities,
getPIIDetector: () => ({
analyzeRequest: mockAnalyzeRequest,
detectPII: mock(() => Promise.resolve([])),