server: forbid activitypub requests on unexpected routes

ActivityPub requests on routes which do not support activitypub
are now replying with HTTP status code 406 "Not Acceptable".

ActivityPub clients are required by the W3C TR to set the `Accept`
header. If this accept header is detected on an unexpected route,
the whole request will be aborted with the status code above.

This is an additional measure for clients who might not be aware of
having to check the content-type header of the reply.

Ref: https://github.com/w3c/activitypub/issues/432
Changelog: Security
This commit is contained in:
Johann150 2024-03-22 10:14:54 +01:00
parent e366116ac1
commit 624157f03e
Signed by untrusted user: Johann150
GPG key ID: 9EE6577A2A06F8F1
4 changed files with 18 additions and 0 deletions

View file

@ -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) {

View file

@ -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) => {

View file

@ -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();

View file

@ -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();