How Draw.io Desktop Configures Content Security Policy (CSP) to Prevent External Script Execution

Draw.io Desktop enforces a strict Content Security Policy by intercepting all HTTP responses in the Electron main process to inject CSP headers that whitelist only local scripts with verified SHA-256 hashes, while simultaneously blocking file protocol requests outside the application directory.

The drawio-desktop application runs the diagram editor inside an Electron BrowserWindow, which requires robust isolation from potentially malicious external code. By implementing a custom Content Security Policy (CSP) configuration through Electron's webRequest API, the application ensures that only bundled, hash-verified scripts can execute, effectively eliminating injection vectors for third-party JavaScript.

Injecting Strict CSP Headers via Electron's Main Process

In src/main/electron.js, the main process registers a header-modifying hook using session.defaultSession.webRequest.onHeadersReceived before any renderer content loads. This intercepts every network response and appends a strict Content Security Policy header that restricts resource loading to the application's own files.

The CSP string constructed at lines 73-84 combines multiple directives to create a locked-down execution environment. The default-src 'self' directive establishes a baseline that prevents loading any resource type from remote origins unless explicitly overridden by subsequent directives.

Script Source Validation with SHA-256 Hashes

The most critical directive for preventing external script execution is script-src 'self' combined with three specific SHA-256 hashes. As implemented in src/main/electron.js, the policy only permits scripts that either originate from the app's local file system or match one of these predetermined hashes:

  • 'sha256-f6cHSTUnCvbQqwa6rKcbWIpgN9dLl0ROfpEKTQUQPr8='
  • 'sha256-6g514VrT/cZFZltSaKxIVNFF46+MFaTSDTPB8WfYK+c='
  • 'sha256-ZQ86kVKhLmcnklYAnUksoyZaLkv7vvOG9cc/hBJAEuQ='

Because the browser calculates the hash of every script block before execution, any modification to the bundled JavaScript—even a single character change—would result in a hash mismatch, causing the browser to refuse execution immediately.

// src/main/electron.js (lines 73-84)
session.defaultSession.webRequest.onHeadersReceived((details, callback) => {
  callback({
    responseHeaders: {
      ...details.responseHeaders,
      'Content-Security-Policy': [
        "default-src 'self'; " +
        "script-src 'self' " +
        "'sha256-f6cHSTUnCvbQqwa6rKcbWIpgN9dLl0ROfpEKTQUQPr8=' " +
        "'sha256-6g514VrT/cZFZltSaKxIVNFF46+MFaTSDTPB8WfYK+c=' " +
        "'sha256-ZQ86kVKhLmcnklYAnUksoyZaLkv7vvOG9cc/hBJAEuQ='; " +
        "connect-src 'self'" + (isGoogleFontsEnabled ?
          " https://fonts.googleapis.com https://fonts.gstatic.com" : "") +
        "; img-src * data:; media-src *; font-src * data:; " +
        "frame-src 'none'; style-src 'self' 'unsafe-inline'" +
        (isGoogleFontsEnabled ? " https://fonts.googleapis.com" : "") +
        "; base-uri 'none'; child-src 'self'; object-src 'none';"
      ]
    }
  });
});

Restricting Network Connections and External Resources

Beyond script execution, the CSP configuration limits external communication through the connect-src 'self' directive, which restricts XMLHttpRequest, fetch, and WebSocket connections to the application itself. The policy optionally appends Google Fonts URLs when font integration is enabled, but maintains strict prohibitions on arbitrary external data exfiltration or script loading.

Additional directives harden the surface area further: frame-src 'none' prevents embedding external frames, object-src 'none' blocks plugin execution, and base-uri 'none' restricts base element manipulation.

File Protocol Request Filtering

Complementing the CSP headers, drawio-desktop implements a second security layer through session.defaultSession.webRequest.onBeforeRequest at lines 90-103 of src/main/electron.js. This hook intercepts all file:// protocol requests and validates them against the application's installation directory and an optional plugins folder.

Any attempt to load JavaScript, HTML, or other resources from arbitrary user-supplied paths outside these whitelisted directories is cancelled before reaching the renderer. This prevents attackers from exploiting local file inclusion vulnerabilities to execute scripts stored elsewhere on the filesystem.

// src/main/electron.js (lines 90-103)
session.defaultSession.webRequest.onBeforeRequest(
  { urls: ['file://*'] },
  (details, callback) => {
    const url = details.url.replace(/\/.\:\//, s => s.toUpperCase());

    // Only allow files under the app's code folder or the plugins folder
    if (!url.startsWith(codeUrl) &&
        (!isPluginsEnabled() || (isPluginsEnabled() && !url.startsWith(pluginsCodeUrl)))) {
      console.log('Blocked loading file from', details.url);
      callback({ cancel: true });
    } else {
      callback({});
    }
  });

Defense in Depth Architecture

Together, these mechanisms create a comprehensive defense-in-depth strategy that prevents external script execution:

  1. Header Injection enforces strict source whitelisting for all resource types including scripts, styles, and images through the onHeadersReceived hook.
  2. Cryptographic Verification via SHA-256 hashes guarantees that bundled script content has not been tampered with between releases, as any modification invalidates the hash.
  3. Filesystem Sandboxing ensures only files within the application bundle (codeUrl) or approved plugin directories (pluginsCodeUrl) can be loaded via the file protocol.

Summary

  • The CSP configuration in drawio-desktop is enforced through Electron's webRequest.onHeadersReceived API in src/main/electron.js at lines 73-84.
  • Script execution is restricted to local files matching three specific SHA-256 hashes, preventing injection of arbitrary external code via script-src directives.
  • Network connections are limited to connect-src 'self', blocking unauthorized data exfiltration and remote script loading.
  • File protocol filtering via webRequest.onBeforeRequest (lines 90-103) blocks attempts to load resources from outside the application directory or plugin folders.
  • This multi-layered approach ensures that only cryptographically verified, bundled scripts can execute within the drawio-desktop application.

Frequently Asked Questions

What specific CSP directives prevent external scripts from running in Draw.io Desktop?

The script-src 'self' directive combined with three SHA-256 hash whitelists ensures only local scripts with matching cryptographic hashes can execute, while default-src 'self' blocks all remote resource loading by default unless explicitly allowed.

Where in the source code is the CSP header actually injected?

The header injection occurs in src/main/electron.js at lines 73-84, where session.defaultSession.webRequest.onHeadersReceived intercepts all responses and appends the Content-Security-Policy header before the renderer processes them.

How does Draw.io Desktop prevent loading JavaScript files from arbitrary user directories?

Through the onBeforeRequest hook at lines 90-103 in src/main/electron.js, the application cancels any file:// URL that does not reside within the application's code folder or the authorized plugins directory, preventing local file inclusion attacks.

Why are SHA-256 hashes used in the script-src directive instead of nonce-based CSP?

SHA-256 hashes provide deterministic verification of specific bundled script blocks without requiring runtime nonce generation, ensuring that any modification to the application's JavaScript—whether malicious or accidental—immediately invalidates the script's authorization to run.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →