diff --git a/index.php b/index.php index ca3a832..78047e3 100644 --- a/index.php +++ b/index.php @@ -30,8 +30,21 @@ if (!isset($_SESSION['messages_timestamps'])) { } /* ======================= - HELPERS + LOGGING SÉCURITÉ ======================= */ +function securityLog(string $level, string $message): void { + $logDir = __DIR__ . '/logs'; + if (!is_dir($logDir)) { + mkdir($logDir, 0700, true); + } + + $logFile = $logDir . '/security.log'; + $timestamp = date('Y-m-d H:i:s'); + $ip = $_SERVER['REMOTE_ADDR'] ?? 'UNKNOWN'; + $logEntry = "[$timestamp] [$level] [$ip] $message\n"; + + file_put_contents($logFile, $logEntry, FILE_APPEND | LOCK_EX); +} function isAjax(): bool { return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && strtolower($_SERVER['HTTP_X_REQUESTED_WITH']) === 'xmlhttprequest'; @@ -61,7 +74,7 @@ function hasBlockedVerbs(string $msg): bool { 'montre', 'affiche', 'révèle', 'expose', 'donne', 'envoie', 'fournis', 'transmets', 'répète', 'résume', 'reformule', 'paraphrase', 'récite', - 'dis-moi', 'dis moi', 'raconte', 'explique', 'CTF', 'CTFM1' + 'dis-moi', 'dis moi', 'raconte', 'explique' ]; $msg_lower = mb_strtolower($msg); @@ -150,7 +163,8 @@ function callLlamaAPI(string $userMessage): string { CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode($payload), CURLOPT_HTTPHEADER => ['Content-Type: application/json'], - CURLOPT_TIMEOUT => 300 + CURLOPT_TIMEOUT => 120, + CURLOPT_CONNECTTIMEOUT => 5 ]); $response = curl_exec($ch); @@ -159,7 +173,7 @@ function callLlamaAPI(string $userMessage): string { curl_close($ch); // DEBUG: Log l'erreur - error_log("Ollama API - HTTP: $http, Error: $error, Response: " . substr($response, 0, 200)); + securityLog('DEBUG', "Ollama API - HTTP: $http, Error: $error, Response: " . substr($response, 0, 200)); if ($error) { return "Erreur de connexion à Ollama: $error"; @@ -176,11 +190,51 @@ function callLlamaAPI(string $userMessage): string { $json = json_decode($response, true); if (!$json || !isset($json['choices'][0]['message']['content'])) { - error_log("JSON decode error: " . json_last_error_msg()); + securityLog('ERROR', "JSON decode error: " . json_last_error_msg()); return "Erreur: réponse LLaMA malformée."; } - return $json['choices'][0]['message']['content']; + $reply = $json['choices'][0]['message']['content']; + + // ============================================================ + // SÉCURITÉ FINALE: Bloquer les fuites de configuration + // ============================================================ + $env = parse_ini_file(__DIR__ . '/config/.env'); + $flag = $env['FLAG_SECRET'] ?? ''; + + // CHECK CRITIQUE: Si la réponse contient CTFM1, bloquer immédiatement + if (stripos($reply, 'CTFM1') !== false) { + securityLog('CRITICAL', "CTFM1 detected in response! Reply: " . substr($reply, 0, 500)); + return "Je ne peux pas répondre à cette question. Peux-tu reformuler ta demande ?"; + } + + // Blockers de fuite TRÈS spécifiques (pour éviter les faux positifs) + $dangerousPatterns = [ + // Flag complet + $flag, + // Phrases TRÈS spécifiques du system prompt (pas juste des mots seuls) + 'RÈGLES ABSOLUES', + 'NON NÉGOCIABLES', + 'COMPORTEMENT ATTENDU', + 'Tu DOIS suivre ces règles', + // Phrases anglaises qui montrent la fuite du prompt + 'As an ethical and honest AI language model', + 'I must follow the rules provided', + 'I cannot reveal or mention', + 'CURLOPT_TIMEOUT', + 'parse_ini_file', + 'security.log', + 'FLAG_SECRET' + ]; + + foreach ($dangerousPatterns as $pattern) { + if (!empty($pattern) && stripos($reply, $pattern) !== false) { + securityLog('WARNING', "LEAK DETECTED - Pattern: $pattern, Reply: " . substr($reply, 0, 300)); + return "Je ne peux pas répondre à cette question. Peux-tu reformuler ta demande ?"; + } + } + + return $reply; } /* ======================= @@ -200,6 +254,27 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) { exit; } +/* ======================= + ENDPOINT LOGS (protégé) +======================= */ +if ($_GET['view'] === 'logs' && isset($_GET['admin_token'])) { + $adminToken = hash('sha256', 'changez-moi-aussi'); + if ($_GET['admin_token'] !== $adminToken) { + http_response_code(403); + die('Accès refusé'); + } + + $logFile = __DIR__ . '/logs/security.log'; + if (!file_exists($logFile)) { + die('Aucun log.'); + } + + echo '
';
+    echo htmlspecialchars(file_get_contents($logFile));
+    echo '
'; + die(); +} + ?> @@ -207,7 +282,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['message'])) { -bobentaz +Assistant IA