Skip to content

Commit

Permalink
Revert "Add displayname mention spam protection (#537)" (#571)
Browse files Browse the repository at this point in the history
  • Loading branch information
H-Shay authored Jan 28, 2025
1 parent 0134a63 commit b150dbb
Show file tree
Hide file tree
Showing 4 changed files with 2 additions and 89 deletions.
4 changes: 1 addition & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,6 @@
"humanize-duration-ts": "^2.1.1",
"js-yaml": "^4.1.0",
"jsdom": "^16.6.0",
"lru-cache": "^11.0.1",
"matrix-appservice-bridge": "10.3.1",
"nsfwjs": "^4.1.0",
"parse-duration": "^1.0.2",
Expand All @@ -70,6 +69,5 @@
},
"engines": {
"node": ">=20.0.0"
},
"packageManager": "[email protected]+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
}
}
56 changes: 1 addition & 55 deletions src/protections/MentionSpam.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,16 +18,10 @@ import { Protection } from "./IProtection";
import { Mjolnir } from "../Mjolnir";
import { LogLevel, LogService, Permalinks, UserID } from "@vector-im/matrix-bot-sdk";
import { NumberProtectionSetting } from "./ProtectionSettings";
import { LRUCache } from "lru-cache";

export const DEFAULT_MAX_MENTIONS = 10;

export class MentionSpam extends Protection {
private roomDisplaynameCache = new LRUCache<string, string[]>({
ttl: 1000 * 60 * 24, // 24 minutes
ttlAutopurge: true,
});

settings = {
maxMentions: new NumberProtectionSetting(DEFAULT_MAX_MENTIONS, 1),
};
Expand All @@ -43,24 +37,6 @@ export class MentionSpam extends Protection {
return "If a user posts many mentions, that message is redacted. No bans are issued.";
}

private async getRoomDisplaynames(mjolnir: Mjolnir, roomId: string): Promise<string[]> {
const existing = this.roomDisplaynameCache.get(roomId);
if (existing) {
return existing;
}
const profiles = await mjolnir.client.getJoinedRoomMembersWithProfiles(roomId);
const displaynames = (
Object.values(profiles)
.map((v) => v.display_name?.toLowerCase())
.filter((v) => typeof v === "string") as string[]
)
// Limit to displaynames with more than a few characters.
.filter((displayname) => displayname.length > 2);

this.roomDisplaynameCache.set(roomId, displaynames);
return displaynames;
}

public checkMentions(
body: unknown | undefined,
htmlBody: unknown | undefined,
Expand All @@ -79,41 +55,11 @@ export class MentionSpam extends Protection {
return false;
}

public checkDisplaynameMentions(
body: unknown | undefined,
htmlBody: unknown | undefined,
displaynames: string[],
): boolean {
const max = this.settings.maxMentions.value;
const bodyWords = ((typeof body === "string" && body) || "").toLowerCase();
if (displaynames.filter((s) => bodyWords.includes(s.toLowerCase())).length > max) {
return true;
}
const htmlBodyWords = decodeURIComponent((typeof htmlBody === "string" && htmlBody) || "").toLowerCase();
if (displaynames.filter((s) => htmlBodyWords.includes(s)).length > max) {
return true;
}
return false;
}

public async handleEvent(mjolnir: Mjolnir, roomId: string, event: any): Promise<any> {
if (event["type"] === "m.room.message") {
let content = event["content"] || {};
const explicitMentions = content["m.mentions"]?.user_ids;
let hitLimit = this.checkMentions(content.body, content.formatted_body, explicitMentions);

// Slightly more costly to hit displaynames, so only do it if we don't hit on mxid matches.
if (!hitLimit) {
const displaynames = await this.getRoomDisplaynames(mjolnir, roomId);
hitLimit = this.checkDisplaynameMentions(content.body, content.formatted_body, displaynames);
if (hitLimit) {
LogService.info(
"MentionSpam",
`Hitlimit reached via display name mention check for event content ${JSON.stringify(content)}`,
);
}
}

const hitLimit = this.checkMentions(content.body, content.formatted_body, explicitMentions);
if (hitLimit) {
await mjolnir.managementRoomOutput.logMessage(
LogLevel.WARN,
Expand Down
26 changes: 0 additions & 26 deletions test/integration/mentionSpamProtectionTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,12 +95,6 @@ describe("Test: Mention spam protection", function () {
});
// Also covers HTML mentions
const mentionUsers = Array.from({ length: DEFAULT_MAX_MENTIONS + 1 }, (_, i) => `@user${i}:example.org`);
const mentionDisplaynames = Array.from({ length: DEFAULT_MAX_MENTIONS + 1 }, (_, i) => `Test User ${i}`);

// Pre-set the displayname cache.
let protection = this.mjolnir.protectionManager.protections.get("MentionSpam");
protection.roomDisplaynameCache.set(room, mentionDisplaynames);

const messageWithTextMentions = await client.sendText(room, mentionUsers.join(" "));
const messageWithHTMLMentions = await client.sendHtmlText(
room,
Expand All @@ -113,7 +107,6 @@ describe("Test: Mention spam protection", function () {
user_ids: mentionUsers,
},
});
const messageWithDisplaynameMentions = await client.sendText(room, mentionDisplaynames.join(" "));

await delay(500);

Expand All @@ -125,24 +118,5 @@ describe("Test: Mention spam protection", function () {

const fetchedMentionsEvent = await client.getEvent(room, messageWithMMentions);
assert.equal(Object.keys(fetchedMentionsEvent.content).length, 0, "This event should have been redacted");

const fetchedDisplaynameEvent = await client.getEvent(room, messageWithDisplaynameMentions);
assert.equal(Object.keys(fetchedDisplaynameEvent.content).length, 0, "This event should have been redacted");

// send messages after activating protection, they should be auto-redacted
const messages = [];
for (let i = 0; i < 10; i++) {
let nextMessage = await client.sendText(room, `hello${i}`);
messages.push(nextMessage);
}

messages.forEach(async (eventID) => {
await client.getEvent(room, eventID);
assert.equal(
Object.keys(fetchedDisplaynameEvent.content).length,
0,
"This event should have been redacted",
);
});
});
});
5 changes: 0 additions & 5 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -2578,11 +2578,6 @@ lru-cache@^10.0.1:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==

lru-cache@^11.0.1:
version "11.0.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-11.0.1.tgz#3a732fbfedb82c5ba7bca6564ad3f42afcb6e147"
integrity sha512-CgeuL5uom6j/ZVrg7G/+1IXqRY8JXX4Hghfy5YE0EhoYQWvndP1kufu58cmZLNIDKnRhZrXfdS9urVWx98AipQ==

lru-cache@^4.1.5:
version "4.1.5"
resolved "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz"
Expand Down

0 comments on commit b150dbb

Please sign in to comment.