implement ApResolver.secureResolve to use a provided object only if the authority matches

This commit is contained in:
Hazelnoot 2025-03-02 20:37:17 -05:00
parent 1ed2f207f7
commit ad49faa956
2 changed files with 36 additions and 2 deletions

View file

@ -23,7 +23,7 @@ import { getApId, getNullableApId, isCollectionOrOrderedCollection } from './typ
import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
import type { IObject, ICollection, IOrderedCollection } from './type.js';
import type { IObject, ICollection, IOrderedCollection, ApObject } from './type.js';
export class Resolver {
private history: Set<string>;
@ -76,6 +76,33 @@ export class Resolver {
}
}
/**
* Securely resolves an AP object or URL that has been sent from another instance.
* An input object is trusted if and only if its ID matches the authority of sentFromUri.
* In all other cases, the object is re-fetched from remote by input string or object ID.
*/
@bindThis
public async secureResolve(input: ApObject, sentFromUri: string): Promise<IObject> {
// Unpack arrays to get the value element.
const value = fromTuple(input);
if (value == null) {
throw new IdentifiableError('20058164-9de1-4573-8715-425753a21c1d', 'Cannot resolve null input');
}
// This will throw if the input has no ID, which is good because we can't verify an anonymous object anyway.
const id = getApId(value);
// Check if we can use the provided object as-is.
// Our security requires that the object ID matches the host authority that sent it, otherwise it can't be trusted.
// A mismatch isn't necessarily malicious, it just means we can't use the object we were given.
if (typeof(value) === 'object' && this.apUtilityService.haveSameAuthority(id, sentFromUri)) {
return value;
}
// If the checks didn't pass, then we must fetch the object and use that.
return await this.resolve(id);
}
@bindThis
public async resolve(value: string | IObject | [string | IObject]): Promise<IObject> {
// eslint-disable-next-line no-param-reassign

View file

@ -1,4 +1,11 @@
export function fromTuple<T>(value: T | [T]): T {
/*
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export function fromTuple<T>(value: T | [T]): T;
export function fromTuple<T>(value: T | [T] | T[]): T | undefined;
export function fromTuple<T>(value: T | [T] | T[]): T | undefined {
if (Array.isArray(value)) {
return value[0];
}