fix(ap): filter self-sent Create activities from inbox to prevent Anonymous self-mentions
Deploy Indiekit Server / deploy (push) Successful in 1m45s

handleCreate in inbox-handlers.js had no guard against activities from the
local actor itself. When svemagie.net posts mentioned itself (e.g. syndication
loopback or self-tagging), the Create arrived in the inbox, matched the Mention
tag check for ourActorUrl, and was stored as an inbound notification. The actor
could not be resolved (hairpin NAT) so the display name was 'Anonymous'.

Fix: patch-ap-inbox-self-mention-filter.mjs adds a self-exclusion gate:
  if (actorUrl && actorUrl === ourActorUrl) return;
...immediately after ourActorUrl is derived (before DM/reply/mention/timeline
branches). Defense-in-depth: mention-tag condition also gets actorUrl !== ourActorUrl.
This commit is contained in:
Sven
2026-05-29 09:34:06 +02:00
parent 6dbbc183e3
commit 521bc04c4e
2 changed files with 103 additions and 0 deletions
+3
View File
@@ -460,6 +460,9 @@ The Bluesky `listNotifications` API paginates backwards in time — using the cu
**`patch-conversations-bluesky-self-filter.mjs`**
Self-interactions from the blog's own Bluesky account (likes, reposts, replies) appeared as inbound interactions in the conversations endpoint. Two-pronged fix: (1) the scheduler skips storing any notification whose author handle matches `BLUESKY_IDENTIFIER`; (2) the conversations controller filters out items where the author URL matches the site owner's Bluesky profile URL.
**`patch-ap-inbox-self-mention-filter.mjs`**
Self-sent Create activities (posts from the local AP actor mentioning itself) appeared as inbound "mention" notifications, displayed as "Anonymous" because the own actor URL is unreachable from inside the jail (hairpin NAT). Root cause: `handleCreate` in `inbox-handlers.js` had no self-exclusion guard. Fix: insert `if (actorUrl === ourActorUrl) return` immediately after `ourActorUrl` is derived, so all processing for self-sent creates is skipped. Defense-in-depth: also guard the mention-tag branch with `actorUrl !== ourActorUrl`.
### Files
**`patch-endpoint-files-upload-route.mjs`**
@@ -0,0 +1,100 @@
/**
* 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",
oldSnippet: ` if (tag.type === "Mention" && tag.href?.href === ourActorUrl) {`,
newSnippet: ` if (tag.type === "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)`,
);
}