Index: src/routes/posts.js
===================================================================
--- src/routes/posts.js	(revision ad6f62af71f052ed32116666c62fefa7a06be1ac)
+++ src/routes/posts.js	(revision d9ad6c564eccd0e4c23c3d8c2c8a32334f2443bc)
@@ -920,5 +920,5 @@
   const append = req.query.append === '1';
   const offset = Math.max(0, parseInt(req.query.offset, 10) || 0);
-  const page = site ? ActivityPubService.getMessages(site.slug, FEED_PAGE + 1, offset) : [];
+  const page = gateEmbeds(site, site ? ActivityPubService.getMessages(site.slug, FEED_PAGE + 1, offset) : []);
   const hasMore = page.length > FEED_PAGE;
   const items = page.slice(0, FEED_PAGE);
@@ -1084,4 +1084,20 @@
 }
 
+/**
+ * FEP-633c §5.3-style gated feature: may this account see previews of links
+ * that point OUTSIDE the fediverse? For a ward that is the guardians' call.
+ *
+ * Applied at SERVE time on every surface, the way the app's inbox read already
+ * does it (routes/activitypub.js): a card the client merely hides has still
+ * been delivered.
+ */
+function gateEmbeds(site, rows) {
+  if (!site || !rows.length) return rows;
+  let isWard = false;
+  try { isWard = Guardianship.listGuardians(site.slug).length > 0; } catch { /* no relations yet */ }
+  if (Guardianship.externalEmbedsAllowed(site.external_embeds, isWard)) return rows;
+  return rows.map((r) => (r && r.embed_json ? { ...r, embed_json: null } : r));
+}
+
 router.get('/news', requireSiteManager, (req, res) => {
   const site = res.locals.site;
@@ -1090,5 +1106,5 @@
   const cspOrigins = new Set();
   // Fetch one extra to know whether a "Load more" button belongs on this page.
-  const rows = site ? ActivityPubService.getTimeline(site.slug, FEED_PAGE + 1, offset) : [];
+  const rows = gateEmbeds(site, site ? ActivityPubService.getTimeline(site.slug, FEED_PAGE + 1, offset) : []);
   const hasMore = rows.length > FEED_PAGE;
   const timeline = rows.slice(0, FEED_PAGE).map((p) => {
