diff --git a/CLAUDE.md b/CLAUDE.md index 52aac16..6300492 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -502,6 +502,26 @@ report in chat, but it never computes it. 7. **Virtual Threads**: Enabled for concurrency (JDK 25). ### Frontend Rules + +0. **Anything reaching `dangerouslySetInnerHTML` must be escaped at the source.** + `AgentView`'s `boldify` applied its markdown substitutions to the raw string and escaped + nothing, so every character of an assistant reply was parsed as markup — verified by + executing it: `boldify('')` returned the payload byte for byte, and + in a browser Chromium built a real `` element and fired its handler. It is **stored** + XSS: `agent_conversation.transcript` is replayed into the renderer on load, and the agent + echoes database content, so a table named `` renders in an analyst's + browser. httpOnly cookies mean the token cannot be read, but `withCredentials` means the + payload acts *as* the reader. **Escape before substituting, never after** — escaping after + would escape the ``/`` tags the function itself emits and print literal tag + text. Note `') + + assert.ok(!out.includes('