import "dotenv/config"; import fs from "fs/promises"; import path from "path"; import { execSync } from "child_process"; import { testerAgent } from "../agents/tester/agent.js"; import { VulnerabilityReport, PoCResult } from "../agents/tester/types.js"; import { setupSandbox, applyPatchSmart, computePatchDiff } from "./runTesterBenchmark.js"; const DATASET_PATH = path.join(process.cwd(), "Proof-of-Patch-only-dataset"); const TEMP_DIR = path.join(process.cwd(), "temp_eval_run"); const CSV_FILE = path.join(process.cwd(), "data", "final_evaluation_results.csv"); async function runEvaluation() { const metadataStr = await fs.readFile(path.join(DATASET_PATH, "dataset_metadata.json"), "utf8"); const metadata = JSON.parse(metadataStr); const cases = Object.keys(metadata); // Configurações Globais const MAX_CASES = 1; const TIMEOUT_MS = 3 * 60 * 1000; // 3 minutos por caso const targetCases = cases.slice(0, MAX_CASES); console.log(`Iniciando avaliação final para ${targetCases.length} projetos...`); // Prepara o arquivo CSV const csvHeaders = [ "ID", "Time_Sec", "Reproducible", "Specific", "False_Positive_Rejected", "A_Infra_Iters", "A_Exploit_Iters", "A_Final_Error", "B_Infra_Iters", "B_Exploit_Iters", "B_Final_Error", "PoC_Code", "Patch_Diff", "A_Execution_Logs", "B_Execution_Logs" ]; await fs.mkdir(path.join(process.cwd(), "data"), { recursive: true }); await fs.writeFile(CSV_FILE, csvHeaders.join(";") + "\n"); for (const caseId of targetCases) { const data = metadata[caseId]; console.log(`\n\n${"=".repeat(60)}`); console.log(`=== INICIANDO CASO: ${caseId} (${data.repo_name}) ===`); console.log(`${"=".repeat(60)}`); const startTime = Date.now(); // ========================================== // CENÁRIO A: Verdadeiro Positivo (Vulnerável) // ========================================== console.log(`\n[CENÁRIO A] Testando reprodução real e especificidade...`); const setupInfo = await setupSandbox(caseId, data); if (!setupInfo) { console.log(`[${caseId}] Falha crítica no setup inicial.`); appendCsvRow([caseId, "0", "FALSE", "FALSE", "FALSE", "0", "SETUP_FAILED", "0", "", "", "", "", ""]); continue; } const sandboxDir = setupInfo; const targetPath = data.main_contract; // Path relative to project root let vulnerableCode = ""; try { vulnerableCode = await fs.readFile(path.join(sandboxDir, targetPath), "utf8"); } catch { vulnerableCode = "// Could not load source code"; } // Ler o arquivo de teste de referência (se houver) let referenceTestCode = ""; try { const allTestFiles = execSync(`find ${path.join(sandboxDir, "test")} -name "*.t.sol" -o -name "*.sol"`, { encoding: "utf8" }) .split("\n").filter(Boolean); if (allTestFiles.length > 0) { referenceTestCode = await fs.readFile(allTestFiles[0], "utf8"); } } catch(e) {} let patchDiff = ""; try { const patchSourceDir = path.join(process.cwd(), DATASET_PATH, data.patch); const targetDir = path.join(process.cwd(), DATASET_PATH, data.target_directory); patchDiff = await computePatchDiff(patchSourceDir, path.join(sandboxDir, targetPath), targetDir, targetPath); } catch {} const reportA: VulnerabilityReport = { id: caseId, severity: data.impact || "high", type: data.expected_vulnerability, title: `${data.repo_name} - ${caseId}`, description: data.annotation, affectedContract: { name: targetPath.split("/").pop()!.replace(".sol", ""), sourceCode: vulnerableCode, sourceFilePath: targetPath }, attackVector: data.expected_vulnerability, customSandboxDir: sandboxDir, referenceTestCode, patchDiff }; const resultA = await testerAgent.invoke({ report: reportA }, { recursionLimit: 100 }) as any; let reproducible = resultA.status === "success"; let specific = false; // Ler o PoC gerado diretamente do sandbox, já que o ReAct agent usa write_file let pocCodeStr = ""; try { pocCodeStr = await fs.readFile(path.join(sandboxDir, "test", "Exploit.t.sol"), "utf8"); } catch { pocCodeStr = resultA.pocCode || ""; } // Se reproduziu, testa a especificidade aplicando o patch if (reproducible) { console.log(`\n[CENÁRIO A] Reproduzível! PoC gerado com sucesso. Testando especificidade no patch...`); const patchApplied = await applyPatchSmart(caseId, sandboxDir); if (patchApplied) { // Escreve o teste na pasta (já patcheada) await fs.writeFile(path.join(sandboxDir, "test", "Exploit.t.sol"), pocCodeStr); const { runFoundry } = await import("../agents/tester/tools/foundryRunner.js"); const specResult = await runFoundry(pocCodeStr, sandboxDir); if (specResult.exitCode !== 0) { console.log(`[CENÁRIO A] Especificidade CONFIRMADA! O teste falhou após o patch.`); specific = true; } else { console.log(`[CENÁRIO A] FALSO ESPECÍFICO! O teste continuou passando mesmo no código corrigido.`); } } else { console.log(`[CENÁRIO A] Falha ao aplicar patch. Assumindo especificidade FALSA.`); } } const lastErrorA = (resultA as any).lastError || (resultA.status === "success" ? "" : "TIMEOUT"); const logsA = Buffer.from(((resultA as any).executionLogs || []).join("\n---\n")).toString("base64"); // ========================================== // CENÁRIO B: Teste de Falso Positivo (Patch) // ========================================== console.log(`\n[CENÁRIO B] Testando rejeição de falso positivo (Robustez)...`); // Recria a sandbox do zero await execSync(`rm -rf ${sandboxDir}`); const setupInfoB = await setupSandbox(caseId, data); let falsePositiveRejected = false; let resultB: Partial = { iterations: 0, status: "failed" }; let lastErrorB = ""; let logsB = ""; if (setupInfoB) { // Aplica o patch ANTES de chamar o agente (tornando o código seguro) const patchAppliedB = await applyPatchSmart(caseId, sandboxDir); if (patchAppliedB) { let patchedCode = ""; try { patchedCode = await fs.readFile(path.join(sandboxDir, data.main_contract), "utf8"); } catch { patchedCode = "// Could not load patched code"; } // Passa a MESMA anotação (mentindo que é vulnerável) const reportB: VulnerabilityReport = { ...reportA, affectedContract: { name: targetPath.split("/").pop()!.replace(".sol", ""), sourceCode: patchedCode, sourceFilePath: targetPath }, patchDiff: undefined // Oculta o patch diff do LLM para este cenário }; resultB = await testerAgent.invoke({ report: reportB }, { recursionLimit: 100 }) as any; // Se falhou em gerar exploit, REJEITOU com sucesso o falso positivo! if (resultB.status !== "success") { console.log(`\n[CENÁRIO B] SUCESSO DE ROBUSTEZ! Agente não conseguiu hackear o código seguro.`); falsePositiveRejected = true; } else { console.log(`\n[CENÁRIO B] ALUCINAÇÃO CRÍTICA! Agente hackeou um código que já estava corrigido.`); } lastErrorB = (resultB as any).lastError || (resultB.status === "success" ? "" : "TIMEOUT"); logsB = Buffer.from(((resultB as any).executionLogs || []).join("\n---\n")).toString("base64"); } } const totalTimeSec = Math.floor((Date.now() - startTime) / 1000); // Salva no CSV appendCsvRow([ caseId, totalTimeSec.toString(), reproducible ? "TRUE" : "FALSE", specific ? "TRUE" : "FALSE", falsePositiveRejected ? "TRUE" : "FALSE", (resultA as any).infraIterations || 0, (resultA as any).exploitIterations || 0, lastErrorA, (resultB as any).infraIterations || 0, (resultB as any).exploitIterations || 0, lastErrorB, Buffer.from(pocCodeStr).toString("base64"), reproducible ? Buffer.from(patchDiff).toString("base64") : "", logsA, logsB ]); console.log(`[${caseId}] Avaliação concluída em ${totalTimeSec}s. Salvo no CSV.`); } console.log(`\nAVALIAÇÃO FINAL CONCLUÍDA! Resultados em: ${CSV_FILE}`); } function escapeCsv(str: string) { if (!str) return ""; // Troca aspas duplas por duplas aspas duplas (padrão CSV) return `"${str.replace(/"/g, '""')}"`; } async function appendCsvRow(columns: string[]) { const row = columns.join(";") + "\n"; await fs.appendFile(CSV_FILE, row); } runEvaluation().catch(console.error);