---
name: goclaw-csp-graph-fix
description: "Fix GoClaw Knowledge Graph CSP issue - add worker-src blob: and unsafe-eval via nginx sub_filter (Sigma.js Web Workers blocked by strict CSP)"
version: 1.0.0
author: Hermes Agent
tags: [goclaw, nginx, csp, knowledge-graph, sigma-js]
---

# GoClaw CSP Fix for Knowledge Graph

## Problem

GoClaw's Knowledge Graph doesn't render. Browser console shows:

```
Refused to create a worker from 'blob:...'
because it violates Content Security Policy
Failed to construct 'Worker'
```

GoClaw embeds a strict CSP meta-tag in its HTML:
```html
<meta http-equiv="Content-Security-Policy" content="script-src 'self'; ...">
```

Sigma.js (graph rendering lib) uses `blob:` URLs to create Web Workers, but this CSP blocks `blob:` in both `script-src` and `worker-src`.

## Why add_header doesn't work

Adding `add_header Content-Security-Policy "worker-src 'self' blob:"` in nginx **won't fix it** — when both a `<meta>` tag and an HTTP header CSP exist, **both** must allow the resource (most restrictive wins). The meta-tag still blocks.

## Solution: nginx sub_filter

Replace the CSP meta-tag in the HTML response body before it reaches the browser.

### 1. Remove old meta-tag + inject new one

```nginx
location / {
    # Disable upstream gzip so sub_filter can read the raw HTML
    proxy_set_header Accept-Encoding "";
    
    # Remove old CSP meta-tag (both lowercase and mixed case)
    sub_filter '<meta http-equiv="content-security-policy"' '<meta data-removed="csp"';
    sub_filter '<meta http-equiv="Content-Security-Policy"' '<meta data-removed="csp"';
    
    # Inject new CSP before </head>
    sub_filter '</head>' '<meta http-equiv="Content-Security-Policy" content="default-src \'self\'; script-src \'self\' \'unsafe-inline\' \'unsafe-eval\' blob:; worker-src \'self\' blob:; connect-src \'self\' https: wss:; img-src \'self\' data: blob:; style-src \'self\' \'unsafe-inline\'; font-src \'self\' data:;"></head>';
    
    sub_filter_once off;
    sub_filter_types text/html;
    
    # ... rest of proxy config
}
```

### 2. Critical detail — disable gzip

`sub_filter` operates on **uncompressed** response body. If the upstream (GoClaw) sends gzipped HTML, sub_filter silently does nothing.

```nginx
proxy_set_header Accept-Encoding "";
```

### 3. Reload nginx

```bash
nginx -t && systemctl reload nginx
# or: nginx -t && nginx -s reload
```

## The exact CSP GoClaw ships with (for reference)

```
default-src 'self';
script-src 'self' 'unsafe-eval' blob:;
worker-src 'self' blob:;
style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net;
font-src 'self' data: https://fonts.gstatic.com https://cdn.jsdelivr.net;
img-src 'self' data: blob:;
media-src 'self' blob:;
connect-src 'self' ws: wss:;
base-uri 'self';
```

## Verification

1. Open browser DevTools → Network tab
2. Load GoClaw page
3. Find the HTML document request
4. Check Response headers — Content-Encoding should NOT be gzip
5. Check Response body — the old CSP `<meta>` should be gone, replaced with `data-removed="csp"`
6. Open Console — no more "Refused to create a worker from 'blob:'" errors
7. Navigate to Knowledge Graph — should render

## Browser cache

After fixing CSP on the server, **the browser caches the old HTML**. The fix may not appear immediately:
- **Hard refresh:** `Ctrl+F5` (Win) or `Cmd+Shift+R` (Mac)
- **Disable cache:** F12 → Network → check "Disable cache" → reload

## Common pitfalls

| Issue | Cause | Fix |
|-------|-------|------|
| sub_filter doesn't fire | gzip compression | Add `proxy_set_header Accept-Encoding ""` |
| "Refused to create a worker" persists | Missing `worker-src blob:` | Add `worker-src 'self' blob:` to CSP |
| Graph still blank after CSP fix | GoClaw chart width/height bug | This is a separate frontend issue (width=-1), not CSP |

## Model IDs on OpenRouter

If configuring GoClaw's models via OpenRouter, valid DeepSeek model IDs (cheapest first):

| ID | Price / 1M input | Context |
|---|---|---|
| `deepseek/deepseek-v4-flash` | $0.0983 | 1,048,576 |
| `deepseek/deepseek-chat-v3-0324` | $0.20 | 163,840 |
| `deepseek/deepseek-chat` | $0.2288 | 131,072 |

`deepseek/deepseek-chat-flash` is **not** a valid OpenRouter model ID.

## Relevant GoClaw config files

- nginx: `/etc/nginx/sites-available/goclaw`
- GoClaw config: `/app/data/config.json` (inside Docker)
- GoClaw data volume: `goclaw-data` → `/app/data/`
