From 339b85ba4055dfec6b349b7fcd2ad420d775793a Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 14 Sep 2026 19:26:05 +0200 Subject: [PATCH] MessageLinkEmbeds: fix issue where a message's origin channel could be faked This had a bad interaction with SupportHelper, where it could be used to add a "Run Snippet" button to any linked message, which when clicked would run code contained in that message Reported by Kaede --- package.json | 2 +- src/plugins/_core/supportHelper.tsx | 2 ++ src/plugins/messageLinkEmbeds/index.tsx | 35 +++++++++++++++---------- src/utils/constants.ts | 8 ++++-- 4 files changed, 30 insertions(+), 17 deletions(-) diff --git a/package.json b/package.json index 4c2e61196..59a925f25 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.15.5", + "version": "1.15.6", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index b88643a33..3312c51e9 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -264,6 +264,8 @@ export default definePlugin({ }, renderMessageAccessory(props) { + if (props.message.vencordEmbeddedBy) return null; + const buttons = [] as JSX.Element[]; const shouldAddUpdateButton = diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index dd7501920..92aaecd98 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -46,6 +46,8 @@ const messageCache = new Map(); +const getCacheKey = (channelId: string, messageId: string) => `${channelId}:${messageId}`; + const Embed = findComponentLazy(m => m.prototype?.renderSuppressButton); const ChannelMessage = findComponentByCodeLazy("childrenExecutedCommand:", ".hideAccessories"); let AutoModEmbed: ComponentType = () => null; @@ -128,17 +130,19 @@ const settings = definePluginSettings({ }); -async function fetchMessage(channelID: string, messageID: string) { - const cached = messageCache.get(messageID); +async function fetchMessage(channelId: string, messageId: string) { + const cacheKey = getCacheKey(channelId, messageId); + + const cached = messageCache.get(cacheKey); if (cached) return cached.message; - messageCache.set(messageID, { fetched: false }); + messageCache.set(cacheKey, { fetched: false }); const res = await RestAPI.get({ - url: Constants.Endpoints.MESSAGES(channelID), + url: Constants.Endpoints.MESSAGES(channelId), query: { limit: 1, - around: messageID + around: messageId }, retries: 2 }).catch(() => null); @@ -146,10 +150,12 @@ async function fetchMessage(channelID: string, messageID: string) { const msg = res?.body?.[0]; if (!msg) return; + if (msg.id !== messageId) return; + const message = MessageStore.getMessages(msg.channel_id).receiveMessage(msg).get(msg.id); if (!message) return; - messageCache.set(message.id, { + messageCache.set(cacheKey, { message, fetched: true }); @@ -230,31 +236,32 @@ function MessageEmbedAccessory({ message }: { message: Message; }) { const accessories = [] as (JSX.Element | null)[]; - for (const [_, channelID, messageID] of message.content!.matchAll(messageLinkRegex)) { - if (embeddedBy.includes(messageID) || embeddedBy.length > 2) { + for (const [_, channelId, messageId] of message.content!.matchAll(messageLinkRegex)) { + if (embeddedBy.includes(messageId) || embeddedBy.length > 2) { continue; } - const linkedChannel = ChannelStore.getChannel(channelID); + const linkedChannel = ChannelStore.getChannel(channelId); if (!linkedChannel || (!linkedChannel.isPrivate() && !PermissionStore.can(PermissionsBits.VIEW_CHANNEL, linkedChannel))) { continue; } const { listMode, idList } = settings.store; - const isListed = [linkedChannel.guild_id, channelID, message.author.id].some(id => id && idList.includes(id)); + const isListed = [linkedChannel.guild_id, channelId, message.author.id].some(id => id && idList.includes(id)); if (listMode === "blacklist" && isListed) continue; if (listMode === "whitelist" && !isListed) continue; - let linkedMessage = messageCache.get(messageID)?.message; + const cacheKey = getCacheKey(channelId, messageId); + let linkedMessage = messageCache.get(cacheKey)?.message; if (!linkedMessage) { - linkedMessage ??= MessageStore.getMessage(channelID, messageID); + linkedMessage ??= MessageStore.getMessage(channelId, messageId); if (linkedMessage) { - messageCache.set(messageID, { message: linkedMessage, fetched: true }); + messageCache.set(cacheKey, { message: linkedMessage, fetched: true }); } else { - messageFetchQueue.unshift(() => fetchMessage(channelID, messageID) + messageFetchQueue.unshift(() => fetchMessage(channelId, messageId) .then(m => m && updateMessage(message.channel_id, message.id)) ); continue; diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 024acfd30..160408c53 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -655,8 +655,8 @@ export const Devs = /* #__PURE__*/ Object.freeze({ id: 383365021415243776n }, paige: { - name: "paige", - id: 1375697625864601650n + name: "paige", + id: 1375697625864601650n }, jax: { name: "jax", @@ -669,6 +669,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ Davri: { name: "Davri", id: 457579346282938368n + }, + Kaede: { + name: "Kaede", + id: 1492642701320126504n } } satisfies Record);