From a75248f34b0be4c78c470d341d0c80b6524c4750 Mon Sep 17 00:00:00 2001
From: Goutam Adwant <8672451+goutamadwant@users.noreply.github.com>
Date: Sat, 12 Sep 2026 12:13:55 -0700
Subject: [PATCH] Preserve text gates in renamed literal elements
---
change_log.md | 4 ++++
...ndAttributePolicyBasedSanitizerPolicy.java | 20 +++++++++++++++++++
.../owasp/html/HtmlChangeReporterTest.java | 7 ++++---
.../org/owasp/html/HtmlPolicyBuilderTest.java | 14 +++++++++++++
4 files changed, 42 insertions(+), 3 deletions(-)
diff --git a/change_log.md b/change_log.md
index 9f8970f1..7aa027da 100644
--- a/change_log.md
+++ b/change_log.md
@@ -2,6 +2,10 @@
Most recent at top.
* Next release
+ * A nested element inside one renamed to a literal-content element no
+ longer opens its own text gate. Its text now follows the outer element's
+ gate, so it cannot reach `style` or similar content where text was not
+ allowed (#482).
* Docs: Add focused README files for each Maven module and an index for
supporting documentation, including safe build and regeneration steps.
* Table parts inside cell content now return to the existing table, and a
diff --git a/owasp-java-html-sanitizer/src/main/java/org/owasp/html/ElementAndAttributePolicyBasedSanitizerPolicy.java b/owasp-java-html-sanitizer/src/main/java/org/owasp/html/ElementAndAttributePolicyBasedSanitizerPolicy.java
index 7322263f..fd3238c2 100644
--- a/owasp-java-html-sanitizer/src/main/java/org/owasp/html/ElementAndAttributePolicyBasedSanitizerPolicy.java
+++ b/owasp-java-html-sanitizer/src/main/java/org/owasp/html/ElementAndAttributePolicyBasedSanitizerPolicy.java
@@ -85,6 +85,12 @@ class ElementAndAttributePolicyBasedSanitizerPolicy
* without counting them toward its nesting limit.
*/
transient boolean skipText = true;
+ /**
+ * True while an emitted element whose content the renderer writes literally
+ * is open. A nested start tag cannot be emitted in that context, so it must
+ * be treated as dropped before it can become a text container in the policy.
+ */
+ private boolean inKeptLiteralElement;
/**
* True while a kept element whose text the renderer emits unescaped, such
* as {@code ", result.html);
assertEquals(" ", result.log);
diff --git a/owasp-java-html-sanitizer/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java b/owasp-java-html-sanitizer/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java
index 5efa2281..3100a812 100644
--- a/owasp-java-html-sanitizer/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java
+++ b/owasp-java-html-sanitizer/src/test/java/org/owasp/html/HtmlPolicyBuilderTest.java
@@ -2059,6 +2059,20 @@ void testRenameIntoALiteralContentElementStillNeedsAllowTextInOnTheTarget() {
apply(divToStyle.allowTextIn("style"), "
a{b:c}
"));
}
+ /** A nested kept element does not override a literal element's text gate. */
+ @Test
+ void testNestedElementInsideRenamedLiteralContentUsesOuterTextGate() {
+ HtmlPolicyBuilder divToStyle = new HtmlPolicyBuilder()
+ .allowElements((name, attrs) -> "style", "div")
+ .allowElements("b", "style");
+ String html = "aboldc
after";
+
+ assertEquals("after", apply(divToStyle, html));
+ assertEquals(
+ "after",
+ apply(divToStyle.allowTextIn("style"), html));
+ }
+
/**
* A void element renamed to one that is not void is closed at once (#450).
* The lexer never produces a close tag for {@code br}, and the balancer,