b>x = 1<
src>';
+ const result = LatexPreRenderer._cleanLatexFromHtml(input);
+
+ // After fixed-point removal no opening "<" (start of a tag) may remain.
+ expect(result).not.toContain('<');
+ expect(result).toContain('x = 1');
+ });
+
+ // Security: double-escaping (CodeQL). Decoding & must happen LAST so
+ // an already-escaped sequence like "<" (the literal text "<")
+ // is not double-decoded into "<".
+ test('does not double-decode < into <', () => {
+ expect(LatexPreRenderer._cleanLatexFromHtml('<')).toBe('<');
+ });
+
+ test('does not double-decode & into &', () => {
+ expect(LatexPreRenderer._cleanLatexFromHtml('&')).toBe('&');
+ });
+
+ test('still decodes a single & to & (legitimate input preserved)', () => {
+ expect(LatexPreRenderer._cleanLatexFromHtml('a && b')).toBe('a && b');
+ });
});
describe('preRender', () => {
@@ -421,6 +459,40 @@ describe('LatexPreRenderer', () => {
expect(result.html).not.toContain('data-latex="\\[
');
});
+ // Security: stored-xss (CodeQL) on the DOMParser round-trip.
+ // The only NEW HTML preRender injects is the MathJax library-rendered
+ // wrapper, with its data-latex attribute escaped via escapeHtmlAttribute().
+ // LaTeX that contains a quote/angle-bracket payload must be neutralised in
+ // the attribute (escaped), never emitted as a live attribute/tag.
+ test('escapes quote/angle payloads inside the injected math wrapper attribute', async () => {
+ const html = '\\(x" onmouseover="alert(1)\\)
';
+ const result = await LatexPreRenderer.preRender(html);
+
+ expect(result.latexRendered).toBe(true);
+ // The injected wrapper is present and library-rendered.
+ expect(result.html).toContain('exe-math-rendered');
+ // The wrapper's own data-latex attribute (built by THIS file via
+ // escapeHtmlAttribute) must entity-escape the double quote so the
+ // payload cannot break out into a live onmouseover handler.
+ const dataLatexMatch = result.html.match(/data-latex="([^"]*)"/);
+ expect(dataLatexMatch).not.toBeNull();
+ expect(dataLatexMatch[1]).toContain('" onmouseover="alert(1)');
+ expect(dataLatexMatch[1]).not.toContain('"');
+ });
+
+ test('does not introduce a new live (note: space inside tag is common)
html = html.replace(
- /