Skip to content

Commit 559b978

Browse files
committed
Check direct quote recipients
Direct quote requests against unknown-visibility targets now still run the recipient subset check. This keeps a requester who can see the target from getting a stamp for a direct quote addressed outside the target audience. #31 (comment) Assisted-by: Codex:gpt-5.5
1 parent 44cd215 commit 559b978

2 files changed

Lines changed: 75 additions & 1 deletion

File tree

packages/botkit/src/bot-impl.test.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4120,6 +4120,72 @@ test("BotImpl.onQuoteRequested() rejects unknown-audience quotes", async () => {
41204120
);
41214121
});
41224122

4123+
test("BotImpl.onQuoteRequested() checks direct quote recipients for unknown targets", async () => {
4124+
const repository = new MemoryRepository();
4125+
const bot = new BotImpl<void>({
4126+
kv: new MemoryKvStore(),
4127+
repository,
4128+
username: "bot",
4129+
quotePolicy: "public",
4130+
});
4131+
const ctx = createMockInboxContext(bot, "https://example.com", "bot");
4132+
const session = new SessionImpl(bot, ctx);
4133+
const actor = new Person({
4134+
id: new URL("https://remote.example/users/alice"),
4135+
preferredUsername: "alice",
4136+
});
4137+
const unrelatedActor = new Person({
4138+
id: new URL("https://remote.example/users/bob"),
4139+
preferredUsername: "bob",
4140+
});
4141+
const targetId = new URL(
4142+
"https://example.com/ap/note/01950000-0000-7000-8000-000000000101",
4143+
);
4144+
await repository.addMessage(
4145+
"bot",
4146+
"01950000-0000-7000-8000-000000000101" as Uuid,
4147+
new Create({
4148+
id: new URL(
4149+
"https://example.com/ap/create/01950000-0000-7000-8000-000000000101",
4150+
),
4151+
actor: session.actorId,
4152+
object: new Note({
4153+
id: targetId,
4154+
attribution: session.actorId,
4155+
content: "A target with an unrecognized audience.",
4156+
to: actor.id,
4157+
}),
4158+
}),
4159+
);
4160+
const quote = new Note({
4161+
id: new URL("https://remote.example/notes/direct-quote-outside-target"),
4162+
attribution: actor.id,
4163+
quoteUrl: targetId,
4164+
content: "Quoted directly to someone else.",
4165+
to: unrelatedActor.id,
4166+
tags: [new Mention({ href: unrelatedActor.id })],
4167+
});
4168+
4169+
await bot.onQuoteRequested(
4170+
ctx,
4171+
new QuoteRequest({
4172+
id: new URL("https://remote.example/quote-requests/direct-outside"),
4173+
actor,
4174+
object: targetId,
4175+
instrument: quote,
4176+
}),
4177+
);
4178+
4179+
assert.deepStrictEqual(ctx.sentActivities.length, 1);
4180+
const { recipients, activity } = ctx.sentActivities[0];
4181+
assert.deepStrictEqual(recipients, [actor]);
4182+
assert.ok(activity instanceof Reject);
4183+
assert.deepStrictEqual(
4184+
await repository.findQuoteAuthorization("bot", quote.id!),
4185+
undefined,
4186+
);
4187+
});
4188+
41234189
test("AuthorizedMessage.unauthorizeQuote() checks the target message", async () => {
41244190
const repository = new MemoryRepository();
41254191
const bot = new BotImpl<void>({

packages/botkit/src/bot-impl.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -852,7 +852,15 @@ export class BotImpl<TContextData> implements Bot<TContextData> {
852852
quoteVisibility: MessageVisibility,
853853
targetVisibility: MessageVisibility,
854854
): Promise<boolean> {
855-
if (targetVisibility === "unknown") return quoteVisibility !== "direct";
855+
if (targetVisibility === "unknown") {
856+
return quoteVisibility !== "direct" ||
857+
!await this.#isQuoteAudienceSubset(
858+
ctx,
859+
actorId,
860+
quoteObject,
861+
targetObject,
862+
);
863+
}
856864
if (quoteVisibility === "unknown") {
857865
return targetVisibility !== "public" && targetVisibility !== "unlisted";
858866
}

0 commit comments

Comments
 (0)