Files
indiekit-server/scripts/patch-ap-allow-private-address.mjs
Sven e3765948e5 fix(patches): silence false warnings for already-applied patches
- patch-ap-allow-private-address: detect allowPrivateAddress in source
- patch-endpoint-posts-ai-cleanup: detect any AI field key variant

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-16 20:32:24 +01:00

104 lines
3.5 KiB
JavaScript

/**
* Patch: allow Fedify to fetch URLs that resolve to private IP addresses.
*
* Root cause:
* blog.giersig.eu resolves to 10.100.0.10 (a private RFC-1918 address)
* from within the home network where the indiekit server runs. When a
* remote Fediverse server sends an activity (Like, Announce, etc.) whose
* object URL points to blog.giersig.eu, Fedify tries to dereference that
* URL to validate the object. Its built-in SSRF guard calls
* validatePublicUrl(), sees the resolved IP is private, and throws:
*
* Disallowed private URL: 'https://blog.giersig.eu/likes/ed6d1/'
* Invalid or private address: 10.100.0.10
*
* This causes WebFinger lookups and lookupObject() calls for own-site URLs
* to fail, producing ERR-level noise in the log and breaking thread loading
* in the ActivityPub reader for local posts.
*
* Fix:
* Pass allowPrivateAddress: true to createFederation. This disables the
* SSRF IP check so Fedify can dereference own-site URLs. The network-level
* solution (split-horizon DNS returning the public IP inside the LAN) is
* cleaner but requires router/DNS changes outside the codebase.
*/
import { access, readFile, writeFile } from "node:fs/promises";
const candidates = [
"node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js",
"node_modules/@indiekit/indiekit/node_modules/@rmdes/indiekit-endpoint-activitypub/lib/federation-setup.js",
];
const MARKER = "// allow private address fix";
const OLD_SNIPPET = ` const federation = createFederation({
kv,
queue,
// Accept signatures up to 12 h old. // signature time window fix
// Mastodon retries failed deliveries with the original signature, which
// can be hours old by the time the delivery succeeds.
signatureTimeWindow: { hours: 12 },
});`;
const NEW_SNIPPET = ` const federation = createFederation({
kv,
queue,
// Accept signatures up to 12 h old. // signature time window fix
// Mastodon retries failed deliveries with the original signature, which
// can be hours old by the time the delivery succeeds.
signatureTimeWindow: { hours: 12 },
// Allow fetching own-site URLs that resolve to private IPs. // allow private address fix
// blog.giersig.eu resolves to 10.100.0.10 on the home LAN. Without this,
// Fedify's SSRF guard blocks lookupObject() / WebFinger for own posts.
allowPrivateAddress: true,
});`;
async function exists(filePath) {
try {
await access(filePath);
return true;
} catch {
return false;
}
}
let checked = 0;
let patched = 0;
for (const filePath of candidates) {
if (!(await exists(filePath))) {
continue;
}
checked += 1;
const source = await readFile(filePath, "utf8");
if (source.includes(MARKER) || source.includes("allowPrivateAddress")) {
continue;
}
if (!source.includes(OLD_SNIPPET)) {
console.log(`[postinstall] patch-ap-allow-private-address: snippet not found in ${filePath} — skipping`);
continue;
}
const updated = source.replace(OLD_SNIPPET, NEW_SNIPPET);
if (updated === source) {
continue;
}
await writeFile(filePath, updated, "utf8");
patched += 1;
console.log(`[postinstall] Applied patch-ap-allow-private-address to ${filePath}`);
}
if (checked === 0) {
console.log("[postinstall] patch-ap-allow-private-address: no target files found");
} else if (patched === 0) {
console.log("[postinstall] patch-ap-allow-private-address: already up to date");
} else {
console.log(`[postinstall] patch-ap-allow-private-address: patched ${patched}/${checked} file(s)`);
}