From ef91184d7579c9b8343590971107de854a770cfa Mon Sep 17 00:00:00 2001 From: Leafus Date: Sun, 24 Aug 2025 04:17:34 +0200 Subject: [PATCH] feat: validate remote actors on inbox --- packages/backend/src/core/QueueService.ts | 18 ++++++++ .../src/core/activitypub/ApInboxService.ts | 44 +++++++++++++++++++ 2 files changed, 62 insertions(+) diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index 361e636662..ac75dcf082 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -211,6 +211,24 @@ export class QueueService { @bindThis public inbox(activity: IActivity, signature: httpSignature.IParsedSignature) { + // Basic validation to prevent obvious invalid activities + if (!activity || !activity.actor || typeof activity.actor !== 'string') { + throw new Error('Invalid activity: missing or invalid actor'); + } + + // Validate actor URI format + try { + new URL(activity.actor); + } catch { + throw new Error(`Invalid actor URI format: ${activity.actor}`); + } + + // Check if the actor domain is valid + const actorHost = new URL(activity.actor).hostname; + if (!actorHost || actorHost.length > 253) { + throw new Error(`Invalid actor hostname: ${actorHost}`); + } + const data = { activity: activity, signature, diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 009d4cbd39..12d8a7ff4f 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -106,6 +106,11 @@ export class ApInboxService { @bindThis public async performActivity(actor: MiRemoteUser, activity: IObject, resolver?: Resolver): Promise { + // Verify that the actor actually exists and is accessible + if (!await this.verifyActorExists(actor)) { + throw new Bull.UnrecoverableError(`Actor verification failed: ${actor.uri} does not exist or is not accessible`); + } + let result = undefined as string | void; if (isCollectionOrOrderedCollection(activity)) { const results = [] as [string, string | void][]; @@ -905,4 +910,43 @@ export class ApInboxService { return await this.apPersonService.updatePerson(actor.uri, resolver) ?? 'skip: nothing to do'; } + + @bindThis + private async verifyActorExists(actor: MiRemoteUser): Promise { + try { + // Check if actor URI is valid and from an allowed domain + if (!actor.uri || !this.utilityService.isFederationAllowedUri(actor.uri)) { + return false; + } + + // Extract host from actor URI + const actorHost = this.utilityService.extractDbHost(actor.uri); + if (!actorHost) { + return false; + } + + // Try to fetch the actor to verify it exists + const resolver = this.apResolverService.createResolver(); + try { + const resolvedActor = await resolver.resolve(actor.uri); + if (!isActor(resolvedActor)) { + return false; + } + + // Verify the resolved actor ID matches the expected URI + if (getApId(resolvedActor) !== actor.uri) { + return false; + } + + return true; + } catch (err) { + // If we can't resolve the actor, it doesn't exist or is inaccessible + this.logger.warn(`Failed to verify actor existence: ${actor.uri}`, err instanceof Error ? err.message : String(err)); + return false; + } + } catch (err) { + this.logger.error(`Error during actor verification: ${actor.uri}`, err instanceof Error ? err.message : String(err)); + return false; + } + } }