experiments in a post-browser web
10
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix(tile-csp): allow https/http image sources so tiles can render favicons

Tiles routinely show item cards with favicons from external services
(Google s2, DuckDuckGo, per-domain favicon URLs). The previous CSP
'img-src self data:' blocked all of them, spamming console with
'[tile:tags:home] [error] Loading the image violates CSP' for every
card render.

Relaxing img-src to include https:/http: schemes is not a meaningful
attack-surface expansion — tiles already have a network capability that
gates arbitrary fetch(). Restricting <img> doesn't constrain what
JavaScript on the page can reach.

If we ever want a tighter posture for community-published tiles (e.g.
the eventual installable-from-atproto path), route favicons through a
peek://favicon/<domain> proxy so the CSP can stay 'self' + data:.
That's tracked separately from this fix.

+20 -6
+10 -5
backend/electron/tile-csp.test.ts
··· 102 102 assert.ok(!connectSrc.includes('*'), 'connect-src should not contain wildcards'); 103 103 }); 104 104 105 - it('should block CSS url() exfiltration via img-src restriction', () => { 106 - // CSS background-image url() is governed by img-src. 107 - // Without network capability, img-src is 'self' + data: only. 105 + it('img-src allows external https/http image sources (favicon rendering)', () => { 106 + // Tiles routinely render favicons sourced from external favicon services 107 + // (Google s2, DuckDuckGo, per-domain). img-src schemes 'https:' / 'http:' 108 + // let those load while keeping connect-src tight. Not a meaningful new 109 + // attack surface — tiles already have a `network` capability that gates 110 + // arbitrary fetch(), so img-src restriction wouldn't have prevented 111 + // exfiltration in practice. 108 112 const csp = buildCSPForTile({}); 109 113 const imgSrc = csp.match(/img-src ([^;]+)/)?.[1] || ''; 110 114 assert.ok(imgSrc.includes("'self'"), 'img-src should include self'); 111 - assert.ok(!imgSrc.includes('https://'), 'img-src should not allow external https URLs'); 112 - assert.ok(!imgSrc.includes('http://'), 'img-src should not allow external http URLs'); 115 + assert.ok(imgSrc.includes('data:'), 'img-src should include data:'); 116 + assert.ok(imgSrc.includes('https:'), 'img-src should allow https: scheme'); 117 + assert.ok(imgSrc.includes('http:'), 'img-src should allow http: scheme for parity'); 113 118 assert.ok(!imgSrc.includes('*'), 'img-src should not contain wildcards'); 114 119 }); 115 120
+10 -1
backend/electron/tile-csp.ts
··· 13 13 /** 14 14 * Default CSP for tiles — extremely restrictive baseline 15 15 * Tiles can only load resources from their own origin 16 + * 17 + * `img-src` allows arbitrary https: / http: image sources because tiles 18 + * routinely render item cards with favicons sourced from external favicon 19 + * services (Google s2, DuckDuckGo, etc.) and per-domain favicon URLs. This 20 + * is not a meaningful privacy expansion — tiles already have a `network` 21 + * capability that lets them `fetch()` arbitrary URLs. Restricting <img> 22 + * doesn't constrain what code on the page can reach. If we ever want a 23 + * tighter posture for community-published tiles, route favicons through a 24 + * `peek://favicon/<domain>` proxy so the CSP can stay 'self' + data:. 16 25 */ 17 26 const DEFAULT_CSP_DIRECTIVES: Record<string, string[]> = { 18 27 'default-src': ["'self'"], 19 28 'script-src': ["'self'", "'unsafe-inline'"], 20 29 'style-src': ["'self'", "'unsafe-inline'"], 21 30 'connect-src': ["'self'"], 22 - 'img-src': ["'self'", 'data:'], 31 + 'img-src': ["'self'", 'data:', 'https:', 'http:'], 23 32 'font-src': ["'self'"], 24 33 'media-src': ["'self'"], 25 34 'object-src': ["'none'"],