CSP: wasm loads via data: URI fetch (blocked by connect-src 'self') and init() offers no wasm path override
- Dominant language
- TypeScript
- Stars
- 2.8k
- Forks
- 172
- PR merge metrics
- No merged PRs in 30d
Description
## Context
Same evaluation as #187 — considering ghostty-web for the SSH/Kubernetes terminals in [Cloud Foundry Stratos](https://github.com/cloudfoundry/stratos), this time measuring startup under a Content-Security-Policy. Functionally the probe went well: rendering, canvas selection, and the `fit()` → `onResize` resize contract all worked. What did not survive is wasm loading under a strict-but-ordinary policy. Full measurements are recorded [here](https://github.com/cloudfoundry/stratos/issues/5705#issuecomment-5151849689).
## What happens under `connect-src 'self'`
Policy used (a fairly common strict baseline, and Stratos's shipped default):
```
default-src 'self'; script-src 'self'; connect-src 'self'; ...
```
With `ghostty-web@0.4.0`, `await init()` fails in three steps:
1. The first candidate the loader tries is the module URL — but in the published bundle the wasm is **inlined as a base64 `data:` URI**, so the module-relative URL *is* a `data:` URL. `fetch()` of a `data:` URL is subject to `connect-src`, and `'self'` blocks it. One CSP violation is logged per load.
2. The fallbacks `./ghostty-vt.wasm` and `/ghostty-vt.wasm` resolve against the page, not the module — 404 unless the deployment happens to serve the wasm at the document root.
3. `init()` takes no arguments, so there is no way to point the loader at the real file (which does ship in `dist/ghostty-vt.wasm`).
Result: `Error: Failed to fetch WASM: 404 Not Found`, and the terminal cannot start at all under a policy that plain xterm.js runs under.
Reading `lib/ghostty.ts`, the capability already exists one layer down: `Ghostty.load(wasmPath?)` accepts an explicit path and `defaultPaths` puts the module-relative URL first — the gap is only that `init()` does not expose it, and that bundling turns the module-relative default into a `data:` URI.
## Suggestions
1. **Plumb the path through `init()`** — `init(wasmPath?: string)` or `init({ wasmUrl })`, forwarding to `Ghostty.load()`. This alone unblocks any CSP-constrained embedder, and it looks like a one-line change given `load()` already takes it.
2. **Publish the bundle without inlining the wasm**, so the module-relative candidate resolves to the real `dist/ghostty-vt.wasm` file — or at least order the same-origin candidates before the `data:` fallback. Beyond CSP, the inlining also carries ~560KB of base64 in the JS bundle for a 413KB binary that ships in the package anyway, and even when loading eventually succeeds by another route, the `data:` attempt logs a CSP violation on every page load, which is noise for anyone running violation reporting.
3. **A docs note on `'wasm-unsafe-eval'`** — once the fetch succeeds, `WebAssembly.compile()` still requires `script-src` to include `'wasm-unsafe-eval'`. That one is inherent to running wasm under CSP rather than anything ghostty-web can fix, but a line in the README would save the next embedder the discovery cost.
For what it's worth, with the wasm served at the document root and `'wasm-unsafe-eval'` added, everything downstream passed cleanly in our probe — zero style violations, correct rendering, selection and resize all good. The loading path is the only blocker.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start in lib/ghostty.ts, comparing init() with Ghostty.load(wasmPath?) and its defaultPaths. Check how the published bundle and dist/ghostty-vt.wasm are produced, then review the README CSP guidance. Done means init() can load the shipped wasm under connect-src 'self' without a failing data: attempt, with tests or documented verification for the chosen loading path.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript, wasm
- Domain
- security, web-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 70/100