diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 9a7d137..f98131f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,6 +27,14 @@ jobs: - name: Check out repository uses: actions/checkout@v4 + - name: Set up Node for renderer security tests + uses: actions/setup-node@v4 + with: + node-version: '22' + + - name: Test shipped Markdown renderer + run: node --test tests/test_web_renderer.cjs + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: diff --git a/tests/test_web_renderer.cjs b/tests/test_web_renderer.cjs new file mode 100644 index 0000000..4d52f39 --- /dev/null +++ b/tests/test_web_renderer.cjs @@ -0,0 +1,30 @@ +const assert = require('node:assert/strict'); +const fs = require('node:fs'); +const path = require('node:path'); +const vm = require('node:vm'); +const { test } = require('node:test'); + +// Exercise the shipped renderer without starting the UI or evaluating its HTML. +const source = fs.readFileSync(path.join(__dirname, '../web_companion/app.js'), 'utf8'); +const start = source.indexOf(' function escapeHtml('); +const end = source.indexOf(' function renderPreview()', start); +assert.ok(start >= 0 && end > start); +const renderer = { t: () => '' }; +vm.createContext(renderer); +vm.runInContext(source.slice(start, end), renderer); + +test('ordinary fenced code preserves language and escapes body', () => { + assert.equal(renderer.renderMarkdown('```python\n & "quoted"\n```'), + '
<x> & "quoted"
'); +}); + +for (const language of ['">', '" onmouseover="probe', + "'<> &", '">', '日本語']) { + test(`fence language remains one escaped attribute: ${JSON.stringify(language)}`, () => { + const output = renderer.renderMarkdown('```' + language + '\nbody\n```'); + assert.equal(output, '
body
'); + assert.deepEqual(output.match(/<[^>]+>/g), ['
',
+      '', '', '
']); + }); +} diff --git a/web_companion/app.js b/web_companion/app.js index caa66ad..5117f5c 100644 --- a/web_companion/app.js +++ b/web_companion/app.js @@ -326,7 +326,7 @@ } else { inCodeBlock = false; const codeContent = escapeHtml(codeBlockLines.join('\n')); - out.push(`
${codeContent}
`); + out.push(`
${codeContent}
`); } continue; }