# for local dev (bun run dev against the docker-compose detector service).
detector_url: ${DETECTOR_URL:-http://localhost:5002}
+ # Timeout for each detector /analyze request in seconds.
+ # Increase this for very large messages; set to 0 to disable.
+ detector_timeout: ${DETECTOR_TIMEOUT:-30}
+
# Add regions only if you need national-format numbers; + numbers work globally.
phone_regions: []
# phone_regions: [US, GB, DE, IT, IN]
},
"pii_detection": {
"phone_regions": [],
+ "detector_timeout": 30,
"score_threshold": 0.7,
"entities": ["PERSON", "EMAIL_ADDRESS", "PHONE_NUMBER"]
},
pii_detection:
detector_url: ${DETECTOR_URL:-http://localhost:5002}
+ detector_timeout: ${DETECTOR_TIMEOUT:-30}
```
```yaml
pii_detection:
detector_url: http://localhost:5002
+ detector_timeout: 30
phone_regions: []
score_threshold: 0.7
entities:
| Option | Default | Description |
|--------|---------|-------------|
| `detector_url` | `http://localhost:5002` | Detector `/analyze` URL |
+| `detector_timeout` | `30` | Timeout in seconds for each detector `/analyze` request. Increase for very large messages; set to `0` to disable |
| `phone_regions` | `[]` | Optional regions for national-format phone numbers |
| `score_threshold` | `0.7` | Minimum confidence floor for the neural labels PERSON and LOCATION (0.0-1.0). Checksum-validated identifiers always score `1.0` and are unaffected |
| `entities` | See below | Entity types to return |
| Variable | Default | Description |
|----------|---------|-------------|
| `DETECTOR_URL` | `http://localhost:5002` | Where the proxy reaches the detector. In the all-in-one image this is in-container and rarely changed; override it to point at an external detector. |
+| `DETECTOR_TIMEOUT` | `30` | Seconds to wait for each detector `/analyze` request when using the example config. Increase for very large messages; set to `0` to disable. |
| `PASTEGUARD_STARTUP_TIMEOUT` | `180` | Seconds to wait for the detector to become ready at startup |
## Next Steps
}
});
+ test("defaults PII detector timeout to 30 seconds", () => {
+ const path = writeConfig(`
+mode: mask
+providers:
+ openai: {}
+ anthropic: {}
+pii_detection:
+ detector_url: http://localhost:5002
+`);
+
+ try {
+ const config = loadConfig(path);
+
+ expect(config.pii_detection.detector_timeout).toBe(30);
+ } finally {
+ cleanupConfig(path);
+ }
+ });
+
+ test("accepts PII detector timeout override", () => {
+ const path = writeConfig(`
+mode: mask
+providers:
+ openai: {}
+ anthropic: {}
+pii_detection:
+ detector_url: http://localhost:5002
+ detector_timeout: \${DETECTOR_TIMEOUT:-300}
+`);
+
+ try {
+ const config = loadConfig(path);
+
+ expect(config.pii_detection.detector_timeout).toBe(300);
+ } finally {
+ cleanupConfig(path);
+ }
+ });
+
test("defaults request logging to SQLite", () => {
const path = writeConfig(`
mode: mask
const PIIDetectionSchema = z.object({
enabled: z.boolean().default(true),
detector_url: z.string().url(),
+ detector_timeout: z.coerce.number().int().min(0).default(30),
phone_regions: PhoneRegionsSchema,
score_threshold: z.coerce.number().min(0).max(1).default(0.7),
entities: z
pii_detection: {
enabled: true,
detector_url: "http://localhost:8080",
+ detector_timeout: 30,
phone_regions: [],
score_threshold: 0.7,
entities: ["EMAIL_ADDRESS"],
});
describe("detectPII", () => {
+ test("uses the configured detector timeout", async () => {
+ const config = getConfig();
+ const previousTimeout = config.pii_detection.detector_timeout;
+ const originalTimeout = AbortSignal.timeout;
+ let timeoutMs: number | undefined;
+ config.pii_detection.detector_timeout = 300;
+ AbortSignal.timeout = mock((ms: number) => {
+ timeoutMs = ms;
+ return undefined as unknown as AbortSignal;
+ }) as unknown as typeof AbortSignal.timeout;
+ mockDetector({});
+
+ try {
+ const detector = new PIIDetector();
+ await detector.detectPII("Hello world");
+
+ expect(timeoutMs).toBe(300_000);
+ } finally {
+ config.pii_detection.detector_timeout = previousTimeout;
+ AbortSignal.timeout = originalTimeout;
+ }
+ });
+
+ test("disables detector request timeout when configured as 0", async () => {
+ const config = getConfig();
+ const previousTimeout = config.pii_detection.detector_timeout;
+ let signal: AbortSignal | null | undefined;
+ config.pii_detection.detector_timeout = 0;
+
+ globalThis.fetch = mock(async (url: string | URL | Request, init?: RequestInit) => {
+ if (url.toString().includes("/analyze")) {
+ signal = init?.signal;
+ return new Response(JSON.stringify([]), {
+ status: 200,
+ headers: { "Content-Type": "application/json" },
+ });
+ }
+
+ return originalFetch(url, init);
+ }) as unknown as typeof fetch;
+
+ try {
+ const detector = new PIIDetector();
+ await detector.detectPII("Hello world");
+
+ expect(signal).toBeUndefined();
+ } finally {
+ config.pii_detection.detector_timeout = previousTimeout;
+ }
+ });
+
test("returns entities from the detector", async () => {
mockDetector({
"test@example.com": [{ entity_type: "EMAIL_ADDRESS", start: 0, end: 16, score: 0.99 }],
export class PIIDetector {
private detectorUrl: string;
+ private detectorTimeoutMs: number;
private scoreThreshold: number;
private entityTypes: string[];
private phoneRegions: string[];
constructor() {
const config = getConfig();
this.detectorUrl = config.pii_detection.detector_url;
+ this.detectorTimeoutMs = config.pii_detection.detector_timeout * 1000;
this.scoreThreshold = config.pii_detection.score_threshold;
this.entityTypes = config.pii_detection.entities;
this.phoneRegions = config.pii_detection.phone_regions;
"Content-Type": "application/json",
},
body: JSON.stringify(request),
- signal: AbortSignal.timeout(30_000),
+ signal:
+ this.detectorTimeoutMs > 0 ? AbortSignal.timeout(this.detectorTimeoutMs) : undefined,
});
if (!response.ok) {
((body.providers as Record<string, { base_url: string }>).codex.base_url as string).length,
).toBeGreaterThan(0);
expect(body.pii_detection).toBeDefined();
+ expect((body.pii_detection as Record<string, unknown>).detector_timeout).toBeDefined();
});
test("includes secrets_detection and logging sections", async () => {
providers,
pii_detection: {
phone_regions: config.pii_detection.phone_regions,
+ detector_timeout: config.pii_detection.detector_timeout,
score_threshold: config.pii_detection.score_threshold,
entities: config.pii_detection.entities,
},