Problem
Three CDN-loaded assets in static/index.html ship without integrity (SRI) attributes:
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css" id="hljs-theme">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
If cdnjs is compromised (or any CDN edge serving us the file is), the browser will execute whatever JavaScript is delivered — Marked.js parses raw markdown into HTML and highlight.js runs on every code block, so a swapped payload runs in our origin with full DOM access. SRI (integrity="sha384-..." + crossorigin="anonymous") tells the browser to refuse anything whose hash doesn't match a known-good value.
Wrinkle: runtime theme swap
static/js/app.js:128-131 (and duplicate at 839-843) toggles the hljs-theme <link> between two stylesheets at runtime:
hljsLink.href = theme === 'dark'
? 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/vs2015.min.css'
: 'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github.min.css';
If we set integrity="sha384-<vs2015 hash>" on the static tag and the user toggles to light theme at runtime, the swap rewrites href but leaves the wrong-hash integrity in place. The browser then refuses the new stylesheet → broken UI.
The fix has to update both href and integrity together. A small const map of { href, integrity } keyed by theme name is the right shape.
Suggested fix
- Add
integrity="sha384-<hash>" crossorigin="anonymous" to all three static CDN tags in index.html. Hashes verified against the cdnjs SRI API (https://api.cdnjs.com/libraries/<name>/<version>?fields=sri).
- Replace the inline ternary in
app.js with a const map containing both { href, integrity } pairs; the theme swap reads from the map and assigns both attributes together.
- Same fix applied to both call sites (app.js:128 and app.js:839).
- (Optional follow-up, separate issue)
github.min.css becomes a runtime-needed asset with its own SRI; consider preloading it via <link rel="preload" as="style"> so first toggle is instant.
Severity
Medium — Listed as Medium / 1pt in Will's eval week-1 plan for claude-code-chat-browser. Defence in depth: the dashboard already loads only over 127.0.0.1 and the CDN is reputable, but pinning hashes neutralises a whole class of supply-chain risk for ~6 lines of HTML / 10 lines of JS.
Problem
Three CDN-loaded assets in
static/index.htmlship withoutintegrity(SRI) attributes:If cdnjs is compromised (or any CDN edge serving us the file is), the browser will execute whatever JavaScript is delivered — Marked.js parses raw markdown into HTML and highlight.js runs on every code block, so a swapped payload runs in our origin with full DOM access. SRI (
integrity="sha384-..."+crossorigin="anonymous") tells the browser to refuse anything whose hash doesn't match a known-good value.Wrinkle: runtime theme swap
static/js/app.js:128-131(and duplicate at 839-843) toggles thehljs-theme<link>between two stylesheets at runtime:If we set
integrity="sha384-<vs2015 hash>"on the static tag and the user toggles to light theme at runtime, the swap rewriteshrefbut leaves the wrong-hashintegrityin place. The browser then refuses the new stylesheet → broken UI.The fix has to update both
hrefandintegritytogether. A small const map of{ href, integrity }keyed by theme name is the right shape.Suggested fix
integrity="sha384-<hash>" crossorigin="anonymous"to all three static CDN tags inindex.html. Hashes verified against the cdnjs SRI API (https://api.cdnjs.com/libraries/<name>/<version>?fields=sri).app.jswith a const map containing both{ href, integrity }pairs; the theme swap reads from the map and assigns both attributes together.github.min.cssbecomes a runtime-needed asset with its own SRI; consider preloading it via<link rel="preload" as="style">so first toggle is instant.Severity
Medium — Listed as Medium / 1pt in Will's eval week-1 plan for
claude-code-chat-browser. Defence in depth: the dashboard already loads only over127.0.0.1and the CDN is reputable, but pinning hashes neutralises a whole class of supply-chain risk for ~6 lines of HTML / 10 lines of JS.