notes.test.ts
Language TypeScript1.8 KB40 lines
/*** User notes ride into pre-rendered pages as serve-time JSON.** The text is typed by a person and shared with everyone who opens the board,* which makes it the one string on this page an insider could aim at a* colleague's browser. The defence is layered: the page script renders notes* via textContent only, and this file pins the injection layer: a note* containing `</script>` must not be able to end the JSON tag and spill the* rest of itself into the document as markup.*/import { describe, expect, it } from 'vitest'import { injectNotes, type BoardNote } from '../src/index.js'const PAGE = '<html><body><p>board</p></body></html>'const note = (text: string): Record<string, BoardNote> => ({'order:MD2639640': { text, by: 'Rafael Montero', at: '2026-09-01T20:00:00.000Z' },})describe('injectNotes', () => {it('injects the notes as a JSON script tag before </body>', () => {const out = injectNotes(PAGE, note('waiting on the client to confirm colors'))expect(out).toContain('<script id="board-notes" type="application/json">')expect(out).toContain('waiting on the client to confirm colors')expect(out.indexOf('</body>')).toBeGreaterThan(out.indexOf('board-notes'))})it('a note containing </script> cannot break out of the tag', () => {const out = injectNotes(PAGE, note('</script><img src=x onerror=alert(1)>'))// Every `<` in the payload is \u003c-escaped, so the literal sequences the// parser would act on never appear.expect(out).not.toContain('</script><img')expect(out).toContain('\\u003c/script>')})it('no notes still injects an empty object, so the page script can tell "no notes" from "old worker"', () => {const out = injectNotes(PAGE, {})expect(out).toContain('type="application/json">{}</script>')})})