diff --git a/packages/backend/src/server/activitypub.ts b/packages/backend/src/server/activitypub.ts index 6feb06ba6..b60b236f3 100644 --- a/packages/backend/src/server/activitypub.ts +++ b/packages/backend/src/server/activitypub.ts @@ -55,6 +55,18 @@ function isActivityPubReq(ctx: Router.RouterContext): boolean { return typeof accepted === 'string' && !accepted.match(/html/); } +export function denyActivityPub() { + return async (ctx, next) => { + if (!isActivityPubReq(ctx)) return await next(); + + // Clients are required to set the `Accept` header to an Activitypub content type. + // If such a content type negotiation header is received on an unexpected route, + // something seems to be fishy and we should not respond with content. A user + // might have e.g. uploaded a malicious file that looks like an activity. + ctx.status = 406; + }; +} + export function setResponseType(ctx: Router.RouterContext): void { const accept = ctx.accepts(ACTIVITY_JSON, LD_JSON); if (accept === LD_JSON) { diff --git a/packages/backend/src/server/api/index.ts b/packages/backend/src/server/api/index.ts index 554596e3a..bd84e99ac 100644 --- a/packages/backend/src/server/api/index.ts +++ b/packages/backend/src/server/api/index.ts @@ -10,6 +10,7 @@ import cors from '@koa/cors'; import { Instances, AccessTokens, Users } from '@/models/index.js'; import config from '@/config/index.js'; +import { denyActivityPub } from '@/server/activitypub.js'; import { endpoints } from './endpoints.js'; import { handler } from './api-handler.js'; import signup from './private/signup.js'; @@ -24,6 +25,7 @@ const app = new Koa(); app.use(cors({ origin: '*', })); +app.use(denyActivityPub()); // No caching app.use(async (ctx, next) => { diff --git a/packages/backend/src/server/file/index.ts b/packages/backend/src/server/file/index.ts index 56bf14f9a..4dc40f612 100644 --- a/packages/backend/src/server/file/index.ts +++ b/packages/backend/src/server/file/index.ts @@ -8,6 +8,7 @@ import { dirname } from 'node:path'; import Koa from 'koa'; import cors from '@koa/cors'; import Router from '@koa/router'; +import { denyActivityPub } from '@/server/activitypub.js'; import { sendDriveFile } from './send-drive-file.js'; const _filename = fileURLToPath(import.meta.url); @@ -16,6 +17,7 @@ const _dirname = dirname(_filename); // Init app const app = new Koa(); app.use(cors()); +app.use(denyActivityPub()); app.use(async (ctx, next) => { ctx.set('Content-Security-Policy', "default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'"); await next(); diff --git a/packages/backend/src/server/proxy/index.ts b/packages/backend/src/server/proxy/index.ts index 0132817b4..b1e12fa25 100644 --- a/packages/backend/src/server/proxy/index.ts +++ b/packages/backend/src/server/proxy/index.ts @@ -5,11 +5,13 @@ import Koa from 'koa'; import cors from '@koa/cors'; import Router from '@koa/router'; +import { denyActivityPub } from '@/server/activitypub.js'; import { proxyMedia } from './proxy-media.js'; // Init app const app = new Koa(); app.use(cors()); +app.use(denyActivityPub()); app.use(async (ctx, next) => { ctx.set('Content-Security-Policy', "default-src 'none'; img-src 'self'; media-src 'self'; style-src 'unsafe-inline'"); await next();