Skip to content

Security: add SRI integrity attributes to CDN-loaded highlight.js, marked.js, and theme CSS #19

@timon0305

Description

@timon0305

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

  1. 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).
  2. 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.
  3. Same fix applied to both call sites (app.js:128 and app.js:839).
  4. (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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions