diff --git a/extension/src/rules/__tests__/prompt-injection-redact.test.ts b/extension/src/rules/__tests__/prompt-injection-redact.test.ts index 14b5220..0b12ac6 100644 --- a/extension/src/rules/__tests__/prompt-injection-redact.test.ts +++ b/extension/src/rules/__tests__/prompt-injection-redact.test.ts @@ -113,6 +113,33 @@ describe("prompt-injection-redact", () => { expect(document.querySelector(`.${PLACEHOLDER_CLASS}`)).not.toBeNull(); }); + it("does not redact the surrounding article when injection text lives inside an SVG", () => { + // svg-text-strip is the rule that scrubs SVG title/desc/text content. + // prompt-injection-redact must not fire on those text nodes — the SVG + // has no paragraph-like ancestor, so it would otherwise escalate the + // placeholder all the way up to the wrapping
and wipe out + // the whole section (#133). + document.body.innerHTML = ` +
+

Product title that stays visible.

+
+ + Codestin Search App + ${FIXTURES.OVERRIDE_GUARDRAILS} + ${FIXTURES.DISREGARD} + +
+
+ `; + promptInjectionRedactRule.apply(document.body); + + expect(document.querySelector("article")).not.toBeNull(); + expect(document.querySelector("h1")?.textContent).toBe( + "Product title that stays visible.", + ); + expect(document.querySelector(`.${PLACEHOLDER_CLASS}`)).toBeNull(); + }); + it("does not process text inside SCRIPT or STYLE", () => { document.body.innerHTML = ` diff --git a/extension/src/rules/prompt-injection-redact.ts b/extension/src/rules/prompt-injection-redact.ts index 59572ef..63552fd 100644 --- a/extension/src/rules/prompt-injection-redact.ts +++ b/extension/src/rules/prompt-injection-redact.ts @@ -54,7 +54,16 @@ function findContainer(textNode: Text): HTMLElement | null { function apply(root: ParentNode): void { const containers = new Set(); - for (const node of walkTextNodes(root, { minLength: MIN_TEXT_LENGTH })) { + for (const node of walkTextNodes(root, { + minLength: MIN_TEXT_LENGTH, + // SVG /<desc>/<text> are the injection carriers inside SVG, and + // `svg-text-strip` handles them by blanking the text in place. Letting + // prompt-injection-redact also fire on those text nodes is destructive: + // the SVG has no p/li/td ancestor, so `findContainer` escalates all the + // way up to the surrounding <article>/<section> and the entire product + // header gets replaced with a single placeholder (#133). + shouldSkipParent: (parent) => parent.closest("svg") !== null, + })) { if (!containsInjection(node.nodeValue ?? "")) { continue; }