From e70e990a1aa932a3f994b2205bacc2f35730aa01 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Thu, 22 May 2025 14:13:41 -0400 Subject: [PATCH] feat: restrict access to ap.probe method to registered users, add rate limiting protection --- src/activitypub/index.js | 22 ++++++++++++++++++++++ src/controllers/activitypub/index.js | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/activitypub/index.js b/src/activitypub/index.js index c87a0654f3..4067405080 100644 --- a/src/activitypub/index.js +++ b/src/activitypub/index.js @@ -27,6 +27,9 @@ const probeCache = ttl({ max: 500, ttl: 1000 * 60 * 60, // 1 hour }); +const probeRateLimit = ttl({ + ttl: 1000 * 3, // 3 seconds +}); const ActivityPub = module.exports; @@ -506,6 +509,13 @@ ActivityPub.probe = async ({ uid, url }) => { * - Returns a relative path if already available, true if not, and false otherwise. */ + // Disable on config setting; restrict lookups to HTTPS-enabled URLs only + const { activitypubProbe } = meta.config; + const { protocol } = new URL(url); + if (!activitypubProbe || protocol !== 'https:') { + return false; + } + // Known resources const [isNote, isMessage, isActor, isActorUrl] = await Promise.all([ posts.exists(url), @@ -541,6 +551,17 @@ ActivityPub.probe = async ({ uid, url }) => { } } + // Guests not allowed to use expensive logic path + if (!uid) { + return false; + } + + // One request allowed every 3 seconds (configured at top) + const limited = probeRateLimit.get(uid); + if (limited) { + return false; + } + // Cached result if (probeCache.has(url)) { return probeCache.get(url); @@ -572,6 +593,7 @@ ActivityPub.probe = async ({ uid, url }) => { return false; } try { + probeRateLimit.set(uid, true); return await checkHeader(meta.config.activitypubProbeTimeout || 2000); } catch (e) { if (e.name === 'TimeoutError') { diff --git a/src/controllers/activitypub/index.js b/src/controllers/activitypub/index.js index 36054d6616..0e7112ddfd 100644 --- a/src/controllers/activitypub/index.js +++ b/src/controllers/activitypub/index.js @@ -31,7 +31,7 @@ Controller.fetch = async (req, res, next) => { if (typeof result === 'string') { return helpers.redirect(res, result); } else if (result) { - const { id, type } = await activitypub.get('uid', req.uid || 0, url.href); + const { id, type } = await activitypub.get('uid', req.uid, url.href); switch (true) { case activitypub._constants.acceptedPostTypes.includes(type): { return helpers.redirect(res, `/post/${encodeURIComponent(id)}`);