Skip to content

Conversation

@2witstudios
Copy link
Owner

@2witstudios 2witstudios commented Feb 10, 2026

Summary

  • Self-host Monaco editor runtime assets at /_next/static/monaco/vs to eliminate external CDN dependency
  • Remove cdn.jsdelivr.net from CSP script-src, style-src, and font-src directives
  • Extract Monaco loader configuration to dedicated, testable module with unit tests
  • Remove monaco-editor-webpack-plugin dependency (replaced by CopyPlugin)

Security Impact

This change hardens the Content Security Policy by removing external CDN allowances that were only needed for Monaco loading. Self-hosting Monaco assets:

  • Eliminates supply chain attack surface from third-party CDN
  • Enables air-gapped/offline deployments
  • Improves compliance posture (SOC 2, enterprise requirements)

Test plan

  • Verify Monaco editor loads correctly in browser
  • Check DevTools Network tab shows assets from /_next/static/monaco/vs
  • Confirm no CSP violations in console
  • Run unit tests: pnpm --filter web vitest run src/lib/editor/monaco/__tests__/
  • Run security header tests: pnpm --filter web vitest run src/middleware/__tests__/security-headers.test.ts

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Security Improvements

    • Tightened Content Security Policy to remove external CDN allowances for Monaco assets.
  • Refactor

    • Centralized and simplified Monaco loader initialization; editor component unchanged for users.
  • Chores

    • Build now ensures Monaco assets and the PDF worker are copied into the app bundle; removed legacy build plugin.
  • Tests

    • Added unit tests for loader path logic and a CSP test asserting absence of the CDN.
  • Documentation

    • Updated changelog, architecture, and compliance docs to reflect these changes.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

This PR moves Monaco Editor assets to self-hosting, removes the monaco-editor-webpack-plugin, adds CopyPlugin patterns to copy Monaco runtime files into the build output, centralizes runtime loader configuration into a new loader-config module, adapts MonacoEditor to call that initializer, and tightens CSP by removing jsDelivr allowances.

Changes

Cohort / File(s) Summary
Monaco loader & tests
apps/web/src/lib/editor/monaco/loader-config.ts, apps/web/src/lib/editor/monaco/__tests__/loader-config.test.ts
New loader-config module that normalizes assetPrefix/origin, builds the /vs path, exposes configureMonacoLoader() and helper functions; accompanied by unit tests covering path resolution and origin handling.
MonacoEditor component
apps/web/src/components/editors/MonacoEditor.tsx
Replaced custom worker URL/environment setup with a call to configureMonacoLoader() (prior language registration preserved).
Build config / static asset copying
apps/web/next.config.ts, apps/web/package.json
Removed monaco-editor-webpack-plugin devDependency; updated webpack CopyPlugin usage to copy monaco-editor/min/vs into .next/static/monaco/vs and copy pdf.worker.min.mjs to public/.
CSP / security tests
apps/web/src/middleware/security-headers.ts, apps/web/src/middleware/__tests__/security-headers.test.ts
Removed https://cdn.jsdelivr.net entries from script/style/font CSP directives; added test asserting jsDelivr is not present.
Docs / changelog / compliance
docs/1.0-overview/changelog.md, docs/2.0-architecture/2.5-integrations/monaco-editor.md, docs/security/compliance-sovereignty-analysis.md
Documented self-hosting of Monaco/PDF.js, updated architecture notes to reflect loader-based configuration, updated compliance analysis to remove jsDelivr and note completed self-hosting.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Build as Build (webpack)
participant Static as Static Server (.next / public)
participant Browser as Client Browser
participant Loader as Monaco AMD Loader

Build->>Static: Copy monaco-editor/min/vs -> .next/static/monaco/vs\nCopy pdf.worker.min.mjs -> public/
Browser->>Static: Request page + /_next/static assets (includes NEXT_DATA with assetPrefix)
Browser->>Browser: configureMonacoLoader() reads NEXT_DATA and window.location
Browser->>Loader: loader.config({ paths: { vs: "/_next/static/monaco/vs" } })
Loader->>Static: Fetch VS runtime modules from /_next/static/monaco/vs
Loader->>Browser: Initialize Monaco with local workers

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 A rabbit tucked the CDN away,
Copied VS to static for a safer day.
Loader set the path,
Workers find their math,
Hopping happy — self-hosted play! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title '[web] Self-host Monaco assets and harden CSP' directly and accurately summarizes the main changes: self-hosting Monaco editor assets and tightening Content Security Policy directives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/monaco-self-host-csp-hardening

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
apps/web/src/middleware/security-headers.ts (1)

36-36: Nonce generation uses base64-encoded UUIDv4 — consider a direct random source.

btoa(crypto.randomUUID()) produces a base64-encoded UUID (hyphenated hex), yielding ~48 characters of relatively low entropy density. A more standard approach for CSP nonces is to use raw random bytes:

const array = new Uint8Array(16);
crypto.getRandomValues(array);
return btoa(String.fromCharCode(...array));

This gives a full 128-bit random nonce in a shorter string. Not a blocking issue since UUIDs are still unpredictable, but worth noting for a security-focused file.

Tip

We've launched Issue Planner and it is currently in beta. Please try it out and share your feedback on Discord!


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor

@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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@docs/security/compliance-sovereignty-analysis.md`:
- Around line 342-345: The guidance currently recommends adding SRI hashes for
remaining third-party scripts but Stripe.js and Google One Tap
(accounts.google.com/gsi/client) do not support SRI; update the document to
remove or qualify the SRI recommendation for these two vendors by stating SRI
applies only where vendors publish/allow it (e.g., self-hosted Monaco and PDF.js
remain eligible), replace the SRI recommendation for Stripe.js and Google One
Tap with CSP allowlisting as the primary compensating control, and adjust the
"Effort" estimate to reflect low-medium for hardening plus minimal changes to
add CSP guidance and vendor-specific notes.
🧹 Nitpick comments (1)
apps/web/src/lib/editor/monaco/loader-config.ts (1)

11-11: Module-level isMonacoLoaderConfigured flag prevents resetting in tests.

The singleton flag means configureMonacoLoader() can only execute once per module lifetime (including across test runs in the same process). This is fine for production, but if you ever need to test configureMonacoLoader directly, you'll need to use vi.resetModules() or expose a reset helper. Not blocking since the current test suite only covers the pure functions.

Eliminate external CDN dependency by self-hosting Monaco editor runtime
assets. This improves security posture and enables air-gapped deployments.

- Copy monaco-editor/min/vs to .next/static/monaco/vs at build time
- Configure Monaco AMD loader to resolve from self-hosted path
- Remove monaco-editor-webpack-plugin (no longer needed)
- Remove cdn.jsdelivr.net from CSP script-src, style-src, font-src
- Extract loader configuration to dedicated testable module

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@2witstudios 2witstudios force-pushed the feat/monaco-self-host-csp-hardening branch from 26dc56f to 85e4115 Compare February 10, 2026 14:21
@2witstudios 2witstudios merged commit 920b8fb into master Feb 10, 2026
10 checks passed
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.

1 participant