From: Stefan Gasser Date: Mon, 26 Jan 2026 07:15:59 +0000 (+0100) Subject: Remove SECRET_MASKED_ prefix from secrets placeholders X-Git-Url: http://git.99rst.org/?a=commitdiff_plain;h=10021645cc925bd37c1599450dde5eb4cef31660;p=sgasser-llm-shield.git Remove SECRET_MASKED_ prefix from secrets placeholders Simplify placeholder format by using [[TYPE_N]] for both PII and secrets. This makes the format consistent and shorter. Before: [[SECRET_MASKED_API_KEY_OPENAI_1]] After: [[API_KEY_OPENAI_1]] --- diff --git a/src/masking/placeholders.test.ts b/src/masking/placeholders.test.ts index 39d4bb9..d46dee2 100644 --- a/src/masking/placeholders.test.ts +++ b/src/masking/placeholders.test.ts @@ -23,7 +23,7 @@ describe("placeholder constants", () => { test("secret format uses correct delimiters", () => { expect(SECRET_PLACEHOLDER_FORMAT).toContain(PLACEHOLDER_DELIMITERS.start); expect(SECRET_PLACEHOLDER_FORMAT).toContain(PLACEHOLDER_DELIMITERS.end); - expect(SECRET_PLACEHOLDER_FORMAT).toBe("[[SECRET_MASKED_{N}]]"); + expect(SECRET_PLACEHOLDER_FORMAT).toBe("[[{N}]]"); }); }); @@ -42,12 +42,12 @@ describe("generatePlaceholder", () => { describe("generateSecretPlaceholder", () => { test("generates secret placeholder", () => { const result = generateSecretPlaceholder("API_KEY_OPENAI", 1); - expect(result).toBe("[[SECRET_MASKED_API_KEY_OPENAI_1]]"); + expect(result).toBe("[[API_KEY_OPENAI_1]]"); }); test("generates secret placeholder with different type and count", () => { const result = generateSecretPlaceholder("PEM_PRIVATE_KEY", 2); - expect(result).toBe("[[SECRET_MASKED_PEM_PRIVATE_KEY_2]]"); + expect(result).toBe("[[PEM_PRIVATE_KEY_2]]"); }); }); diff --git a/src/masking/placeholders.ts b/src/masking/placeholders.ts index 149c37d..66f38b2 100644 --- a/src/masking/placeholders.ts +++ b/src/masking/placeholders.ts @@ -10,8 +10,8 @@ export const PLACEHOLDER_DELIMITERS = { /** PII placeholder format: [[TYPE_N]] e.g. [[PERSON_1]], [[EMAIL_ADDRESS_2]] */ export const PII_PLACEHOLDER_FORMAT = "[[{TYPE}_{N}]]"; -/** Secrets placeholder format: [[SECRET_MASKED_TYPE_N]] e.g. [[SECRET_MASKED_API_KEY_OPENAI_1]] */ -export const SECRET_PLACEHOLDER_FORMAT = "[[SECRET_MASKED_{N}]]"; +/** Secrets placeholder format: [[TYPE_N]] e.g. [[API_KEY_OPENAI_1]] */ +export const SECRET_PLACEHOLDER_FORMAT = "[[{N}]]"; /** * Generates a placeholder string from the format diff --git a/src/routes/api.test.ts b/src/routes/api.test.ts index 589e241..2e07187 100644 --- a/src/routes/api.test.ts +++ b/src/routes/api.test.ts @@ -163,7 +163,7 @@ describe("POST /api/mask", () => { masked: string; entities: { type: string }[]; }; - expect(body.masked).toContain("[[SECRET_MASKED_PEM_PRIVATE_KEY_1]]"); + expect(body.masked).toContain("[[PEM_PRIVATE_KEY_1]]"); expect(body.entities.some((e) => e.type === "PEM_PRIVATE_KEY")).toBe(true); }); @@ -209,11 +209,11 @@ describe("POST /api/mask", () => { // Both PII and secrets should be masked expect(body.masked).toContain("[[EMAIL_ADDRESS_1]]"); - expect(body.masked).toContain("[[SECRET_MASKED_PEM_PRIVATE_KEY_1]]"); + expect(body.masked).toContain("[[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(); + expect(body.context["[[PEM_PRIVATE_KEY_1]]"]).toBeDefined(); // Entities should include both types expect(body.entities.some((e) => e.type === "EMAIL_ADDRESS")).toBe(true); diff --git a/src/routes/api.ts b/src/routes/api.ts index fe4205d..4c79863 100644 --- a/src/routes/api.ts +++ b/src/routes/api.ts @@ -50,7 +50,6 @@ interface MaskResponse { function extractEntities( countersBefore: Record, context: PlaceholderContext, - isSecret: boolean, ): MaskEntity[] { const entities: MaskEntity[] = []; @@ -58,8 +57,8 @@ function extractEntities( const startCount = countersBefore[type] || 0; // Add entities for each new placeholder created for (let i = startCount + 1; i <= count; i++) { - // Build placeholder directly using known format - const placeholder = isSecret ? `[[SECRET_MASKED_${type}_${i}]]` : `[[${type}_${i}]]`; + // Build placeholder directly using known format (same for PII and secrets) + const placeholder = `[[${type}_${i}]]`; if (context.mapping[placeholder]) { entities.push({ type, placeholder }); @@ -152,7 +151,7 @@ apiRoutes.post("/mask", async (c) => { const countersBefore = { ...context.counters }; const piiResult = maskPII(maskedText, filteredEntities, context); maskedText = piiResult.masked; - allEntities.push(...extractEntities(countersBefore, piiResult.context, false)); + allEntities.push(...extractEntities(countersBefore, piiResult.context)); // Collect unique entity types for logging for (const entity of filteredEntities) { @@ -206,7 +205,7 @@ apiRoutes.post("/mask", async (c) => { const countersBefore = { ...context.counters }; const secretsMaskResult = maskSecrets(maskedText, secretsResult.locations, context); maskedText = secretsMaskResult.masked; - allEntities.push(...extractEntities(countersBefore, secretsMaskResult.context, true)); + allEntities.push(...extractEntities(countersBefore, secretsMaskResult.context)); // Collect unique secret types for logging for (const match of secretsResult.matches) { diff --git a/src/secrets/mask.test.ts b/src/secrets/mask.test.ts index 2fc7e3d..3bd8564 100644 --- a/src/secrets/mask.test.ts +++ b/src/secrets/mask.test.ts @@ -21,14 +21,14 @@ function createRequest(messages: OpenAIMessage[]): OpenAIRequest { } describe("secrets placeholder format", () => { - test("uses [[SECRET_MASKED_TYPE_N]] format", () => { + test("uses [[TYPE_N]] format", () => { const text = `My API key is ${sampleSecret}`; const locations: SecretLocation[] = [ { start: 14, end: 14 + sampleSecret.length, type: "API_KEY_OPENAI" }, ]; const result = maskSecrets(text, locations); - expect(result.masked).toBe("My API key is [[SECRET_MASKED_API_KEY_OPENAI_1]]"); + expect(result.masked).toBe("My API key is [[API_KEY_OPENAI_1]]"); }); test("increments counter per secret type", () => { @@ -44,8 +44,8 @@ describe("secrets placeholder format", () => { ]; const result = maskSecrets(text, locations); - expect(result.masked).toContain("[[SECRET_MASKED_API_KEY_OPENAI_1]]"); - expect(result.masked).toContain("[[SECRET_MASKED_API_KEY_OPENAI_2]]"); + expect(result.masked).toContain("[[API_KEY_OPENAI_1]]"); + expect(result.masked).toContain("[[API_KEY_OPENAI_2]]"); }); test("tracks different secret types separately", () => { @@ -61,8 +61,8 @@ describe("secrets placeholder format", () => { ]; const result = maskSecrets(text, locations); - expect(result.masked).toContain("[[SECRET_MASKED_API_KEY_OPENAI_1]]"); - expect(result.masked).toContain("[[SECRET_MASKED_API_KEY_AWS_1]]"); + expect(result.masked).toContain("[[API_KEY_OPENAI_1]]"); + expect(result.masked).toContain("[[API_KEY_AWS_1]]"); }); }); @@ -80,7 +80,7 @@ describe("maskRequest with MessageSecretsResult", () => { const { masked, context } = maskRequest(request, detection, openaiExtractor); - expect(masked.messages[0].content).toContain("[[SECRET_MASKED_API_KEY_OPENAI_1]]"); + expect(masked.messages[0].content).toContain("[[API_KEY_OPENAI_1]]"); expect(masked.messages[0].content).not.toContain(sampleSecret); expect(masked.messages[1].content).toBe("I'll help you with that."); expect(Object.keys(context.mapping)).toHaveLength(1); @@ -98,8 +98,8 @@ describe("maskRequest with MessageSecretsResult", () => { const { masked, context } = maskRequest(request, detection, openaiExtractor); - expect(masked.messages[0].content).toBe("Key1: [[SECRET_MASKED_API_KEY_OPENAI_1]]"); - expect(masked.messages[1].content).toBe("Key2: [[SECRET_MASKED_API_KEY_OPENAI_1]]"); + expect(masked.messages[0].content).toBe("Key1: [[API_KEY_OPENAI_1]]"); + expect(masked.messages[1].content).toBe("Key2: [[API_KEY_OPENAI_1]]"); expect(Object.keys(context.mapping)).toHaveLength(1); }); @@ -121,29 +121,29 @@ describe("maskRequest with MessageSecretsResult", () => { const { masked } = maskRequest(request, detection, openaiExtractor); const content = masked.messages[0].content as Array<{ type: string; text?: string }>; - expect(content[0].text).toBe("Key: [[SECRET_MASKED_API_KEY_OPENAI_1]]"); + expect(content[0].text).toBe("Key: [[API_KEY_OPENAI_1]]"); expect(content[1].type).toBe("image_url"); }); }); describe("streaming with secrets placeholders", () => { - test("buffers partial [[SECRET_MASKED placeholder", () => { + test("buffers partial [[ placeholder", () => { const context = createSecretsMaskingContext(); - context.mapping["[[SECRET_MASKED_API_KEY_OPENAI_1]]"] = sampleSecret; + context.mapping["[[API_KEY_OPENAI_1]]"] = sampleSecret; - const { output, remainingBuffer } = unmaskSecretsStreamChunk("", "Key: [[SECRET_MAS", context); + const { output, remainingBuffer } = unmaskSecretsStreamChunk("", "Key: [[API_KEY", context); expect(output).toBe("Key: "); - expect(remainingBuffer).toBe("[[SECRET_MAS"); + expect(remainingBuffer).toBe("[[API_KEY"); }); test("completes buffered placeholder across chunks", () => { const context = createSecretsMaskingContext(); - context.mapping["[[SECRET_MASKED_API_KEY_OPENAI_1]]"] = sampleSecret; + context.mapping["[[API_KEY_OPENAI_1]]"] = sampleSecret; const { output, remainingBuffer } = unmaskSecretsStreamChunk( - "[[SECRET_MAS", - "KED_API_KEY_OPENAI_1]] done", + "[[API_KEY", + "_OPENAI_1]] done", context, ); @@ -153,8 +153,8 @@ describe("streaming with secrets placeholders", () => { test("flushes incomplete buffer as-is", () => { const context = createSecretsMaskingContext(); - const result = flushSecretsMaskingBuffer("[[SECRET_MAS", context); - expect(result).toBe("[[SECRET_MAS"); + const result = flushSecretsMaskingBuffer("[[API_KEY", context); + expect(result).toBe("[[API_KEY"); }); }); @@ -176,7 +176,7 @@ Please store them securely. const { masked, context } = maskSecrets(originalText, locations); expect(masked).not.toContain(sampleSecret); - expect(masked).toContain("[[SECRET_MASKED_API_KEY_OPENAI_1]]"); + expect(masked).toContain("[[API_KEY_OPENAI_1]]"); const restored = unmaskSecrets(masked, context); expect(restored).toBe(originalText); @@ -186,7 +186,7 @@ Please store them securely. describe("unmaskSecretsResponse", () => { test("unmasks all choices in response", () => { const context = createSecretsMaskingContext(); - context.mapping["[[SECRET_MASKED_API_KEY_OPENAI_1]]"] = sampleSecret; + context.mapping["[[API_KEY_OPENAI_1]]"] = sampleSecret; const response: OpenAIResponse = { id: "test", @@ -198,7 +198,7 @@ describe("unmaskSecretsResponse", () => { index: 0, message: { role: "assistant", - content: "Your key is [[SECRET_MASKED_API_KEY_OPENAI_1]]", + content: "Your key is [[API_KEY_OPENAI_1]]", }, finish_reason: "stop", }, @@ -253,9 +253,7 @@ describe("edge cases", () => { ]; const result = maskSecrets(text, locations); - expect(result.masked).toBe( - "Key1: [[SECRET_MASKED_API_KEY_OPENAI_1]] Key2: [[SECRET_MASKED_API_KEY_OPENAI_1]]", - ); + expect(result.masked).toBe("Key1: [[API_KEY_OPENAI_1]] Key2: [[API_KEY_OPENAI_1]]"); expect(Object.keys(result.context.mapping)).toHaveLength(1); }); @@ -275,7 +273,7 @@ describe("edge cases", () => { context, ); - expect(result2.masked).toBe("Another: [[SECRET_MASKED_API_KEY_OPENAI_2]]"); + expect(result2.masked).toBe("Another: [[API_KEY_OPENAI_2]]"); expect(Object.keys(context.mapping)).toHaveLength(2); }); });