Skip to content

fix: pin CDN scripts + theme CSS with SRI integrity hashes (closes #19)#20

Merged
wpak-ai merged 3 commits intomasterfrom
fix/sri-cdn-scripts-19
May 6, 2026
Merged

fix: pin CDN scripts + theme CSS with SRI integrity hashes (closes #19)#20
wpak-ai merged 3 commits intomasterfrom
fix/sri-cdn-scripts-19

Conversation

@timon0305
Copy link
Copy Markdown
Collaborator

@timon0305 timon0305 commented May 5, 2026

Problem

Three cross-origin assets in static/index.html loaded without integrity attributes — the dashboard executed whatever the CDN delivered. Marked.js parses raw markdown and highlight.js runs on every code block, so a swapped payload would have full DOM access in our origin.

Change

  • static/index.htmlintegrity="sha512-…" + crossorigin="anonymous" on all three CDN tags (highlight.js JS + CSS + marked.js).

  • static/js/app.js — runtime theme swap previously rewrote href between vs2015.min.css and github.min.css via inline ternary. With integrity on the static tag, that would have left a stale hash at the moment of fetch and broken the UI on theme toggle. Replaced both inline sites with a const-map helper:

    const HLJS_THEME_SHEETS = {
      dark:  { href: '…vs2015.min.css',  integrity: 'sha512-…' },
      light: { href: '…github.min.css',  integrity: 'sha512-…' },
    };
    
    function applyHljsTheme(theme) {
      const link = document.getElementById('hljs-theme');
      if (!link) return;
      const sheet = HLJS_THEME_SHEETS[theme] || HLJS_THEME_SHEETS.dark;
      link.integrity = sheet.integrity;  // set FIRST so the browser sees it at fetch time
      link.href = sheet.href;
    }

Test plan

All four SHA-512 hashes verified two ways:

Method Source
gh api 'https://api.cdnjs.com/libraries/<name>/<version>?fields=sri' cdnjs's published SRI for the version
curl -sL <url> | openssl dgst -sha512 -binary | base64 Re-derived from the actual CDN response

Both methods agree for all four files (vs2015 / github CSS, highlight.min.js, marked.min.js).

End-to-end:

  • pytest tests/75/75 OK (no behaviour change in Python).
  • ✅ Served HTML carries integrity + crossorigin on all three CDN tags.
  • ✅ App boots clean, returns HTTP 200 on http://127.0.0.1:5000/.

Manual browser verification (DevTools → Console + Network):

  1. First load — highlight.js, marked.js, vs2015.min.css all 200, no SRI error in Console.
  2. Toggle theme (sun/moon icon) — github.min.css fetched fresh with its own hash, page recolours, no SRI error.
  3. Toggle back — vs2015.min.css re-fetches / cached, no SRI error.

Forced negative test (optional): mutate one character of any integrity attr → browser refuses the resource with the distinctive Failed to find a valid digest in the 'integrity' attribute… console message. Confirms enforcement is real, not silently bypassed.

Closes #19.

Summary by CodeRabbit

  • Bug Fixes

    • Fixed runtime theme switching so code highlighting updates reliably when changing themes.
  • Security

    • Added Subresource Integrity (SRI) and crossorigin attributes for CDN-loaded highlighting and Markdown assets to prevent tampering.
  • Tests

    • Added a regression test ensuring highlight theme URLs and their SRI hashes remain consistent between markup and runtime theme logic.

The dashboard loads three cross-origin assets without integrity
attributes:

  highlight.js/11.9.0/styles/vs2015.min.css
  highlight.js/11.9.0/highlight.min.js
  marked/12.0.1/marked.min.js

Marked.js parses raw markdown and highlight.js runs on every code
block — a swapped CDN payload would execute in our origin with full
DOM access. Subresource Integrity (sha512 hash + crossorigin="anonymous")
makes the browser refuse anything whose hash does not match a
known-good value.

Wrinkle: static/js/app.js used to swap `hljsLink.href` between the
dark (vs2015) and light (github) stylesheets at runtime via a plain
ternary. Adding integrity to the static <link> tag would have made
the runtime swap break — the browser reads the (now-stale) integrity
at fetch time and refuses the new sheet.

Fix: introduced HLJS_THEME_SHEETS const map keyed by theme name,
each entry carrying { href, integrity }. New applyHljsTheme(theme)
helper sets integrity FIRST then href so the browser sees the right
hash when it triggers the new fetch. Both inline ternary call sites
(applyTheme and setWorkspaceMode) now call the helper instead of
duplicating the logic.

All four SHA-512 hashes verified two ways:
  1. cdnjs SRI API
       gh api 'https://api.cdnjs.com/libraries/<name>/<version>?fields=sri'
  2. Re-derived from actual CDN content
       curl -sL <url> | openssl dgst -sha512 -binary | base64
  → all four match.

pytest 75/75 OK (no behaviour change in Python code).
Live HTTP smoke: served HTML carries integrity + crossorigin on all
three tags; manual browser verification path documented in PR body.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

📝 Walkthrough

Walkthrough

Adds Subresource Integrity (SRI) attributes to CDN-loaded Highlight.js and Marked.js assets in static/index.html, centralizes runtime Highlight.js theme switching in static/js/app.js by mapping themes to {href, integrity} and applying both together, and adds a regression test verifying URL/hash parity between HTML and JS.

Changes

SRI Pinning and Theme-Aware Asset Loading

Layer / File(s) Summary
Static SRI attributes
static/index.html
Adds integrity="sha384-..." and crossorigin="anonymous" to CDN <link>/<script> tags and inserts comments warning that theme swaps must update integrity together with href.
Data Mapping
static/js/app.js (top)
Introduces HLJS_THEME_SHEETS mapping that pairs each theme name with both href and integrity values.
Core Implementation
static/js/app.js (new helper)
Adds applyHljsTheme(themeName) which updates the #hljs-theme link by setting integrity before href.
Wiring / Call Sites
static/js/app.js (setWorkspaceMode, applyTheme locations)
Replaces direct hljsLink.href assignments with calls to applyHljsTheme(...) at both runtime theme-switch sites.
Tests
tests/test_hljs_theme_consistency.py
New regression test that parses static/index.html and static/js/app.js to assert the dark theme href and integrity values are in sync between files.

Sequence Diagram

sequenceDiagram
    participant User
    participant App as app.js
    participant DOM
    participant Browser
    participant CDN

    rect rgba(240, 100, 100, 0.5)
    Note over User,CDN: OLD: Theme swap risk (integrity mismatch)
    User->>App: request theme change
    App->>DOM: set hljsLink.href only
    DOM->>Browser: request stylesheet (wrong integrity)
    Browser->>CDN: fetch stylesheet
    CDN-->>Browser: stylesheet payload
    Browser->>Browser: hash mismatch → reject stylesheet
    end

    rect rgba(100, 200, 100, 0.5)
    Note over User,CDN: NEW: Coordinated integrity + href update
    User->>App: request theme change
    App->>App: lookup HLJS_THEME_SHEETS[theme]
    App->>DOM: applyHljsTheme(theme) — set integrity then href
    DOM->>Browser: request stylesheet (correct integrity)
    Browser->>CDN: fetch stylesheet
    CDN-->>Browser: stylesheet payload
    Browser->>Browser: hash matches → apply stylesheet
    DOM->>User: theme updated
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through links and hashes bright,
Pinned each CDN so all feels right,
When themes swap now I set both two,
Integrity first — then style anew! 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix: pin CDN scripts + theme CSS with SRI integrity hashes (closes #19)' accurately and specifically describes the main change—adding SRI integrity attributes to CDN-loaded assets and handling runtime theme swaps.
Linked Issues check ✅ Passed All coding requirements from issue #19 are met: SRI integrity attributes added to three CDN tags in index.html, HLJS_THEME_SHEETS const map created, applyHljsTheme() function updates both href and integrity atomically, both call sites updated, and regression test added to prevent hash drift.
Out of Scope Changes check ✅ Passed All changes are directly scoped to issue #19: SRI implementation in HTML/JS and a regression test to prevent future maintenance drift; no unrelated modifications present.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sri-cdn-scripts-19

Comment @coderabbitai help to get the list of available commands and usage tips.

@timon0305 timon0305 self-assigned this May 5, 2026
@timon0305 timon0305 requested a review from clean6378-max-it May 5, 2026 16:43
@clean6378-max-it
Copy link
Copy Markdown
Collaborator

Nice to have

Maintenance — vs2015.min.css URL and hash exist in both static/index.html and HLJS_THEME_SHEETS.dark in static/js/app.js. On a highlight.js version bump, both must stay aligned (comments already point at issue #19 and the cdnjs SRI API). A build step or single generated snippet would remove that drift class; without a bundler, the current cross-referenced comments are a reasonable tradeoff.

Per review feedback on PR #20 — vs2015.min.css URL and SRI hash live in
both static/index.html (initial load) and static/js/app.js
(HLJS_THEME_SHEETS.dark, runtime swap). On a highlight.js version bump
both must update together; the cross-referenced comments warn about it
but couldn't enforce it. This test extracts the href + integrity from
each file and asserts both pairs match, turning the drift hazard into a
checked invariant.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
tests/test_hljs_theme_consistency.py (1)

30-43: ⚡ Quick win

Harden regex matching for exact keys/attributes.

Current patterns can match substrings (for example, data-href or darkMode), which can create false positives as files evolve.

Suggested patch
-    attr_m = re.search(re.escape(attr) + r'\s*=\s*"([^"]*)"', m.group(0))
+    attr_m = re.search(
+        rf'\b{re.escape(attr)}\b\s*=\s*["\']([^"\']*)["\']',
+        m.group(0),
+    )
@@
-    block = re.search(re.escape(theme) + r"\s*:\s*\{([^}]*)\}", js, re.DOTALL)
+    block = re.search(
+        rf"\b{re.escape(theme)}\b\s*:\s*\{{([^}}]*)\}}",
+        js,
+        re.DOTALL,
+    )
@@
-        m = re.search(key + r"\s*:\s*['\"]([^'\"]+)['\"]", body)
+        m = re.search(
+            rf"\b{re.escape(key)}\b\s*:\s*['\"]([^'\"]+)['\"]",
+            body,
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_hljs_theme_consistency.py` around lines 30 - 43, The regexes are
too permissive and can match substrings; tighten them by forcing exact
key/attribute boundaries: in the HTML attribute lookup (the attr_m search)
require a word boundary or non-identifier context before and an equals sign
after (e.g. use (?<![A-Za-z0-9_-])attr(?=\s*=) or \battr(?=\s*=)); in
_js_theme_entry tighten the block and key matches to avoid substrings by
matching the literal object key with boundaries (e.g. use (?<![A-Za-z0-9_-])" +
re.escape(theme) + r"(?<![A-Za-z0-9_-])\s*:\s*\{...}" or add \b around theme)
and match keys ("href" and "integrity") with lookarounds like
(?<![A-Za-z0-9_-])href(?![A-Za-z0-9_-])\s*:\s*['\"]...['\"]; update the regexes
in _js_theme_entry and the earlier attr_m search accordingly so only exact
keys/attributes are captured.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/test_hljs_theme_consistency.py`:
- Around line 30-43: The regexes are too permissive and can match substrings;
tighten them by forcing exact key/attribute boundaries: in the HTML attribute
lookup (the attr_m search) require a word boundary or non-identifier context
before and an equals sign after (e.g. use (?<![A-Za-z0-9_-])attr(?=\s*=) or
\battr(?=\s*=)); in _js_theme_entry tighten the block and key matches to avoid
substrings by matching the literal object key with boundaries (e.g. use
(?<![A-Za-z0-9_-])" + re.escape(theme) + r"(?<![A-Za-z0-9_-])\s*:\s*\{...}" or
add \b around theme) and match keys ("href" and "integrity") with lookarounds
like (?<![A-Za-z0-9_-])href(?![A-Za-z0-9_-])\s*:\s*['\"]...['\"]; update the
regexes in _js_theme_entry and the earlier attr_m search accordingly so only
exact keys/attributes are captured.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1a5768ea-cc0e-4864-af73-f3ed638e228d

📥 Commits

Reviewing files that changed from the base of the PR and between 4a33817 and 7595d8f.

📒 Files selected for processing (1)
  • tests/test_hljs_theme_consistency.py

@timon0305 timon0305 requested a review from wpak-ai May 6, 2026 17:10
@wpak-ai wpak-ai merged commit 289c0da into master May 6, 2026
2 checks passed
@wpak-ai wpak-ai deleted the fix/sri-cdn-scripts-19 branch May 6, 2026 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants