Reuse filterWhitelistedEntities and add combined masking test
authorStefan Gasser <redacted>
Wed, 21 Jan 2026 17:42:58 +0000 (18:42 +0100)
committerStefan Gasser <redacted>
Wed, 21 Jan 2026 17:42:58 +0000 (18:42 +0100)
- Import filterWhitelistedEntities from pii/detect instead of duplicating logic
- Add test for combined PII + secrets masking (default behavior)

src/routes/api.test.ts
src/routes/api.ts

index 0ad1117e3f3e8c7d38c910177555cd77a5cdfe42..6d6d7d0a6fd61b37670eb369cd15b8d1ed98261a 100644 (file)
@@ -1,6 +1,6 @@
 import { describe, expect, mock, test } from "bun:test";
 import { Hono } from "hono";
-import type { PIIEntity } from "../pii/detect";
+import { filterWhitelistedEntities, type PIIEntity } from "../pii/detect";
 
 // Mock the PII detector to avoid needing Presidio running
 const mockDetectPII = mock<(text: string, language: string) => Promise<PIIEntity[]>>(() =>
@@ -10,6 +10,7 @@ mock.module("../pii/detect", () => ({
   getPIIDetector: () => ({
     detectPII: mockDetectPII,
   }),
+  filterWhitelistedEntities,
 }));
 
 // Mock the logger to avoid database operations
@@ -185,4 +186,37 @@ describe("POST /api/mask", () => {
     expect(body.counters.PERSON).toBe(1);
     expect(body.counters.EMAIL_ADDRESS).toBe(1);
   });
+
+  test("masks both PII and secrets in single request (default behavior)", async () => {
+    mockDetectPII.mockResolvedValueOnce([
+      { entity_type: "EMAIL_ADDRESS", start: 8, end: 24, score: 0.9 },
+    ]);
+
+    const res = await app.request("/api/mask", {
+      method: "POST",
+      headers: { "Content-Type": "application/json" },
+      body: JSON.stringify({
+        text: "Contact john@example.com with key -----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----",
+      }),
+    });
+
+    expect(res.status).toBe(200);
+    const body = (await res.json()) as {
+      masked: string;
+      context: Record<string, string>;
+      entities: { type: string; placeholder: string }[];
+    };
+
+    // Both PII and secrets should be masked
+    expect(body.masked).toContain("[[EMAIL_ADDRESS_1]]");
+    expect(body.masked).toContain("[[SECRET_MASKED_PEM_PRIVATE_KEY_1]]");
+
+    // Context should contain mappings for both
+    expect(body.context["[[EMAIL_ADDRESS_1]]"]).toBe("john@example.com");
+    expect(body.context["[[SECRET_MASKED_PEM_PRIVATE_KEY_1]]"]).toBeDefined();
+
+    // Entities should include both types
+    expect(body.entities.some((e) => e.type === "EMAIL_ADDRESS")).toBe(true);
+    expect(body.entities.some((e) => e.type === "PEM_PRIVATE_KEY")).toBe(true);
+  });
 });
index 79adbb045946150ff2c4ace2ce69bb6db2bdab61..46b382d7a55ff68ef34e335e469687bbf3fa35ba 100644 (file)
@@ -9,7 +9,7 @@ import { Hono } from "hono";
 import { z } from "zod";
 import { getConfig, type SecretsDetectionConfig } from "../config";
 import { createPlaceholderContext, type PlaceholderContext } from "../masking/context";
-import { getPIIDetector } from "../pii/detect";
+import { filterWhitelistedEntities, getPIIDetector } from "../pii/detect";
 import { mask as maskPII } from "../pii/mask";
 import { detectSecrets } from "../secrets/detect";
 import { maskSecrets } from "../secrets/mask";
@@ -139,13 +139,11 @@ apiRoutes.post("/mask", async (c) => {
       scanTimeMs = Date.now() - piiStartTime;
 
       // Apply whitelist filtering
-      const whitelist = config.masking.whitelist;
-      const filteredEntities = piiEntities.filter((entity) => {
-        const detectedText = maskedText.slice(entity.start, entity.end);
-        return !whitelist.some(
-          (pattern) => pattern.includes(detectedText) || detectedText.includes(pattern),
-        );
-      });
+      const filteredEntities = filterWhitelistedEntities(
+        maskedText,
+        piiEntities,
+        config.masking.whitelist,
+      );
 
       // Capture counters before masking to track new entities
       const countersBefore = { ...context.counters };
git clone https://git.99rst.org/PROJECT