Give a caption or column group under a dropped template its table in Sanitizers.TABLES - #499
Merged
Merged
Conversation
…Sanitizers.TABLES A template holds a caption or column group directly, so the containment metadata implies no table for one there, but a template the policy dropped or renamed establishes none in the output: the part landed where the template was as an orphan that a browser drops, and the next pass gave it its table. The part is now judged in the output, where the template stood, by the rule that already judges an option there, so Sanitizers.TABLES emits on the first pass what it emitted on the second. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…a col directly too A part under a template the policy dropped, inside an open table, now returns to that table as a browser reading the output does, closing a column group, cell or row, instead of opening a second table or coming out bare in the cell: the dropped template is no table-scope boundary in the output, and the close loop judges what a dropped template can hold where its content lands, so a caption under one inside a kept formatting element closes it and gets its table beside it, as without the template. A col is held directly by a template as well and gets its table the same way. Tests cover the open-table, cell, formatting and col shapes, a hostile column group, and a policy that drops the part itself. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A review reproduced three regressions in the scope and closing changes
this branch added, all in states its tests never entered: a part under a
dropped template at an HTML integration point came out as the orphan this
change exists to remove; the closing loop that returns a part to an open
table is the one pop site that never queues formatting to reconstruct, so
a u around the caption was destroyed; and judging the container by the
output made a dropped template look like a dropped raw-text container, so
the caption's own text was deleted under disallowTextIn("template").
Both changes are reverted. What stays is the container rule, which the
review found sound: a caption, column group or col under a template the
policy dropped or renamed is judged where the template stood. The text
deletion is fixed by keeping a dropped template out of the dropped
raw-text container path. A part under a template inside an open table
keeps the shape it had, which is not a fixed point here or on main; that
belongs with the five other scans that read table scope the same way.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Sep 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Item 2 of #492:
Sanitizers.TABLESemits an orphan table part that is not a fixed point.What broke
Sanitizers.TABLES.sanitize("<template><ul><math><caption>TEXT")gave<caption>TEXT</caption>, a caption with no table, which a browser drops; the next pass gave<table><caption>TEXT</caption></table>. Same for<template><caption>TEXT, for acolgroup, for acol, and under any policy that drops or renamestemplate:<u><template><caption>xgave<caption>x</caption>.Why
A
templateholds a caption, column group orcoldirectly, so the containment metadata implies no table for one there, and the balancer judged the part against its logical container, the template, though the policy had dropped it. A dropped template establishes nothing in the output: the part lands where the template stood, as an orphan. The other table parts already got their table under a template through the metadata, which is why atdthere was a fixed point.What changed
TagBalancingHtmlStreamEventReceiver.effectiveContainer: the rule that Give a free option or list item its wrapper under every container, and push the select out of a kept table #497 added for anoptionoroptgroupunder a template the policy dropped or renamed, which judges the element against the output's container instead of the template, now coverscaption,colgroupandcol. Those five are exactly the elements atemplateholds directly andbodydoes not.disallowTextIn("template")lost the text of the caption it keeps.HtmlSanitizerTest;change_log.mdbullet.Before and after
<template><ul><math><caption>TEXTSanitizers.TABLES<caption>TEXT</caption>, then with its table<table><caption>TEXT</caption></table><template><ul><math><colgroup>TEXT<colgroup></colgroup>TEXT, then with its table<table><colgroup></colgroup></table>TEXT<template><col>x<col />x, then with its table<table><colgroup><col /></colgroup></table>x<template><caption>TEXT</caption></template>after<caption>TEXT</caption>after, then with its table<table><caption>TEXT</caption></table>after<u><template><caption>TEXT<caption>TEXT</caption>, then with its table<table><caption>TEXT</caption></table><template><caption>TEXT</caption></template>disallowTextIn("template")<caption>TEXT</caption>, then with its table<table><caption>TEXT</caption></table><template><ul><math><caption>TEXTBLOCKS.and(TABLES)<ul><li></li></ul><caption>TEXT</caption>, then with its table<ul><li></li></ul><table><caption>TEXT</caption></table><template><caption>TEXT</caption></template>The output narrows:
Sanitizers.TABLESemits on the first pass what it emitted on the second, and allows nothing it did not allow. A policy that drops the caption itself, or disallows text in the template, gets what it gets for the same part written without the template.Verification
./mvnw -o -ntp -B clean verify: build success, 687 tests, 0 failures (686 existing, unchanged, plus 1 new).probes-review3/ReviewProbe, 15,430 inputs x 44 policies,sanandeventsmodes, main2bc430eagainst the final commit9ebd255):san678,920 rows compared, 330 differ in output, 501 rows lose every flag, 4 keep fewer, no row throws, and 8 rows on 2 inputs gain one. Both are the same shape under the three policies that allowlibut no table: acolunder a dropped template inside a list gets a table the policy drops, which closes the list item, and the next pass gives the text after it a new item, as<ol><option><col><dl>taildoes on main without any template.events: 36,063 rows, identical.Review
/code-reviewran twice. The first pass found the container rule leaving a second table where the output container was an open column group, an orphan caption in an open cell,colleft out although the metadata treats it exactly as caption and colgroup, and a change-log bullet that did not mention custom policies.coland the bullet are fixed here.For the rest, the first pass named a root cause, that a dropped template still bounds table scope, and a version of this branch acted on it in
returnToTableContextand the container-closing loop. The second pass reproduced three regressions in exactly those two changes: a part under a dropped template at an HTML integration point came back as the orphan this change exists to remove; the closing loop that returns a part to an open table is the one pop site that never queues formatting to reconstruct, so auaround the caption was destroyed; and judging the container by the output there made a dropped template look like a dropped raw-text container, deleting the caption's own text. Both changes are reverted and the text deletion is fixed; the second pass called the container rule itself "sound and well covered".Left out
<table><tr><td><template><caption>x, keeps the shape it has on main: the template still bounds table scope, so the part stays in the cell and the next pass moves it. That is not a fixed point here or on main. Closing that scope belongs with the five other scans that read it the same way, which is its own change.<u><template><caption>xunderFORMATTING.and(TABLES)likewise keeps main's shape, non-idempotent there as on main.#497left them.Part of #492 (item 2)
🤖 Generated with Claude Code