Fix: HtmlBlock sanitizer strips select/meter presentation attrs

size, low, high, and optimum were missing from PURIFY_CONFIG.ALLOWED_ATTR
even though <select> and <meter> are already in ALLOWED_TAGS, so
<select size="4"> rendered at default height and <meter low/high/optimum>
lost its threshold-based gauge colouring. All four are pure
presentation/semantic attributes with no URL/script/event-handler
surface, so no security implication.

Also regenerates the pinned pre-Task-25 output fixture: its source
(html-block-test-body.html) already exercises size/low/high/optimum, so
the byte-identity test's expected output legitimately changes to include
them; verified the regenerated fixture's only diff from the prior one is
those four attributes now surviving.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-10 07:23:36 -07:00
co-authored by Claude Opus 5
parent 916a568e9f
commit 98f2ebf118
3 changed files with 37 additions and 5 deletions
@@ -135,15 +135,20 @@ describe('purifyHtml -- Task 24 fixture regression (formerly-dropped constructs
expect(out).toContain('<select id="f-select" name="select">');
expect(out).toContain('<optgroup label="Group one">');
expect(out).toContain('<option value="1" selected="">One</option>');
expect(out).toContain('<select id="f-multi" multiple="">');
expect(out).toContain('<select id="f-multi" multiple="" size="4">');
expect(out).toContain('<datalist id="suggestions">');
expect(out).toContain('<textarea id="f-area" rows="4" cols="40">');
expect(out).toContain('<output name="result" for="f-num f-range">');
expect(out).toContain('<progress id="f-prog" value="0.6">');
expect(out).toContain('<meter id="f-meter" min="0" max="100" value="72">');
expect(out).toContain('<meter id="f-meter" min="0" max="100" low="30" high="80" optimum="90" value="72">');
expect(out).toContain('<button type="submit">Submit</button>');
});
test('bug fix: <select size> and <meter low/high/optimum> survive (both tags were already allowed, only these four attrs were missing)', () => {
expect(out).toContain('<select id="f-multi" multiple="" size="4">');
expect(out).toContain('low="30" high="80" optimum="90"');
});
test('fixture byte survival crosses 90% (was 61.6% -- 9739/15815 -- before Task 24)', () => {
expect(out.length).toBeGreaterThan(fixtureHtml.length * 0.9);
});
@@ -306,3 +311,22 @@ describe('purifyHtml -- Task 24: security properties on newly-allowed elements',
expect(iframeOut).not.toContain('data:');
});
});
describe('purifyHtml -- bug fix: size/low/high/optimum were stripped despite select/meter being allowed tags', () => {
test('<select size> survives with its value intact (multi-select row count)', () => {
const out = purifyHtml('<select size="4"><option>a</option></select>');
expect(out).toContain('size="4"');
});
test('<input size> survives with its value intact', () => {
const out = purifyHtml('<input type="text" size="10">');
expect(out).toContain('size="10"');
});
test('<meter low/high/optimum> survive with their values intact (threshold-based gauge colouring)', () => {
const out = purifyHtml('<meter low="1" high="9" optimum="5" value="4" min="0" max="10"></meter>');
expect(out).toContain('low="1"');
expect(out).toContain('high="9"');
expect(out).toContain('optimum="5"');
});
});
+9 -1
View File
@@ -107,12 +107,20 @@ const PURIFY_CONFIG = {
// Task 24 additions.
'colspan','rowspan','scope','headers','span','start','reversed',
'type','value','name','placeholder','required','disabled','readonly',
'checked','selected','multiple','min','max','step','minlength',
'checked','selected','multiple','size','min','max','step','minlength',
'maxlength','pattern','rows','cols','accept','action','method','for',
'list','label','datetime','cite','lang','dir','role','srcset','media',
'sizes','loading','controls','poster','loop','muted','autoplay',
'preload','playsinline','kind','srclang','default','open','download',
'hidden','contenteditable',
// Bug fix: <select size="4">/<input size> and <meter low/high/optimum>
// were still being stripped even though <select>/<meter> are already in
// ALLOWED_TAGS -- only these four attribute names were missing here.
// Effect: a multi-select rendered at default height instead of the
// requested row count, and <meter> lost its threshold-based gauge
// colouring. Pure presentation/semantic attributes -- no URL, no
// script, no event-handler surface -- so no security weight added.
'low','high','optimum',
// SVG presentation attributes (explicit route -- see ALLOWED_TAGS
// comment on the SVG tag list). Covers the fixture's <svg viewBox
// role>/<rect>/<circle>/<text> block plus the common presentation
@@ -266,7 +266,7 @@ here</textarea></p>
</p>
<p>
<label for="f-multi">Multi-select</label><br>
<select id="f-multi" multiple="">
<select id="f-multi" multiple="" size="4">
<option>Red</option><option selected="">Green</option><option>Blue</option><option>Violet</option>
</select>
</p>
@@ -275,7 +275,7 @@ here</textarea></p>
<fieldset>
<legend>Output and buttons</legend>
<p><label for="f-prog">Progress</label> <progress id="f-prog" value="0.6">60%</progress></p>
<p><label for="f-meter">Meter</label> <meter id="f-meter" min="0" max="100" value="72">72</meter></p>
<p><label for="f-meter">Meter</label> <meter id="f-meter" min="0" max="100" low="30" high="80" optimum="90" value="72">72</meter></p>
<p><output name="result" for="f-num f-range">Computed output</output></p>
<p>
<button type="submit">Submit</button>