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
});
});