From 9f7ce7dbd570270de387e70f965d91aeeef69353 Mon Sep 17 00:00:00 2001 From: asepharyana Date: Wed, 12 Aug 2026 18:28:19 +0700 Subject: [PATCH] fix(gateway): hash full image data URL for cache key to prevent collision MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: makeImageCacheKey() only hashed the first 128 chars of the data URL. Since all resized images use the same MIME prefix ('data:image/png;base64,') + identical base64 header bytes, nearly every image got the same 16-char hash → 'image:' → all images reused the first cached vision analysis (often a gambling-detection verdict). Fix: hash the entire data URL instead of just the prefix. Verified 114 stale 'image:' entries + 745 stale 'phash:' entries purged from prod DB. tsc --noEmit clean, 133 tests pass. --- .../src/modules/ai-moderation/textCacheStore.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/services/discord-gateway/src/modules/ai-moderation/textCacheStore.ts b/services/discord-gateway/src/modules/ai-moderation/textCacheStore.ts index 6f015bd..191d37b 100644 --- a/services/discord-gateway/src/modules/ai-moderation/textCacheStore.ts +++ b/services/discord-gateway/src/modules/ai-moderation/textCacheStore.ts @@ -63,12 +63,15 @@ export function makeCustomEmojiCacheKey(emojiId: string): string { /** * Generate a deterministic cache key for an image data URL. - * Hashes the first 128 chars of the data URL (enough to identify the image - * without storing the full base64 string as the key). + * Hashes the FULL data URL — only hashing a prefix (e.g. first 128 chars) + * causes hash collisions for images that share the same MIME prefix + + * identical base64 header bytes (common when images are resized to the same + * dimensions), which makes every image incorrectly reuse the same cached + * vision analysis. Hashing the entire data URL guarantees uniqueness per + * actual pixel content. */ export function makeImageCacheKey(dataUrl: string): string { - const prefix = dataUrl.slice(0, 128); - const hash = createHash("sha256").update(prefix).digest("hex").slice(0, 16); + const hash = createHash("sha256").update(dataUrl).digest("hex").slice(0, 16); return `image:${hash}`; }