WordPress / WordPress/contributor-toolkit

The renderer's two global error handlers never install: CSP blocks them on every launch

Open Beginner friendly
#374 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug
Dominant language
JavaScript
Stars
36
Forks
13
Avg merge
23h 19m
Merged PRs (30d)
72

Description

Description

src/renderer/index.html declares two inline scripts that install the window's global error handlers:

<script>window.addEventListener('error', e => { console.error(e.error || e.message) })</script>
<script>window.addEventListener('unhandledrejection', e => { console.error(e.reason) })</script>

The same file sets:

<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'">

There is no script-src, so default-src 'self' applies to scripts and neither inline script is allowed to run. Chromium blocks both on every launch.

Expected: an uncaught error or an unhandled promise rejection in the renderer reaches console.error and therefore the log file.

Actual: neither listener is installed, so nothing reaches them.

Confirmed rather than inferred

I computed the SHA-256 of the two script bodies and compared them with the hashes Chromium names in its own violation message:

script computed named in the log
error listener sha256-XdpMs9OeHqoB7EJADagtz2B/g2p8FQ7EfWKEVjv/Jlc= same
unhandledrejection listener sha256-kidb3oKMTWYevFgKdLd4IWn/PUpAMSd07tXVqZc6FPg= same

Two blocked scripts, two hashes, exact match.

What it costs, stated carefully

Less than it first appears, and worth being precise about.

initLogging() calls log.initialize({ spyRendererConsole: true }), so anything reaching console.error still lands in the log. React catches render errors and reports them through console.error itself, so those are logged today. I confirmed that: a render crash I caused while working on this appeared in the log three times, despite both handlers being absent.

What is lost is everything React does not catch:

  • an unhandled promise rejection anywhere in the renderer, including in the many async IPC callers
  • an error thrown from an event handler or a timer outside React's tree
  • anything thrown before React mounts

Those reach window.onerror or unhandledrejection and nowhere else, so today they go unrecorded. Given that logging.js opens by explaining the log exists because "a spawn that fails before producing output leaves no trace whatsoever", the gap runs against that file's own intent.

There is also a smaller cost: two CSP violations are logged on every launch, which is noise in a file people are asked to attach to bug reports.

Fixes

Either works and neither weakens the policy:

  1. Move the two listeners into the bundle. They are two lines, and src/renderer/index.jsx already runs at module scope, so it could register them before mounting. This is the one I would pick: it keeps the CSP exactly as strict as it is now and removes the inline scripts entirely.
  2. Add the two hashes to the policy. script-src 'self' 'sha256-XdpMs9OeHqoB7EJADagtz2B/g2p8FQ7EfWKEVjv/Jlc=' 'sha256-kidb3oKMTWYevFgKdLd4IWn/PUpAMSd07tXVqZc6FPg='. Correct, but the hashes then have to be kept in step with the script bodies by hand, and nothing fails loudly when they drift.

'unsafe-inline' would also work and should not be used: it would allow every inline script in order to fix two known ones.

Step-by-step reproduction instructions
  1. Launch the app, packaged or in development.
  2. Open the app log: Help > Open App Log, or read %APPDATA%\electron-setup-wordpress-core\logs\wordpress-contributor-toolkit-windows.log.
  3. Look at the first few lines of the session.

Two CSP violations appear on every launch, naming the two hashes above.

To see the consequence rather than the symptom, cause an unhandled promise rejection in the renderer that React does not catch. Nothing about it reaches the log.

Screenshots, screen recording, or logs

[2026-08-20 01:57:24.918] [error] Executing inline script violates the following Content Security Policy
directive 'default-src 'self''. Either the 'unsafe-inline' keyword, a hash
('sha256-XdpMs9OeHqoB7EJADagtz2B/g2p8FQ7EfWKEVjv/Jlc='), or a nonce ('nonce-...') is required to enable
inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a
fallback. The action has been blocked.

[2026-08-20 01:57:24.919] [error] Executing inline script violates the following Content Security Policy
directive 'default-src 'self''. Either the 'unsafe-inline' keyword, a hash
('sha256-kidb3oKMTWYevFgKdLd4IWn/PUpAMSd07tXVqZc6FPg='), or a nonce ('nonce-...') is required to enable
inline execution. Note also that 'script-src' was not explicitly set, so 'default-src' is used as a
fallback. The action has been blocked.

Operating system

Windows

App version

1.1.0-dev.6 (a local build of trunk at 855bfcc)

Please confirm that you have searched existing issues in the repo.
  • Yes

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with the inline handlers in src/renderer/index.html and the module-scope entry point in src/renderer/index.jsx. Verify the CSP violations by launching the app and checking the app log, then ensure renderer errors and unhandled promise rejections reach console.error without weakening the policy or leaving the launch-time violations.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript
Domain
desktop, security
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
82/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.