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('