frappe / frappe/toolbox

Two dependencies block CSP enforcement: a webpack eval polyfill and an unused socket.io client

Open
#266 1 comment 0 reactions 0 assignees View on GitHub
enhancement
Dominant language
JavaScript
Stars
5
Forks
1
Avg merge
1h 25m
Merged PRs (30d)
1

Description

The new report-only Content-Security-Policy (`toolbox/security_headers.py`) asks the browser what it would block. Walking all 37 routes produced two kinds of report, from two different dependencies. Neither is Toolbox's own code, and together they are what stops the policy being enforced.

## 1. An `eval`, from a webpack polyfill inside the icon chunk

```
script-src blocked "eval"
file assets/iconString-.js (113 kB)
code n = n || Function("return this")() || (0,eval)("this")
```

That is webpack's `buildin/global.js` shim, shipped pre-bundled inside a frappe-ui dependency. `(function(){return this})()` returns undefined under a module's strict mode, so it falls through to `Function(...)` and then to `eval`, and it runs at module load whether or not an icon is drawn.

Note the entry chunk has a *similar-looking* shim from the bundler — `typeof self < "u" ? self : ... : Function("return this")()` — but `self` exists in a browser, so that branch never evaluates and it produces no report. The icon chunk is the one that fires.

## 2. Connections to a realtime server, from a socket.io client nothing uses

```
connect-src blocked http://:9000/socket.io/?EIO=4&transport=polling
blocked ws://:9000/socket.io/?EIO=4&transport=websocket
```

frappe-ui starts a socket.io client through `app.use(FrappeUI, ...)` in `frontend/src/main.js`. Toolbox has no realtime feature: no accounts, no live data, no realtime events in `hooks.py`, and the app never subscribes to anything. The connection fails quietly on every page load and retries.

## Why they are worth removing rather than allowing

The obvious "fix" is to add `unsafe-eval` and widen `connect-src`. That would be the wrong way round: stopping a page evaluating strings is most of what a policy is for, and neither dependency is doing work the product needs.

Removing them also buys:

- **A failed connection on every page load** goes away, with its polling, upgrade and retries.
- **Console noise** goes away. `frontend/e2e/fixtures.js` discounts `/socket.io/` in four separate listeners because each engine reports the failure differently.
- **Bundle weight.** The icon chunk is 113 kB, and socket.io-client plus engine.io-parser sit in the entry chunk rather than a lazy one.

## How to confirm a fix

`frontend/e2e/csp.spec.js` records both as known today and fails on anything new. It prints how many known reports it saw — 249 across the 37 routes at the time of writing. When that reaches zero, drop `KNOWN_VIOLATIONS` from the spec and move the header in `security_headers.py` from `Content-Security-Policy-Report-Only` to `Content-Security-Policy`.

## Not urgent

Nothing is broken for a visitor. Both fail quietly and every page works. This is about being able to enforce a policy, and about not shipping two dependencies the product does not use.

Contributor guide

No contributing guide indexed for this repository

Research direction

Start with frontend/src/main.js and run frontend/e2e/csp.spec.js to reproduce the known reports. Check how frappe-ui initializes the socket.io client and how the icon chunk receives the webpack polyfill, then remove the unused dependencies without widening the policy. Done means the known CSP reports reach zero, the KNOWN_VIOLATIONS list is removed, and security_headers.py enforces Content-Security-Policy.

Written by the indexing model from the issue text.

Assessment

Tech stack
javascript, webpack
Domain
frontend, security
Issue type
Refactor
Difficulty
3/5
Estimated time
1-2 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
74/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.