diff --git a/apps/frontend/src/main/insights/insights-executor.ts b/apps/frontend/src/main/insights/insights-executor.ts
index a8604045..72550e20 100644
--- a/apps/frontend/src/main/insights/insights-executor.ts
+++ b/apps/frontend/src/main/insights/insights-executor.ts
@@ -13,7 +13,7 @@ import type {
InsightsModelConfig,
ImageAttachment
} from '../../shared/types';
-import { MODEL_ID_MAP } from '../../shared/constants';
+import { MODEL_ID_MAP, MAX_IMAGES_PER_TASK, MAX_IMAGE_SIZE } from '../../shared/constants';
import { InsightsConfig } from './config';
import { detectRateLimit, createSDKRateLimitInfo } from '../rate-limit-detector';
@@ -120,6 +120,14 @@ export class InsightsExecutor extends EventEmitter {
const imagesTempFiles: string[] = [];
let imagesManifestFile: string | undefined;
+ // Defense-in-depth: cap image count and filter oversized images in the executor
+ if (images && images.length > MAX_IMAGES_PER_TASK) {
+ images = images.slice(0, MAX_IMAGES_PER_TASK);
+ }
+ if (images) {
+ images = images.filter(img => !img.data || Buffer.byteLength(img.data, 'base64') <= MAX_IMAGE_SIZE);
+ }
+
if (images && images.length > 0) {
try {
const manifest: Array<{ path: string; mimeType: string }> = [];
diff --git a/apps/frontend/src/renderer/components/Insights.tsx b/apps/frontend/src/renderer/components/Insights.tsx
index f83de152..f8e595d5 100644
--- a/apps/frontend/src/renderer/components/Insights.tsx
+++ b/apps/frontend/src/renderer/components/Insights.tsx
@@ -526,7 +526,10 @@ export function Insights({ projectId }: InsightsProps) {
{/* Image analysis warning */}
{pendingImages.length > 0 && (
-
{t('insights.images.analysisUnsupported')}
+
+
+
{t('insights.images.analysisUnsupported')}
+
)}
{/* Image error */}
@@ -625,17 +628,20 @@ function MessageBubble({
{/* Image attachments for user messages */}
{isUser && message.images && message.images.length > 0 && (
-
- {message.images
- .filter(img => img.thumbnail || img.data)
- .map((image) => (
-

- ))}
+
+
+ {message.images
+ .filter(img => img.thumbnail || img.data)
+ .map((image) => (
+

+ ))}
+
+
{t('insights.images.notAnalyzed')}
)}
diff --git a/apps/frontend/src/shared/i18n/locales/en/common.json b/apps/frontend/src/shared/i18n/locales/en/common.json
index 61d3e37d..be33b4e7 100644
--- a/apps/frontend/src/shared/i18n/locales/en/common.json
+++ b/apps/frontend/src/shared/i18n/locales/en/common.json
@@ -442,7 +442,8 @@
"processFailed": "Failed to process image",
"dragOver": "Drop image to attach",
"analysisUnsupported": "Note: Image analysis is not yet supported. Images are stored for reference but cannot be analyzed by the model.",
- "screenshotTooLarge": "Screenshot is too large ({{size}}MB). Maximum size is {{max}}MB. Consider capturing a smaller area."
+ "screenshotTooLarge": "Screenshot is too large ({{size}}MB). Maximum size is {{max}}MB. Consider capturing a smaller area.",
+ "notAnalyzed": "Images were stored for reference but not analyzed by the model."
}
},
"ideation": {
diff --git a/apps/frontend/src/shared/i18n/locales/fr/common.json b/apps/frontend/src/shared/i18n/locales/fr/common.json
index 2eb02c10..fa6463d7 100644
--- a/apps/frontend/src/shared/i18n/locales/fr/common.json
+++ b/apps/frontend/src/shared/i18n/locales/fr/common.json
@@ -442,7 +442,8 @@
"processFailed": "Échec du traitement de l'image",
"dragOver": "Déposer l'image pour joindre",
"analysisUnsupported": "Note : L'analyse d'images n'est pas encore prise en charge. Les images sont stockées pour référence mais ne peuvent pas être analysées par le modèle.",
- "screenshotTooLarge": "La capture d'écran est trop volumineuse ({{size}}Mo). La taille maximale est de {{max}}Mo. Essayez de capturer une zone plus petite."
+ "screenshotTooLarge": "La capture d'écran est trop volumineuse ({{size}}Mo). La taille maximale est de {{max}}Mo. Essayez de capturer une zone plus petite.",
+ "notAnalyzed": "Les images ont été stockées pour référence mais n'ont pas été analysées par le modèle."
}
},
"ideation": {