fix(audit): sécurité + perf + DB — 40 problèmes adressés
SÉCURITÉ (CRITIQUE): - link-preview: auth() obligatoire + filtre IP privées (SSRF fix) - metrics: !metricsToken → 401 au lieu d'etre ouvert si token absent - contextual-ai-chat: DOMPurify.sanitize() sur dangerouslySetInnerHTML (XSS fix) PERFORMANCE: - graph/route.ts: take:500 + orderBy updatedAt desc (pas de full table scan) - syncAllEmbeddings: batch parallèle Promise.allSettled × 5 (pas séquentiel) - 80 console.log supprimés du code production BASE DE DONNÉES: - Note: index contentUpdatedAt + isPublic ajoutés au schema - DocumentChunk: commentaire index vectoriel HNSW (à exécuter manuellement)
This commit is contained in:
@@ -94,7 +94,6 @@ export async function fetchLiveModelsForProvider(
|
||||
.sort();
|
||||
|
||||
if (fetched.length > 0) {
|
||||
console.log(`[fetchLiveModelsForProvider] Got ${fetched.length} models from ${provider} API:`, fetched);
|
||||
return { models: fetched, fromApi: true };
|
||||
} else {
|
||||
console.warn(`[fetchLiveModelsForProvider] API returned empty data array for ${provider}`);
|
||||
|
||||
@@ -38,7 +38,6 @@ async function resolveProviderForLane(
|
||||
if (baseUrlConfigKey && byok.baseUrl) byokCfg[baseUrlConfigKey] = byok.baseUrl;
|
||||
|
||||
const resolvedModel = (byok.model && byok.model.trim()) ? byok.model : adminRoute.modelName;
|
||||
console.log(`[byok] Using BYOK key: provider=${byok.provider} model=${resolvedModel} user=${billingUserId}`);
|
||||
|
||||
const provider = getProviderInstance(
|
||||
byok.provider as ProviderType,
|
||||
|
||||
@@ -119,14 +119,11 @@ async function fetchHtml(url: string): Promise<string | null> {
|
||||
})
|
||||
clearTimeout(timeout)
|
||||
if (!res.ok) {
|
||||
console.log(`[fetchHtml] Failed for ${url}: ${res.status}`)
|
||||
return null
|
||||
}
|
||||
const html = await res.text()
|
||||
console.log(`[fetchHtml] Got ${html.length} chars from ${url}`)
|
||||
return html
|
||||
} catch (e: any) {
|
||||
console.log(`[fetchHtml] Error for ${url}: ${e.message}`)
|
||||
return null
|
||||
}
|
||||
}
|
||||
@@ -139,17 +136,14 @@ async function extractImagesForUrls(urls: string[]): Promise<Array<{ localUrl: s
|
||||
for (const pageUrl of urls) {
|
||||
const html = await fetchHtml(pageUrl)
|
||||
if (!html) {
|
||||
console.log(`[extractImagesForUrls] No HTML fetched for ${pageUrl}`)
|
||||
continue
|
||||
}
|
||||
const remoteUrls = extractImageUrlsFromHtml(html, pageUrl)
|
||||
console.log(`[extractImagesForUrls] ${remoteUrls.length} image URLs found on ${pageUrl}`)
|
||||
for (const remoteUrl of remoteUrls) {
|
||||
if (!seenSourceUrls.has(remoteUrl)) {
|
||||
seenSourceUrls.add(remoteUrl)
|
||||
toDownload.push({ sourceUrl: remoteUrl, pageUrl })
|
||||
} else {
|
||||
console.log(`[extractImagesForUrls] Skipping duplicate source: ${remoteUrl}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,7 +157,6 @@ async function extractImagesForUrls(urls: string[]): Promise<Array<{ localUrl: s
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[extractImagesForUrls] ${toDownload.length} unique candidates, ${allImages.length} downloaded successfully`)
|
||||
return allImages
|
||||
}
|
||||
|
||||
@@ -1508,7 +1501,6 @@ async function executeToolUseAgent(
|
||||
|
||||
const registered = toolRegistry.get('generate_slides')
|
||||
if (registered) {
|
||||
console.log('[AgentExecutor] Running manual fallback execution for generate_slides')
|
||||
const slideTool = registered.buildTool(ctx)
|
||||
const executionResult = await slideTool.execute({ title, theme, slides })
|
||||
if (executionResult && executionResult.success && executionResult.canvasId) {
|
||||
@@ -1538,7 +1530,6 @@ async function executeToolUseAgent(
|
||||
if (diagramStr) {
|
||||
const registered = toolRegistry.get('generate_excalidraw')
|
||||
if (registered) {
|
||||
console.log('[AgentExecutor] Running manual fallback execution for generate_excalidraw')
|
||||
const excalidrawTool = registered.buildTool(ctx)
|
||||
const executionResult = await excalidrawTool.execute({ title, diagram: diagramStr })
|
||||
if (executionResult && executionResult.success && executionResult.canvasId) {
|
||||
@@ -1679,9 +1670,7 @@ async function executeToolUseAgent(
|
||||
// Extract images from scraped URLs and insert into markdown content
|
||||
let imageCount = 0
|
||||
if (agent.includeImages && scrapedUrls.length > 0) {
|
||||
console.log(`[AgentExecutor] Extracting images from ${scrapedUrls.length} URLs:`, scrapedUrls)
|
||||
const imageData = await extractImagesForUrls(scrapedUrls)
|
||||
console.log(`[AgentExecutor] Extracted ${imageData.length} images`)
|
||||
if (imageData.length > 0) {
|
||||
const currentNote = await prisma.note.findUnique({
|
||||
where: { id: noteId },
|
||||
@@ -1697,7 +1686,6 @@ async function executeToolUseAgent(
|
||||
imageCount = imageData.length
|
||||
}
|
||||
} else if (agent.includeImages) {
|
||||
console.log(`[AgentExecutor] includeImages enabled but no scraped URLs found in tool results`)
|
||||
}
|
||||
|
||||
const resultId = canvasId || noteId
|
||||
|
||||
@@ -52,7 +52,6 @@ function getCached(key: string): SuggestChartsResponse | null {
|
||||
cache.delete(key)
|
||||
return null
|
||||
}
|
||||
console.log('[chart-suggestion] Cache hit for key:', key)
|
||||
return entry.data
|
||||
}
|
||||
|
||||
@@ -74,22 +73,18 @@ function setCached(key: string, data: SuggestChartsResponse): void {
|
||||
export async function suggestCharts(request: SuggestChartsRequest): Promise<SuggestChartsResponse> {
|
||||
// Check cache first
|
||||
const cacheKey = getCacheKey(request.content || '', request.selection)
|
||||
console.log('[suggestCharts] Cache key:', cacheKey, 'contentLen:', request.content?.length, 'selectionLen:', request.selection?.length)
|
||||
|
||||
const cached = getCached(cacheKey)
|
||||
if (cached) {
|
||||
console.log('[suggestCharts] CACHE HIT - returning cached data')
|
||||
return cached
|
||||
}
|
||||
|
||||
console.log('[suggestCharts] CACHE MISS - trying fast parser first')
|
||||
|
||||
// FAST PATH: Try regex parser first - NO AI call needed!
|
||||
const textToParse = request.selection || request.content || ''
|
||||
const parsed = parseChartData(textToParse)
|
||||
|
||||
if (parsed.hasData && parsed.confidence > 0.3) {
|
||||
console.log('[suggestCharts] FAST PATH - regex parser found data, skipping AI!')
|
||||
const suggestions = generateChartSuggestions(parsed.data)
|
||||
|
||||
const response: SuggestChartsResponse = {
|
||||
@@ -104,7 +99,6 @@ export async function suggestCharts(request: SuggestChartsRequest): Promise<Sugg
|
||||
return response
|
||||
}
|
||||
|
||||
console.log('[suggestCharts] Parser found no good data, calling AI API (slow...)')
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/ai/suggest-charts', {
|
||||
@@ -135,9 +129,7 @@ export async function suggestCharts(request: SuggestChartsRequest): Promise<Sugg
|
||||
// Cache successful responses
|
||||
if (!data.error && !data.quotaExceeded) {
|
||||
setCached(cacheKey, data)
|
||||
console.log('[suggestCharts] Cached response for key:', cacheKey, 'cache size now:', cache.size)
|
||||
} else {
|
||||
console.log('[suggestCharts] NOT caching (has error or quota exceeded)')
|
||||
}
|
||||
|
||||
return data
|
||||
|
||||
@@ -426,7 +426,6 @@ export class ClusteringService {
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`[DBSCAN Clustering] Selected configuration: epsilon=${bestConfig.eps}, minSize=${bestConfig.minSize} -> Generated ${bestClusters.length} clusters (Noise: ${bestNoiseCount})`)
|
||||
|
||||
// REGROUPEMENT ANALYTIQUE DES PAIRES ISOLÉES DE HAUTE SIMILARITÉ
|
||||
// Pour toutes les notes restées dans le bruit (bestClustered.get(id) === -1) :
|
||||
@@ -470,7 +469,6 @@ export class ClusteringService {
|
||||
bestClustered.set(bestPairId, newCid)
|
||||
processedPairs.add(idA)
|
||||
processedPairs.add(bestPairId)
|
||||
console.log(`[DBSCAN Clustering] Formed high-density micro-cluster ${newCid} for pair [${idA}, ${bestPairId}] (Distance: ${bestPairDist.toFixed(4)})`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,16 +61,13 @@ export class ContextualAutoTagService {
|
||||
})
|
||||
|
||||
if (!notebook) {
|
||||
console.log('[ContextualAutoTag] notebook not found for id:', notebookId)
|
||||
return []
|
||||
}
|
||||
|
||||
console.log('[ContextualAutoTag] notebook:', notebook.name, 'labels:', notebook.labels.map((l: any) => l.name), 'contentLanguage:', contentLanguage)
|
||||
|
||||
// CASE 1: Notebook has existing labels → suggest from them (IA2)
|
||||
if (notebook.labels.length > 0) {
|
||||
const existing = await this.suggestFromExistingLabels(noteContent, notebook, contentLanguage)
|
||||
console.log('[ContextualAutoTag] existing label suggestions:', existing.length)
|
||||
if (existing.length > 0) return existing
|
||||
// Fallback: no existing label matched → suggest new ones too
|
||||
}
|
||||
@@ -113,7 +110,6 @@ ${labelList}
|
||||
Suggest relevant labels from the list above.`
|
||||
|
||||
const { text } = await generateText({ model, system: systemPrompt, prompt: userPrompt, temperature: 0.3 })
|
||||
console.log('[ContextualAutoTag] AI response (existing):', text?.substring(0, 300))
|
||||
|
||||
const parsed = this.parseAIResponse(text)
|
||||
if (!parsed) return []
|
||||
@@ -133,7 +129,6 @@ Suggest relevant labels from the list above.`
|
||||
.sort((a: any, b: any) => b.confidence - a.confidence)
|
||||
.slice(0, 3)
|
||||
|
||||
console.log('[ContextualAutoTag] filtered existing:', suggestions.map((s: any) => `${s.label}(${s.confidence})`))
|
||||
return suggestions as LabelSuggestion[]
|
||||
} catch (error) {
|
||||
console.error('Failed to suggest labels:', error)
|
||||
@@ -171,7 +166,6 @@ Note language detected: ${contentLanguage}
|
||||
Respond with labels in ${contentLanguage} (same language as the note).`
|
||||
|
||||
const { text } = await generateText({ model, system: systemPrompt, prompt: userPrompt, temperature: 0.3 })
|
||||
console.log('[ContextualAutoTag] AI response (new):', text?.substring(0, 300))
|
||||
|
||||
const parsed = this.parseAIResponse(text)
|
||||
if (!parsed) return []
|
||||
@@ -187,7 +181,6 @@ Respond with labels in ${contentLanguage} (same language as the note).`
|
||||
.sort((a: any, b: any) => b.confidence - a.confidence)
|
||||
.slice(0, 5)
|
||||
|
||||
console.log('[ContextualAutoTag] new labels:', suggestions.map((s: any) => `${s.label}(${s.confidence})`))
|
||||
return suggestions as LabelSuggestion[]
|
||||
} catch (error) {
|
||||
console.error('Failed to suggest new labels:', error)
|
||||
|
||||
@@ -176,7 +176,6 @@ export class MemoryEchoService {
|
||||
select: { aiProcessingConsent: true },
|
||||
})
|
||||
if (!userSettings?.aiProcessingConsent) {
|
||||
console.log(`[MemoryEchoService] User ${userId} has not given AI consent. Skipping connection generation for compliance.`)
|
||||
return []
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,6 @@ export class ScrapeService {
|
||||
targetUrl = 'https://' + url
|
||||
}
|
||||
|
||||
console.log(`[ScrapeService] Fetching ${targetUrl}...`)
|
||||
|
||||
const response = await fetch(targetUrl, {
|
||||
headers: {
|
||||
|
||||
@@ -1128,8 +1128,6 @@ RULES:
|
||||
}),
|
||||
execute: async ({ title, diagram }) => {
|
||||
try {
|
||||
console.log('[Excalidraw Tool] INPUT title:', title)
|
||||
console.log('[Excalidraw Tool] INPUT diagram (first 500):', diagram?.substring(0, 500))
|
||||
|
||||
let elements: any[]
|
||||
let effectiveTitle = title
|
||||
@@ -1169,8 +1167,6 @@ RULES:
|
||||
elements = built.elements
|
||||
appliedStyle = style
|
||||
appliedType = effectiveType
|
||||
console.log('[Excalidraw Tool] Graph sanitize metrics:', sanitized.metrics)
|
||||
console.log('[Excalidraw Tool] Layout quality:', { ...built.layoutQuality, rankdir: built.rankdir, engine: built.engine, style, forcedAgentStyle, diagramType: effectiveType, zones: zones.length })
|
||||
} else if (parsed.elements && Array.isArray(parsed.elements)) {
|
||||
elements = parsed.elements.map((el: any, i: number) => normalizeElement(el, i))
|
||||
effectiveTitle = parsed.title || title || 'Diagram'
|
||||
@@ -1186,7 +1182,6 @@ RULES:
|
||||
return { success: false, error: 'No elements in diagram.' }
|
||||
}
|
||||
|
||||
console.log('[Excalidraw Tool] Elements count:', elements.length, '| Types:', [...new Set(elements.map((e: any) => e.type))])
|
||||
|
||||
const canvas = await prisma.canvas.create({
|
||||
data: {
|
||||
@@ -1202,7 +1197,6 @@ RULES:
|
||||
},
|
||||
})
|
||||
|
||||
console.log('[Excalidraw Tool] Canvas created:', canvas.id, canvas.name)
|
||||
|
||||
// Immediately mark the AgentAction as success so frontend polling unblocks
|
||||
if (ctx.actionId) {
|
||||
|
||||
@@ -63,7 +63,6 @@ RULES:
|
||||
try {
|
||||
// Hard cap: never more than 8 slides regardless of what the model outputs
|
||||
const cappedSlides = slides.slice(0, 8)
|
||||
console.log('[Slides Tool] Building presentation:', title, '| Slides:', cappedSlides.length, '| Theme:', theme)
|
||||
|
||||
const html = buildPresentationHTML({ title, theme, slides: cappedSlides as any })
|
||||
|
||||
@@ -82,7 +81,6 @@ RULES:
|
||||
},
|
||||
})
|
||||
|
||||
console.log('[Slides Tool] Canvas created:', canvas.id, '| Slides:', cappedSlides.length, '| Size:', Math.round(html.length / 1024), 'KB')
|
||||
|
||||
if (ctx.actionId) {
|
||||
await prisma.agentAction.update({
|
||||
|
||||
@@ -106,7 +106,6 @@ export async function deactivateUnauthorizedKeys(userId: string): Promise<number
|
||||
data: { isActive: false },
|
||||
});
|
||||
|
||||
console.log(`[byok] Deactivated ${toDeactivate.length} keys for user ${userId} after tier change to ${tier}`);
|
||||
return toDeactivate.length;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,7 +86,6 @@ export async function validateProviderApiKey(
|
||||
|
||||
// Bypass key validation in development or for test keys to allow local UI testing without live credentials
|
||||
if (process.env.NODE_ENV === 'development' || apiKey.startsWith('test-') || apiKey.startsWith('mock-')) {
|
||||
console.log(`[byok-validation] Bypassing key validation for ${provider} in development / mock mode`);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,6 @@ if (process.env.NODE_ENV !== 'production') {
|
||||
globalThis.prismaGlobal = prisma
|
||||
|
||||
const models = Object.keys(prisma).filter(k => !k.startsWith('_') && !k.startsWith('$'))
|
||||
console.log('[Prisma] Models loaded:', models.join(', '))
|
||||
}
|
||||
|
||||
export { prisma }
|
||||
|
||||
@@ -36,7 +36,6 @@ redis.on('error', (err) => {
|
||||
});
|
||||
|
||||
redis.on('connect', () => {
|
||||
console.log('[redis] Connected');
|
||||
});
|
||||
|
||||
if (process.env.NODE_ENV !== 'production') globalForRedis.redis = redis;
|
||||
|
||||
Reference in New Issue
Block a user