/** * Patch: filter out self-sent Create activities in the AP inbox. * * When the local AP actor sends a post that mentions itself (e.g. cross-posting * loops, self-tagging, or loopback delivery), handleCreate receives a Create * activity whose actor URL matches ourActorUrl. Without a self-exclusion guard * these are stored as inbound notifications (type "mention" or "reply") and * displayed as "Anonymous" because the own actor is unreachable via hairpin NAT. * * Fix: insert a self-exclusion gate immediately after ourActorUrl is derived. * Defense-in-depth: also add `actorUrl !== ourActorUrl` to the mention tag check * so the mention branch is safe even if the gate is ever moved or regressed. */ import { access, readFile, writeFile } from "node:fs/promises"; const candidates = [ "node_modules/@rmdes/indiekit-endpoint-activitypub/lib/inbox-handlers.js", "node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/inbox-handlers.js", ]; const patchSpecs = [ { name: "ap-inbox-self-create-gate", marker: "// [patch] ap-inbox-self-create-gate", oldSnippet: ` const ourActorUrl = ctx.getActorUri(handle).href; const followersUrl = ctx.getFollowersUri(handle)?.href || ""; if (computeVisibility(object, { ourActorUrl, followersUrl }) === "direct") {`, newSnippet: ` const ourActorUrl = ctx.getActorUri(handle).href; const followersUrl = ctx.getFollowersUri(handle)?.href || ""; // [patch] ap-inbox-self-create-gate // Never process activities sent by our own actor as inbound — they produce // spurious "Anonymous" notifications due to hairpin NAT blocking self-fetch. if (actorUrl && actorUrl === ourActorUrl) return; if (computeVisibility(object, { ourActorUrl, followersUrl }) === "direct") {`, }, { name: "ap-inbox-mention-self-guard", marker: "// [patch] ap-inbox-mention-self-guard", // Upstream fork (>= 32ea375) reads mention tags via getTags() and matches on // tag.constructor.name instead of tag.type. Anchor on the new line shape. oldSnippet: ` if (tag.constructor?.name === "Mention" && tag.href?.href === ourActorUrl) {`, newSnippet: ` if (tag.constructor?.name === "Mention" && tag.href?.href === ourActorUrl && actorUrl !== ourActorUrl) { // [patch] ap-inbox-mention-self-guard`, }, ]; async function exists(filePath) { try { await access(filePath); return true; } catch { return false; } } const checkedFiles = new Set(); const patchedFiles = new Set(); for (const spec of patchSpecs) { let foundAnyTarget = false; for (const filePath of candidates) { if (!(await exists(filePath))) continue; foundAnyTarget = true; checkedFiles.add(filePath); const source = await readFile(filePath, "utf8"); if (source.includes(spec.marker)) { continue; } if (!source.includes(spec.oldSnippet)) { console.warn(`[postinstall] ${spec.name}: oldSnippet not found in ${filePath} — upstream may have changed`); continue; } const updated = source.replace(spec.oldSnippet, spec.newSnippet); if (updated === source) continue; await writeFile(filePath, updated, "utf8"); patchedFiles.add(filePath); } if (!foundAnyTarget) { console.log(`[postinstall] ${spec.name}: no target files found`); } } if (checkedFiles.size === 0) { console.log("[postinstall] ap-inbox-self-mention-filter: no target files found"); } else if (patchedFiles.size === 0) { console.log("[postinstall] ap-inbox-self-mention-filter: patches already applied"); } else { console.log( `[postinstall] Patched ap-inbox-self-mention-filter in ${patchedFiles.size}/${checkedFiles.size} file(s)`, ); }