Skip to main content

max / balanced_breakfast

sanitize: block remote resource loads in feed bodies; tighten CSP Rendering a feed item auto-fetched any remote <img> src, beaconing the reader's IP to the feed publisher -- a tracking-pixel leak at odds with the privacy pitch. The sanitizer now strips remote (http/https/protocol- relative) src/srcset/poster while leaving click-only href intact. Also drop script-src 'unsafe-inline' from the CSP; all scripts are external files, no inline blocks or on* handlers exist. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-11 01:50 UTC
Commit: 60fc35af0b0b468cf5dd40400a887aae6d11b4a1
Parent: a99def1
2 files changed, +19 insertions, -4 deletions
@@ -76,9 +76,24 @@
76 76 continue;
77 77 }
78 78
79 - // Strip dangerous URL schemes in href and src.
80 - // Normalize by removing whitespace/control chars to defeat obfuscation.
81 - if (lower === 'href' || lower === 'src') {
79 + // Auto-loading resource attributes (src/srcset/poster) fetch on
80 + // render with no user action, so a remote URL here beacons the
81 + // reader's IP to the feed publisher (tracking pixel). Strip both
82 + // dangerous schemes and any remote (http/https/protocol-relative)
83 + // target. Normalize away whitespace/control chars to defeat
84 + // obfuscation. Relative URLs resolve against the app origin and
85 + // don't leak, so they're left intact.
86 + if (lower === 'src' || lower === 'srcset' || lower === 'poster') {
87 + const value = (el.getAttribute(name) || '').replace(/[\s\x00-\x1f]/g, '').toLowerCase();
88 + if (value.startsWith('javascript:') || value.startsWith('data:') || value.startsWith('vbscript:') ||
89 + value.startsWith('http:') || value.startsWith('https:') || value.startsWith('//')) {
90 + el.removeAttribute(name);
91 + }
92 + continue;
93 + }
94 +
95 + // Strip dangerous URL schemes in href (links only load on click).
96 + if (lower === 'href') {
82 97 const value = (el.getAttribute(name) || '').replace(/[\s\x00-\x1f]/g, '').toLowerCase();
83 98 if (value.startsWith('javascript:') || value.startsWith('data:') || value.startsWith('vbscript:')) {
84 99 el.removeAttribute(name);
@@ -21,7 +21,7 @@
21 21 }
22 22 ],
23 23 "security": {
24 - "csp": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; connect-src ipc: http://ipc.localhost http://127.0.0.1"
24 + "csp": "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' https: data:; connect-src ipc: http://ipc.localhost http://127.0.0.1"
25 25 }
26 26 },
27 27 "bundle": {