fix: match R2 URL pattern and blur non-R2 images (#477)

- Fix regex to match R2 URLs (pub-*.r2.dev/{id}.{ext})
- Blur non-R2 images by default as they could be injected via ATProto records

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude <noreply@anthropic.com>

authored by zzstoatzz.io Claude and committed by GitHub 715805c8 2a17485a

Changed files
+17 -4
frontend
+17 -4
frontend/src/lib/moderation.svelte.ts
··· 15 15 /** 16 16 * check if an image URL is flagged as sensitive. 17 17 * checks both the full URL and extracts image_id from R2 URLs. 18 + * also flags non-R2 images as sensitive since they could be injected. 18 19 */ 19 20 isSensitive(url: string | null | undefined): boolean { 20 21 if (!url) return false; ··· 22 23 // check full URL match 23 24 if (this.data.urls.has(url)) return true; 24 25 25 - // extract image_id from R2 URL pattern and check 26 - // R2 URLs look like: https://cdn.plyr.fm/images/{image_id}.webp 27 - const match = url.match(/\/images\/([^/.]+)\./); 28 - if (match && this.data.image_ids.has(match[1])) return true; 26 + // check if it's a known R2 URL pattern 27 + const isR2 = url.includes('r2.dev/') || url.includes('cdn.plyr.fm/'); 28 + 29 + if (!isR2) { 30 + // non-R2 images are treated as sensitive - could be injected 31 + return true; 32 + } 33 + 34 + // extract image_id from R2 URL patterns: 35 + // - https://pub-*.r2.dev/{image_id}.{ext} 36 + // - https://cdn.plyr.fm/images/{image_id}.{ext} 37 + const r2Match = url.match(/r2\.dev\/([^/.]+)\./); 38 + if (r2Match && this.data.image_ids.has(r2Match[1])) return true; 39 + 40 + const cdnMatch = url.match(/\/images\/([^/.]+)\./); 41 + if (cdnMatch && this.data.image_ids.has(cdnMatch[1])) return true; 29 42 30 43 return false; 31 44 }