check for cross-domain redirects that bounce from an allowed domain to a blocked domain

This commit is contained in:
Hazelnoot 2024-12-18 10:29:25 -05:00
parent 7f19f8c10b
commit 05ff9ae93f

View file

@ -104,7 +104,7 @@ export class Resolver {
}
if (!this.utilityService.isFederationAllowedHost(host)) {
throw new UnrecoverableError(`instance is blocked: ${value}`);
throw new UnrecoverableError(`cannot fetch AP object ${value}: blocked instance ${host}`);
}
if (this.config.signToActivityPubGet && !this.user) {
@ -123,18 +123,27 @@ export class Resolver {
throw new UnrecoverableError(`invalid AP object ${value}: does not have ActivityStreams context`);
}
// HttpRequestService / ApRequestService have already checked that
// `object.id` or `object.url` matches the URL used to fetch the
// object after redirects; here we double-check that no redirects
// bounced between hosts
// Since redirects are allowed, we cannot safely validate an anonymous object.
// Reject any responses without an ID, as all other checks depend on that value.
if (object.id == null) {
throw new UnrecoverableError(`invalid AP object ${value}: missing id`);
}
// We allow some limited cross-domain redirects, which means the host may have changed during fetch.
// Additional checks are needed to validate the scope of cross-domain redirects.
const finalHost = this.utilityService.extractDbHost(object.id);
if (finalHost !== host) {
// Make sure the redirect stayed within the same authority.
if (this.utilityService.punyHostPSLDomain(object.id) !== this.utilityService.punyHostPSLDomain(value)) {
throw new UnrecoverableError(`invalid AP object ${value}: id ${object.id} has different host`);
}
// Check if the redirect bounce from [allowed domain] to [blocked domain].
if (!this.utilityService.isFederationAllowedHost(finalHost)) {
throw new UnrecoverableError(`cannot fetch AP object ${value}: redirected to blocked instance ${finalHost}`);
}
}
return object;
}