Skip to content

Commit b2ef232

Browse files
committed
Cached 410s for 1h to prevent amplification attacks
ref no-issue - Without this cache, an attacker can send many activities to our inbox referencing actor URLs that return 410/404, causing our server to make an outbound HTTP request for each one. This effectively turns our server into a traffic amplifier. This cache prevents that by remembering URLs that returned 410/404 for 1 hour and failing immediately on subsequent requests without making a network call.
1 parent 150b53a commit b2ef232

File tree

1 file changed

+47
-1
lines changed

1 file changed

+47
-1
lines changed

src/configuration/registrations.ts

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import { createFederation, type KvStore } from '@fedify/fedify';
1+
import {
2+
createFederation,
3+
FetchError,
4+
getDocumentLoader,
5+
type KvStore,
6+
kvCache,
7+
} from '@fedify/fedify';
28
import { RedisKvStore } from '@fedify/redis';
39
import type { PubSub } from '@google-cloud/pubsub';
410
import { getLogger, type Logger } from '@logtape/logtape';
@@ -237,6 +243,10 @@ export function registerDependencies(
237243
container.register(
238244
'fedify',
239245
asFunction((fedifyKv: KvStore, queue: GCloudPubSubPushMessageQueue) => {
246+
// Cache for permanently failed URLs (410 Gone, 404 Not Found)
247+
const goneUrlCache = new Map<string, number>();
248+
const GONE_CACHE_TTL_MS = 60 * 60 * 1000; // 1 hour
249+
240250
return createFederation<ContextData>({
241251
kv: fedifyKv,
242252
queue: process.env.USE_MQ === 'true' ? queue : undefined,
@@ -251,6 +261,42 @@ export function registerDependencies(
251261
['development', 'testing'].includes(
252262
process.env.NODE_ENV || '',
253263
),
264+
documentLoaderFactory(opts) {
265+
const loader = kvCache({
266+
loader: getDocumentLoader({
267+
allowPrivateAddress: opts?.allowPrivateAddress,
268+
userAgent: opts?.userAgent,
269+
}),
270+
kv: fedifyKv,
271+
prefix: ['_fedify', 'remoteDocument'],
272+
});
273+
274+
return async (url: string, options?: unknown) => {
275+
const cachedAt = goneUrlCache.get(url);
276+
if (
277+
cachedAt !== undefined &&
278+
Date.now() - cachedAt < GONE_CACHE_TTL_MS
279+
) {
280+
throw new FetchError(url, `HTTP 410: ${url}`);
281+
}
282+
283+
try {
284+
return await loader(
285+
url,
286+
options as Parameters<typeof loader>[1],
287+
);
288+
} catch (error) {
289+
if (
290+
error instanceof FetchError &&
291+
(error.message.includes('HTTP 410') ||
292+
error.message.includes('HTTP 404'))
293+
) {
294+
goneUrlCache.set(url, Date.now());
295+
}
296+
throw error;
297+
}
298+
};
299+
},
254300
firstKnock: 'draft-cavage-http-signatures-12',
255301
});
256302
}).singleton(),

0 commit comments

Comments
 (0)