proxy + self-host og:image and favicon for link cards
Created by: jedmund
Summary
- adds
GET /api/og-image-proxy?url=<encoded>that streams remote image bytes with strict guards (http(s) only, private/loopback hosts blocked, image content-type only, 10 MB ceiling, 8s timeout, 1-day cache) -
GET /api/og-metadatanow rewrites both theimageandfaviconfields to proxy urls before returning -
enrichUrlEmbedsnow downloads the favicon in parallel with the main image and stores it as a Media row withfaviconMediaIdon the urlEmbed node - legacy posts with foreign favicon urls self-heal on re-save (same pattern as the image path)
Why
after #82 the editor still showed raw third-party og:image urls during editing, which our img-src 'self' ... CSP blocks. Same issue hits legacy posts on public pages saved before #82, and the favicon path had the exact same hotlink problem that the image path had.
Deployment note
#82's migration (20260422000000_add_media_link_card_flag) must be applied before this change is useful — without it, save-time Media.create() throws because isLinkCardImage doesn't exist in the DB, so downloads silently fall through to the hotlink path.
npm run db:deploy
Test plan
-
editor: paste a url, confirm both the preview image and favicon load via /api/og-image-proxy?url=...and no CSP violation fires -
save the post, confirm content.urlEmbed.attrs.imageand.faviconare both/api/media/<id>urls -
confirm new Mediarows for both haveisLinkCardImage = true(so they stay hidden from the library) -
open a post saved before #82 (raw hotlinks in attrs) — proxy handles the render; re-saving upgrades both to hosted urls -
broken/403 source favicon → save still succeeds; attrs keep whatever was there as fallback -
/api/og-image-proxy?url=http://localhost:22→ 400 disallowed -
/api/og-image-proxy?url=file:///etc/passwd→ 400 unsupported scheme -
/api/og-image-proxy?url=https://example.com/huge.pngwithcontent-length > 10MB→ 413 -
/api/og-image-proxy?url=https://example.com/page.html(non-image) → 415