@yonathan124/zentry 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/core/ai.js ADDED
@@ -0,0 +1,328 @@
1
+ /**
2
+ * Zentry — Moteur IA v3.0
3
+ *
4
+ * Améliorations :
5
+ * - Prompt système ULTRA-STRICT avec scoring CVSS-like (0-10)
6
+ * - Nouveau provider : Mistral (meilleur rapport qualité/prix sur le code)
7
+ * - Retry automatique avec backoff exponentiel sur les erreurs réseau
8
+ * - Chunking intelligent : si le code dépasse la fenêtre, découpe proprement
9
+ * - Cache de résultats (évite de re-payer pour un fichier déjà analysé)
10
+ * - Parsing structuré de la réponse IA (JSON forcé)
11
+ */
12
+
13
+ // ─── PROMPT SYSTÈME ULTRA-STRICT ───────────────────────────────────────────
14
+ export const STRICT_SYSTEM_PROMPT = `Tu es un Auditeur de Sécurité et de Qualité de Code de niveau Industriel (OWASP Top 10, CWE, CVE).
15
+
16
+ ══════════════════════════════════════════════════════════
17
+ RÈGLES ABSOLUES — VIOLATION = RÉSULTAT INUTILISABLE
18
+ ══════════════════════════════════════════════════════════
19
+
20
+ RÈGLE 1 — ZÉRO FAUX POSITIF (la plus importante)
21
+ • Ne signale une faille que si tu as VU la ligne de code vulnérable.
22
+ • "Potentiellement" ou "il serait bon de" → ce n'est PAS une faille, c'est un conseil.
23
+ • Si le code fait la même chose de façon différente mais sécurisée → [SAFE]
24
+ • Si une lib gère la sécurité en interne (ex: Supabase RLS, Prisma paramètres auto) → valide [SAFE]
25
+
26
+ RÈGLE 2 — FORMAT DE SORTIE STRICT JSON
27
+ Retourne UNIQUEMENT du JSON valide, sans markdown, sans texte avant/après.
28
+
29
+ Si le code est sain :
30
+ { "status": "SAFE", "findings": [] }
31
+
32
+ Si des failles existent :
33
+ {
34
+ "status": "FINDINGS",
35
+ "findings": [
36
+ {
37
+ "file": "chemin/vers/fichier.ts",
38
+ "line": 42,
39
+ "severity": "CRITICAL|HIGH|MEDIUM|LOW",
40
+ "cvss": 9.1,
41
+ "cwe": "CWE-89",
42
+ "category": "Injection|Auth|XSS|IDOR|Config|Deps|Perf|Architecture",
43
+ "title": "Injection SQL via interpolation de chaîne",
44
+ "description": "La requête SQL à la ligne 42 concatène directement req.body.id sans paramètre préparé.",
45
+ "vulnerable_code": "const q = \`SELECT * FROM users WHERE id = \${req.body.id}\`",
46
+ "fix": "Utilise des paramètres préparés : db.query('SELECT * FROM users WHERE id = $1', [req.body.id])",
47
+ "auto_fixable": true
48
+ }
49
+ ]
50
+ }
51
+
52
+ RÈGLE 3 — SCORING CVSS OBLIGATOIRE
53
+ • CRITICAL : 9.0-10.0 — RCE, fuite de base de données complète, contournement d'auth total
54
+ • HIGH : 7.0-8.9 — IDOR, XSS stocké, injection SQL, secrets exposés
55
+ • MEDIUM : 4.0-6.9 — Rate limiting absent, informations de debug exposées, CSRF
56
+ • LOW : 0.1-3.9 — Problème de configuration mineur, code mort, mauvaise pratique sans impact direct
57
+
58
+ RÈGLE 4 — CHAMPS CWE ET AUTO-FIX
59
+ • Toujours référencer le CWE (Common Weakness Enumeration) pertinent
60
+ • "auto_fixable": true UNIQUEMENT si tu fournis le correctif complet et applicable directement
61
+ • "auto_fixable": false si le correctif nécessite une décision architecturale
62
+
63
+ RÈGLE 5 — CONTEXTE DU PROJET
64
+ Tu analyses du code de production réel. Chaque faille non signalée = risque réel pour les utilisateurs finaux.
65
+ Chaque faux positif = perte de confiance dans l'outil = outil abandonné.`;
66
+
67
+ // ─── CACHE EN MÉMOIRE ──────────────────────────────────────────────────────
68
+ const analysisCache = new Map();
69
+
70
+ function cacheKey(provider, model, content) {
71
+ // Hash simple basé sur la longueur + premiers 200 chars + derniers 200 chars
72
+ const sig = `${provider}:${model}:${content.length}:${content.slice(0, 200)}:${content.slice(-200)}`;
73
+ return sig;
74
+ }
75
+
76
+ // ─── RETRY AVEC BACKOFF ────────────────────────────────────────────────────
77
+ async function withRetry(fn, maxRetries = 3) {
78
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
79
+ try {
80
+ return await fn();
81
+ } catch (err) {
82
+ if (attempt === maxRetries) throw err;
83
+ // Backoff exponentiel : 1s, 2s, 4s
84
+ await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
85
+ }
86
+ }
87
+ }
88
+
89
+ // ─── CHUNKING INTELLIGENT ──────────────────────────────────────────────────
90
+ /**
91
+ * Si le code dépasse le seuil, le découpe par fonctions/classes/lignes
92
+ * pour ne jamais tronquer au milieu d'un contexte important.
93
+ */
94
+ function chunkContent(content, maxChars = 12000) {
95
+ if (content.length <= maxChars) return [content];
96
+
97
+ const chunks = [];
98
+ const lines = content.split('\n');
99
+ let currentChunk = '';
100
+ let lineIndex = 0;
101
+
102
+ while (lineIndex < lines.length) {
103
+ const line = lines[lineIndex];
104
+ // Coupe proprement sur une ligne vide ou une déclaration de fonction
105
+ if (currentChunk.length + line.length > maxChars) {
106
+ if (currentChunk.trim()) chunks.push(currentChunk);
107
+ currentChunk = line + '\n';
108
+ } else {
109
+ currentChunk += line + '\n';
110
+ }
111
+ lineIndex++;
112
+ }
113
+ if (currentChunk.trim()) chunks.push(currentChunk);
114
+ return chunks;
115
+ }
116
+
117
+ // ─── PARSING SÉCURISÉ DE LA RÉPONSE ───────────────────────────────────────
118
+ export function parseAIResponse(raw) {
119
+ try {
120
+ // Cherche le JSON dans la réponse (au cas où il y a du texte parasite)
121
+ const jsonMatch = raw.match(/\{[\s\S]*\}/);
122
+ if (!jsonMatch) return { status: 'SAFE', findings: [] };
123
+
124
+ const parsed = JSON.parse(jsonMatch[0]);
125
+
126
+ // Validation du format
127
+ if (!parsed.status || !Array.isArray(parsed.findings)) {
128
+ return { status: 'SAFE', findings: [] };
129
+ }
130
+
131
+ // Enrichit chaque finding avec un ID unique
132
+ parsed.findings = parsed.findings.map((f, i) => ({
133
+ id: `F${Date.now()}-${i}`,
134
+ ...f,
135
+ cvss: Number(f.cvss) || 5.0,
136
+ auto_fixable: Boolean(f.auto_fixable),
137
+ }));
138
+
139
+ return parsed;
140
+ } catch {
141
+ // Si l'IA a quand même répondu "SAFE" en texte libre
142
+ if (raw.includes('[SAFE]') || raw.trim() === 'SAFE') {
143
+ return { status: 'SAFE', findings: [] };
144
+ }
145
+ return { status: 'PARSE_ERROR', findings: [], raw };
146
+ }
147
+ }
148
+
149
+ // ─── PROVIDERS ─────────────────────────────────────────────────────────────
150
+ export const AI = {
151
+ anthropic: {
152
+ label: "Claude (Anthropic)",
153
+ models: ["claude-opus-4-5", "claude-3-5-sonnet-20241022", "claude-3-5-haiku-20241022"],
154
+ call: async (key, model, prompt) => {
155
+ const r = await fetch("https://api.anthropic.com/v1/messages", {
156
+ method: "POST",
157
+ headers: {
158
+ "Content-Type": "application/json",
159
+ "x-api-key": key,
160
+ "anthropic-version": "2023-06-01"
161
+ },
162
+ body: JSON.stringify({
163
+ model,
164
+ system: STRICT_SYSTEM_PROMPT,
165
+ max_tokens: 4096,
166
+ messages: [{ role: "user", content: prompt }]
167
+ })
168
+ });
169
+ if (!r.ok) throw new Error(`Anthropic API error ${r.status}`);
170
+ const d = await r.json();
171
+ return (d.content && d.content[0] && d.content[0].text) || '{"status":"SAFE","findings":[]}';
172
+ }
173
+ },
174
+
175
+ openai: {
176
+ label: "GPT-4 (OpenAI)",
177
+ models: ["gpt-4o", "gpt-4o-mini", "o4-mini"],
178
+ call: async (key, model, prompt) => {
179
+ const r = await fetch("https://api.openai.com/v1/chat/completions", {
180
+ method: "POST",
181
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
182
+ body: JSON.stringify({
183
+ model,
184
+ response_format: { type: "json_object" },
185
+ messages: [
186
+ { role: "system", content: STRICT_SYSTEM_PROMPT },
187
+ { role: "user", content: prompt }
188
+ ]
189
+ })
190
+ });
191
+ if (!r.ok) throw new Error(`OpenAI API error ${r.status}`);
192
+ const d = await r.json();
193
+ return (d.choices && d.choices[0]?.message?.content) || '{"status":"SAFE","findings":[]}';
194
+ }
195
+ },
196
+
197
+ groq: {
198
+ label: "Llama 3.3 (Groq — Ultra-rapide)",
199
+ models: ["llama-3.3-70b-versatile", "llama3-70b-8192", "mixtral-8x7b-32768"],
200
+ call: async (key, model, prompt) => {
201
+ const r = await fetch("https://api.groq.com/openai/v1/chat/completions", {
202
+ method: "POST",
203
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
204
+ body: JSON.stringify({
205
+ model,
206
+ response_format: { type: "json_object" },
207
+ messages: [
208
+ { role: "system", content: STRICT_SYSTEM_PROMPT },
209
+ { role: "user", content: prompt }
210
+ ]
211
+ })
212
+ });
213
+ if (!r.ok) throw new Error(`Groq API error ${r.status}`);
214
+ const d = await r.json();
215
+ return (d.choices && d.choices[0]?.message?.content) || '{"status":"SAFE","findings":[]}';
216
+ }
217
+ },
218
+
219
+ gemini: {
220
+ label: "Gemini 2.0 (Google)",
221
+ models: ["gemini-2.0-flash", "gemini-1.5-pro", "gemini-1.5-flash"],
222
+ call: async (key, model, prompt) => {
223
+ const r = await fetch(
224
+ `https://generativelanguage.googleapis.com/v1beta/models/${model}:generateContent?key=${key}`,
225
+ {
226
+ method: "POST",
227
+ headers: { "Content-Type": "application/json" },
228
+ body: JSON.stringify({
229
+ systemInstruction: { parts: [{ text: STRICT_SYSTEM_PROMPT }] },
230
+ contents: [{ parts: [{ text: prompt }] }],
231
+ generationConfig: {
232
+ maxOutputTokens: 4096,
233
+ responseMimeType: "application/json"
234
+ }
235
+ })
236
+ }
237
+ );
238
+ if (!r.ok) throw new Error(`Gemini API error ${r.status}`);
239
+ const d = await r.json();
240
+ return (d.candidates?.[0]?.content?.parts?.[0]?.text) || '{"status":"SAFE","findings":[]}';
241
+ }
242
+ },
243
+
244
+ mistral: {
245
+ label: "Mistral Large (Meilleur code)",
246
+ models: ["mistral-large-latest", "codestral-latest", "mistral-small-latest"],
247
+ call: async (key, model, prompt) => {
248
+ const r = await fetch("https://api.mistral.ai/v1/chat/completions", {
249
+ method: "POST",
250
+ headers: { "Content-Type": "application/json", "Authorization": `Bearer ${key}` },
251
+ body: JSON.stringify({
252
+ model,
253
+ response_format: { type: "json_object" },
254
+ messages: [
255
+ { role: "system", content: STRICT_SYSTEM_PROMPT },
256
+ { role: "user", content: prompt }
257
+ ]
258
+ })
259
+ });
260
+ if (!r.ok) throw new Error(`Mistral API error ${r.status}`);
261
+ const d = await r.json();
262
+ return (d.choices && d.choices[0]?.message?.content) || '{"status":"SAFE","findings":[]}';
263
+ }
264
+ }
265
+ };
266
+
267
+ // ─── FONCTION D'APPEL PRINCIPALE ───────────────────────────────────────────
268
+ /**
269
+ * Appel IA avec cache, retry et chunking automatiques.
270
+ * Retourne toujours un objet structuré { status, findings }.
271
+ */
272
+ export async function callAI(provider, key, model, prompt, useCache = true) {
273
+ const key_ = cacheKey(provider, model, prompt);
274
+
275
+ // Vérifie le cache
276
+ if (useCache && analysisCache.has(key_)) {
277
+ return { ...analysisCache.get(key_), fromCache: true };
278
+ }
279
+
280
+ const providerObj = AI[provider];
281
+ if (!providerObj) throw new Error(`Provider inconnu: ${provider}`);
282
+
283
+ // Chunking si le prompt est trop long
284
+ const promptChunks = chunkContent(prompt, 15000);
285
+
286
+ let allFindings = [];
287
+
288
+ for (const chunk of promptChunks) {
289
+ const raw = await withRetry(() => providerObj.call(key, model, chunk));
290
+ const parsed = parseAIResponse(raw);
291
+ if (parsed.findings) allFindings = allFindings.concat(parsed.findings);
292
+ }
293
+
294
+ const result = {
295
+ status: allFindings.length > 0 ? 'FINDINGS' : 'SAFE',
296
+ findings: allFindings,
297
+ provider,
298
+ model,
299
+ timestamp: new Date().toISOString()
300
+ };
301
+
302
+ // Met en cache
303
+ if (useCache) analysisCache.set(key_, result);
304
+
305
+ return result;
306
+ }
307
+
308
+ /**
309
+ * Vide le cache (utile entre deux scans complets)
310
+ */
311
+ export function clearAnalysisCache() {
312
+ analysisCache.clear();
313
+ }
314
+
315
+ /**
316
+ * Génère le prompt complet pour un bloc de fichiers
317
+ */
318
+ export function buildAuditPrompt(files, stack = null) {
319
+ const stackContext = stack
320
+ ? `\n\nCONTEXTE STACK : ${stack.label} (${stack.db}, auth: ${stack.auth})\nRÈGLES SPÉCIFIQUES:\n${stack.rules}`
321
+ : '';
322
+
323
+ const filesContent = files.map(({ path, content, truncated }) =>
324
+ `\n${'='.repeat(60)}\nFICHIER: ${path}${truncated ? ' [TRONQUÉ > 200kb]' : ''}\n${'='.repeat(60)}\n${content || '// Fichier vide ou illisible'}`
325
+ ).join('\n');
326
+
327
+ return `Analyse de sécurité et qualité pour les fichiers suivants.${stackContext}\n\nRetourne UNIQUEMENT du JSON valide selon le format spécifié.\n${filesContent}`;
328
+ }
@@ -0,0 +1,144 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+
4
+ /**
5
+ * Lance le profiler dans le répertoire passé en paramètre (ou process.cwd() par défaut).
6
+ * Accepter un `cwd` explicite permet de tester la fonction sans `process.chdir()`.
7
+ */
8
+ export async function runProfiler(cwd = process.cwd()) {
9
+ console.log('🔍 Zentry — Démarrage du Profiler IA de Sécurité...');
10
+
11
+ const config = {
12
+ project: 'Unknown',
13
+ stackProfile: 'node',
14
+ compliance: [],
15
+ sensitiveData: [],
16
+ sla: '99.9%',
17
+ customRules: [
18
+ { category: "SÉCURITÉ", rule: "Toutes les requêtes externes doivent inclure un timeout." }
19
+ ]
20
+ };
21
+
22
+ // 1. Lire package.json
23
+ const pkgPath = path.join(cwd, 'package.json');
24
+ if (fs.existsSync(pkgPath)) {
25
+ try {
26
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
27
+ config.project = pkg.name || path.basename(cwd);
28
+
29
+ const deps = { ...(pkg.dependencies || {}), ...(pkg.devDependencies || {}) };
30
+
31
+ // Détection Stack
32
+ if (deps['next']) config.stackProfile = 'njs-supa'; // ou njs-prisma
33
+ if (deps['express']) config.stackProfile = 'node-express';
34
+ if (deps['@supabase/supabase-js']) config.stackProfile = 'njs-supa';
35
+ if (deps['prisma']) config.stackProfile = 'njs-prisma';
36
+
37
+ // Détection Conformité
38
+ if (deps['stripe']) {
39
+ config.compliance.push('PCI-DSS');
40
+ config.sensitiveData.push('paiements', 'cartes bancaires');
41
+ }
42
+ } catch (e) {
43
+ console.warn('⚠️ Impossible de parser package.json');
44
+ }
45
+ } else {
46
+ config.project = path.basename(cwd);
47
+ }
48
+
49
+ // 2. Détection Prisma (Santé, RGPD)
50
+ const prismaPath = path.join(cwd, 'prisma', 'schema.prisma');
51
+ if (fs.existsSync(prismaPath)) {
52
+ const schema = fs.readFileSync(prismaPath, 'utf8').toLowerCase();
53
+ if (schema.includes('patient') || schema.includes('health') || schema.includes('medical')) {
54
+ config.compliance.push('HIPAA');
55
+ config.sensitiveData.push('données médicales');
56
+ }
57
+ if (schema.includes('user') || schema.includes('email') || schema.includes('password')) {
58
+ config.compliance.push('RGPD');
59
+ config.sensitiveData.push('données personnelles (PII)');
60
+ }
61
+ }
62
+
63
+ // Dédoublonnage
64
+ config.compliance = [...new Set(config.compliance)];
65
+ config.sensitiveData = [...new Set(config.sensitiveData)];
66
+
67
+ if (config.compliance.length === 0) {
68
+ config.compliance.push('RGPD'); // Base
69
+ }
70
+
71
+ // 3. Sauvegarde de la configuration globale
72
+ const configPath = path.join(cwd, 'zentry.config.json');
73
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
74
+ console.log(`✅ Fichier de configuration généré : ${configPath}`);
75
+
76
+ // 4. Génération des "System Prompts" IDE (Cursor, Windsurf, Cline)
77
+ const rulesContent = buildIdeRules(config);
78
+
79
+ const ideFiles = [
80
+ '.cursorrules',
81
+ '.windsurfrules',
82
+ '.clinerules',
83
+ '.github/copilot-instructions.md'
84
+ ];
85
+
86
+ for (const file of ideFiles) {
87
+ const filePath = path.join(cwd, file);
88
+ // Créer le dossier parent si nécessaire (.github)
89
+ const dir = path.dirname(filePath);
90
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
91
+
92
+ fs.writeFileSync(filePath, rulesContent);
93
+ console.log(`✅ Règles IDE de sécurité générées : ${file}`);
94
+ }
95
+
96
+ console.log('🎉 Initialisation Zentry terminée. Les IA de votre IDE sont désormais verrouillées sur ces règles de sécurité.');
97
+ }
98
+
99
+ function buildIdeRules(config) {
100
+ return `---
101
+ description: "Règles globales de sécurité et conformité générées par Zentry"
102
+ ---
103
+
104
+ # CONTEXTE PROJET ET CONFORMITÉ (ZENTRY)
105
+ Tu travailles sur le projet "${config.project}".
106
+ Ce projet est soumis à des contraintes de sécurité STRICTES.
107
+
108
+ ## NORMES DE CONFORMITÉ ACTIVES
109
+ ${config.compliance.map(c => `- ${c}`).join('\n')}
110
+
111
+ ## DONNÉES SENSIBLES MANIPULÉES
112
+ ${config.sensitiveData.length > 0 ? config.sensitiveData.map(d => `- ${d}`).join('\n') : '- Aucune donnée hautement sensible détectée par défaut.'}
113
+ SLA cible : ${config.sla}
114
+
115
+ # 🔴 RÈGLE ABSOLUE : NON-RÉGRESSION ET RESPECT DE L'EXISTANT
116
+ 1. AVANT toute modification, tu DOIS analyser le graphe d'import et l'architecture locale.
117
+ 2. Tu ne dois JAMAIS casser le code existant qui fonctionne. Tes modifications doivent s'intégrer harmonieusement sans causer d'effets de bord.
118
+ 3. Si ta modification risque d'impacter un autre fichier, demande confirmation à l'utilisateur ou assure-toi de mettre à jour le fichier impacté.
119
+
120
+ # 🛡️ DIRECTIVES DE SÉCURITÉ ("Security By Design")
121
+ Lors de la génération de nouveau code ou de la modification de code existant, tu dois OBLIGATOIREMENT respecter les règles suivantes :
122
+
123
+ 1. **Injection (SQL/NoSQL/Commandes) :**
124
+ - Utilise toujours des requêtes préparées ou l'ORM officiel (ex: Prisma).
125
+ - NE JAMAIS concaténer de chaînes de caractères contenant des inputs utilisateurs dans des requêtes.
126
+
127
+ 2. **Authentification et Autorisation :**
128
+ - Chaque nouvelle route d'API ou action serveur DOIT vérifier l'authentification ET l'autorisation (rôles/permissions) avant d'exécuter la moindre logique métier.
129
+ - Ne jamais faire confiance au client (valider systématiquement les IDs côté serveur).
130
+
131
+ 3. **Protection des Données (RGPD / HIPAA / PCI-DSS) :**
132
+ - Ne jamais logger (console.log) de mots de passe, tokens, clés d'API, ou données de cartes bancaires.
133
+ - Ne jamais renvoyer le mot de passe hashé au client.
134
+
135
+ 4. **Résilience (SLA ${config.sla}) :**
136
+ - Toutes les requêtes réseau externes (fetch, axios) doivent avoir un try/catch ET un Timeout défini.
137
+ - Gérer toutes les erreurs gracieusement sans exposer la stack trace au client.
138
+
139
+ 5. **Règles Personnalisées du Projet :**
140
+ ${config.customRules.map(r => `- [${r.category}] ${r.rule}`).join('\n')}
141
+
142
+ Si tu détectes que l'utilisateur te demande d'écrire du code qui viole ces règles, tu dois REFUSER et proposer la version sécurisée.
143
+ `;
144
+ }