From b78fdb15db6dd1cfda96fece7fa6ef4136f5a212 Mon Sep 17 00:00:00 2001
From: eternal-flame-AD <yume@yumechi.jp>
Date: Fri, 14 Feb 2025 06:12:49 -0600
Subject: [PATCH] Refuse all remote AP redirects

Signed-off-by: eternal-flame-AD <yume@yumechi.jp>
---
 packages/backend/src/server/ServerService.ts | 29 ++++++++++++++++++++
 1 file changed, 29 insertions(+)

diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 818efd8740..d1b8e9e05a 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -290,6 +290,35 @@ export class ServerService implements OnApplicationShutdown {
 			done();
 		});
 
+		fastify.addHook('onSend', (request, reply, payload, done) => {
+			if (reply.statusCode >= 300 && reply.statusCode < 400) {
+				const isAp = ["application/activity+json", "application/ld+json"].some(type => request.headers.accept?.includes(type));
+			
+				if (isAp) {
+					const location = reply.getHeader('location');
+
+					// the only acceptable redirect is to our own domain
+					if (typeof location === 'string') {
+						// allow http in development
+						const normalizedLocation = process.env.NODE_ENV !== 'production' ?
+							location.replace(/^http:\/\//, 'https://') : location;
+
+						if ([`https://${this.config.host}/`, `https://${this.config.hostname}/`].some(host => normalizedLocation.startsWith(host))) {
+							done(null, payload);
+							return;
+						}
+					}
+					
+					reply.code(406);
+					reply.removeHeader('location');
+					done(null, null);
+					return;
+				}
+			}
+			
+			done(null, payload);
+		});
+
 		// CSP
 		if (process.env.NODE_ENV === 'production' && !this.config.browserSandboxing.csp?.disable) {
 			console.debug('cspPrerenderedContent', this.config.cspPrerenderedContent);