From e648c17e79b5afea77eec6a674ccc47190dac506 Mon Sep 17 00:00:00 2001 From: Tim Evans Date: Mon, 20 Jul 2026 15:47:06 +0100 Subject: [PATCH] refactor: dedupe art-tray thumbnail template; test real escaping path --- src/components/art-tray.js | 18 +++++++++--------- tests/components/art-tray.test.js | 14 +++++++++----- 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/src/components/art-tray.js b/src/components/art-tray.js index b18a95f..54f96c7 100644 --- a/src/components/art-tray.js +++ b/src/components/art-tray.js @@ -10,13 +10,17 @@ export class ArtTray extends HTMLElement { } connectedCallback() { this.render(); } - render() { - const results = searchArt(this.manifest, this._query); - const grid = results.map((a) => ` + renderThumb(a) { + return ` `).join(''); + `; + } + + render() { + const results = searchArt(this.manifest, this._query); + const grid = results.map((a) => this.renderThumb(a)).join(''); this.innerHTML = `
@@ -33,11 +37,7 @@ export class ArtTray extends HTMLElement { renderResults() { const results = searchArt(this.manifest, this._query); const container = this.querySelector('[data-role=art-results]'); - container.innerHTML = results.map((a) => ` - `).join('') || '
No matches
'; + container.innerHTML = results.map((a) => this.renderThumb(a)).join('') || '
No matches
'; this.bindThumbs(); } diff --git a/tests/components/art-tray.test.js b/tests/components/art-tray.test.js index 69531e3..88f5feb 100644 --- a/tests/components/art-tray.test.js +++ b/tests/components/art-tray.test.js @@ -34,10 +34,14 @@ describe('', () => { expect(detail).toEqual({ src: '/art/b.png', label: 'Neon street' }); }); - it('escapes a query containing a quote (input does not break)', () => { - const input = el.querySelector('[data-role=art-query]'); - input.value = 'rain " '; - input.dispatchEvent(new Event('input')); - expect(el.querySelector('[data-role=art-query]').value).toBe('rain " '); + it('escapes malicious manifest label/src in rendered thumbnails', () => { + document.body.innerHTML = ''; + const bad = document.createElement('art-tray'); + bad.manifest = [{ id: 'x', src: '/art/x.png', label: 'evil" onerror="boom', tags: ['x'] }]; + document.body.appendChild(bad); + const img = bad.querySelector('[data-art-id="x"] img'); + expect(img).not.toBe(null); + expect(img.hasAttribute('onerror')).toBe(false); // attribute not injected + expect(img.getAttribute('alt')).toBe('evil" onerror="boom'); // literal value, escaped then parsed back }); });