merge: upstream c1a6f7e — Fedify 2.1.0, 5 FEPs, security/perf audit, v3.9.x

Upstream commits merged (0820067..c1a6f7e):
- Fedify 2.1.0 upgrade (FEP-5feb, FEP-f1d5/0151, FEP-4f05 Tombstone,
  FEP-3b86 Activity Intents, FEP-8fcf Collection Sync)
- Comprehensive security/perf audit: XSS/CSRF fixes, OAuth scopes,
  rate limiting, secret hashing, token expiry/rotation, SSRF fix
- Architecture refactoring: syndicator.js, batch-broadcast.js,
  init-indexes.js, federation-actions.js; index.js -35%
- CSS split into 15 feature-scoped files + reader-interactions.js
- Mastodon API status creation: content-warning field, linkify fix

Fork-specific resolutions:
- syndicator.js: added addTimelineItem mirror for own Micropub posts
- syndicator.js: fixed missing await on jf2ToAS2Activity (async fn)
- statuses.js: kept DM path, pin/unpin routes, edit post route,
  processStatusContent (used by edit), addTimelineItem/lookupWithSecurity/
  addNotification imports
- compose.js: kept addNotification + added federation-actions.js imports
- enrich-accounts.js: kept cache-first approach for avatar updates
- ap-notification-card.njk: kept DM lock icon (🔒) for isDirect mentions
This commit is contained in:
svemagie
2026-03-27 09:30:34 +01:00
60 changed files with 5672 additions and 1833 deletions

View File

@@ -28,7 +28,7 @@ export function sanitizeContent(html) {
},
allowedSchemes: ["http", "https", "mailto"],
allowedSchemesByTag: {
img: ["http", "https", "data"]
img: ["http", "https"]
}
});
}
@@ -46,11 +46,16 @@ export function replaceCustomEmoji(html, emojis) {
if (!emojis?.length || !html) return html;
let result = html;
for (const { shortcode, url } of emojis) {
// Validate URL is HTTP(S) only — reject data:, javascript:, etc.
if (!url || (!url.startsWith("https://") && !url.startsWith("http://"))) continue;
// Escape HTML special characters in URL and shortcode to prevent attribute injection
const safeUrl = url.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const safeShortcode = shortcode.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
const escaped = shortcode.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
const pattern = new RegExp(`:${escaped}:`, "g");
result = result.replace(
pattern,
`<img class="ap-custom-emoji" src="${url}" alt=":${shortcode}:" title=":${shortcode}:" draggable="false">`,
`<img class="ap-custom-emoji" src="${safeUrl}" alt=":${safeShortcode}:" title=":${safeShortcode}:" draggable="false">`,
);
}
return result;
@@ -349,20 +354,11 @@ export async function extractObjectData(object, options = {}) {
// Quote URL — Fedify reads quoteUrl / _misskey_quote / quoteUri
const quoteUrl = object.quoteUrl?.href || "";
// Interaction counts — extract from AP Collection objects
// Interaction counts — not fetched at ingest time. The three collection
// fetches (getReplies, getLikes, getShares) each trigger an HTTP round-trip
// for counts that are ephemeral and stale moments after fetching. Removed
// per audit M11 to save 3 network calls per inbound activity.
const counts = { replies: null, boosts: null, likes: null };
try {
const replies = await object.getReplies?.(loaderOpts);
if (replies?.totalItems != null) counts.replies = replies.totalItems;
} catch { /* ignore — collection may not exist */ }
try {
const likes = await object.getLikes?.(loaderOpts);
if (likes?.totalItems != null) counts.likes = likes.totalItems;
} catch { /* ignore */ }
try {
const shares = await object.getShares?.(loaderOpts);
if (shares?.totalItems != null) counts.boosts = shares.totalItems;
} catch { /* ignore */ }
// Replace custom emoji :shortcode: in content with inline <img> tags.
// Applied after sanitization — these are trusted emoji from the post's tags.