Spaces:
Runtime error
Runtime error
Tales-Cunha commited on
Commit ·
7e26449
1
Parent(s): 7e6051f
feat: update the archtecture from the testet agent
Browse filesI add new prompts and new structure. I also add the benchmark to
evaluate the agent through time and change. I am using the same model
- data/benchmark_summary.json +144 -0
- scripts/setup-sandbox.sh +1 -1
- src/agents/tester/agent.ts +132 -47
- src/agents/tester/index.ts +1 -1
- src/agents/tester/prompts/system.ts +38 -80
- src/agents/tester/state.ts +5 -0
- src/agents/tester/tools/foundryRunner.ts +18 -9
- src/agents/tester/tools/scaffoldGenerator.ts +20 -12
- src/agents/tester/types.ts +7 -1
- src/agents/tester/utils/logAnalyzer.ts +2 -2
- src/agents/tester/utils/parserUtils.ts +37 -0
- src/benchmark/runTesterBenchmark.ts +302 -0
data/benchmark_summary.json
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"summary": {
|
| 3 |
+
"timestamp": "2026-06-07T21:36:40.793Z",
|
| 4 |
+
"total_processed": 22,
|
| 5 |
+
"reproducibility_rate": 27.27272727272727,
|
| 6 |
+
"specificity_rate": 0,
|
| 7 |
+
"overall_ground_truth_rate": 0,
|
| 8 |
+
"average_iterations": 8.181818181818182
|
| 9 |
+
},
|
| 10 |
+
"details": [
|
| 11 |
+
{
|
| 12 |
+
"id": "001",
|
| 13 |
+
"reproducible": false,
|
| 14 |
+
"specific": false,
|
| 15 |
+
"iterations": 10
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"id": "003",
|
| 19 |
+
"reproducible": true,
|
| 20 |
+
"specific": false,
|
| 21 |
+
"iterations": 5
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"id": "008",
|
| 25 |
+
"reproducible": true,
|
| 26 |
+
"specific": false,
|
| 27 |
+
"iterations": 5
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"id": "009",
|
| 31 |
+
"reproducible": false,
|
| 32 |
+
"specific": false,
|
| 33 |
+
"iterations": 10
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"id": "015",
|
| 37 |
+
"reproducible": false,
|
| 38 |
+
"specific": false,
|
| 39 |
+
"iterations": 10
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"id": "018",
|
| 43 |
+
"reproducible": false,
|
| 44 |
+
"specific": false,
|
| 45 |
+
"iterations": 10
|
| 46 |
+
},
|
| 47 |
+
{
|
| 48 |
+
"id": "020",
|
| 49 |
+
"reproducible": false,
|
| 50 |
+
"specific": false,
|
| 51 |
+
"iterations": 10
|
| 52 |
+
},
|
| 53 |
+
{
|
| 54 |
+
"id": "032",
|
| 55 |
+
"reproducible": false,
|
| 56 |
+
"specific": false,
|
| 57 |
+
"iterations": 10
|
| 58 |
+
},
|
| 59 |
+
{
|
| 60 |
+
"id": "033",
|
| 61 |
+
"reproducible": false,
|
| 62 |
+
"specific": false,
|
| 63 |
+
"iterations": 10
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"id": "039",
|
| 67 |
+
"reproducible": false,
|
| 68 |
+
"specific": false,
|
| 69 |
+
"iterations": 10
|
| 70 |
+
},
|
| 71 |
+
{
|
| 72 |
+
"id": "041",
|
| 73 |
+
"reproducible": false,
|
| 74 |
+
"specific": false,
|
| 75 |
+
"iterations": 10
|
| 76 |
+
},
|
| 77 |
+
{
|
| 78 |
+
"id": "042",
|
| 79 |
+
"reproducible": false,
|
| 80 |
+
"specific": false,
|
| 81 |
+
"iterations": 10
|
| 82 |
+
},
|
| 83 |
+
{
|
| 84 |
+
"id": "048",
|
| 85 |
+
"reproducible": false,
|
| 86 |
+
"specific": false,
|
| 87 |
+
"iterations": 10
|
| 88 |
+
},
|
| 89 |
+
{
|
| 90 |
+
"id": "049",
|
| 91 |
+
"reproducible": false,
|
| 92 |
+
"specific": false,
|
| 93 |
+
"iterations": 10
|
| 94 |
+
},
|
| 95 |
+
{
|
| 96 |
+
"id": "051",
|
| 97 |
+
"reproducible": true,
|
| 98 |
+
"specific": false,
|
| 99 |
+
"iterations": 1
|
| 100 |
+
},
|
| 101 |
+
{
|
| 102 |
+
"id": "054",
|
| 103 |
+
"reproducible": true,
|
| 104 |
+
"specific": false,
|
| 105 |
+
"iterations": 4
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"id": "058",
|
| 109 |
+
"reproducible": false,
|
| 110 |
+
"specific": false,
|
| 111 |
+
"iterations": 10
|
| 112 |
+
},
|
| 113 |
+
{
|
| 114 |
+
"id": "066",
|
| 115 |
+
"reproducible": false,
|
| 116 |
+
"specific": false,
|
| 117 |
+
"iterations": 10
|
| 118 |
+
},
|
| 119 |
+
{
|
| 120 |
+
"id": "070",
|
| 121 |
+
"reproducible": false,
|
| 122 |
+
"specific": false,
|
| 123 |
+
"iterations": 10
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"id": "077",
|
| 127 |
+
"reproducible": false,
|
| 128 |
+
"specific": false,
|
| 129 |
+
"iterations": 10
|
| 130 |
+
},
|
| 131 |
+
{
|
| 132 |
+
"id": "091",
|
| 133 |
+
"reproducible": true,
|
| 134 |
+
"specific": false,
|
| 135 |
+
"iterations": 4
|
| 136 |
+
},
|
| 137 |
+
{
|
| 138 |
+
"id": "098",
|
| 139 |
+
"reproducible": true,
|
| 140 |
+
"specific": false,
|
| 141 |
+
"iterations": 1
|
| 142 |
+
}
|
| 143 |
+
]
|
| 144 |
+
}
|
scripts/setup-sandbox.sh
CHANGED
|
@@ -1,7 +1,7 @@
|
|
| 1 |
#!/bin/bash
|
| 2 |
set -e
|
| 3 |
|
| 4 |
-
SANDBOX="/tmp/poc-sandbox"
|
| 5 |
|
| 6 |
# Tenta encontrar forge no PATH se a variável não estiver definida ou falhar
|
| 7 |
if [ -z "$FORGE_BIN" ] || [ ! -f "$FORGE_BIN" ]; then
|
|
|
|
| 1 |
#!/bin/bash
|
| 2 |
set -e
|
| 3 |
|
| 4 |
+
SANDBOX="${SANDBOX_DIR:-/tmp/poc-sandbox}"
|
| 5 |
|
| 6 |
# Tenta encontrar forge no PATH se a variável não estiver definida ou falhar
|
| 7 |
if [ -z "$FORGE_BIN" ] || [ ! -f "$FORGE_BIN" ]; then
|
src/agents/tester/agent.ts
CHANGED
|
@@ -4,12 +4,20 @@ import { PoCStateAnnotation, PoCState } from "./state.js";
|
|
| 4 |
import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
|
| 5 |
import { OracleContext } from "./types.js";
|
| 6 |
import { createLLM } from "../../config/llm.ts";
|
| 7 |
-
import {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 8 |
import { extractSolidity } from "./utils/extractSolidity.js";
|
| 9 |
import { runFoundry } from "./tools/foundryRunner.js";
|
| 10 |
import { analyzeFoundryLog } from "./utils/logAnalyzer.js";
|
|
|
|
|
|
|
| 11 |
|
| 12 |
-
const MAX_ITERATIONS =
|
| 13 |
|
| 14 |
const llm = createLLM();
|
| 15 |
|
|
@@ -17,47 +25,128 @@ async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
|
|
| 17 |
console.log("[oracleNode] gerando scaffold para:", state.report.title);
|
| 18 |
|
| 19 |
const solidityScaffold = generateLocalScaffold(state.report);
|
| 20 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
|
| 22 |
-
console.log("[oracleNode] scaffold gerado,
|
| 23 |
return { oracleContext };
|
| 24 |
}
|
| 25 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 26 |
async function generatePoCNode(state: PoCState): Promise<Partial<PoCState>> {
|
| 27 |
-
const { report, oracleContext, executionLogs, pocCode, iterations, lastError } = state;
|
| 28 |
const isRetry = iterations > 0;
|
| 29 |
|
| 30 |
-
|
| 31 |
-
|
| 32 |
|
| 33 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 34 |
\`\`\`solidity
|
| 35 |
-
${
|
| 36 |
\`\`\`
|
| 37 |
|
| 38 |
-
|
| 39 |
-
${
|
| 40 |
-
|
| 41 |
-
Análise do erro: ${lastError ?? "desconhecido"}
|
| 42 |
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
- Título: ${report.title}
|
| 46 |
-
- Tipo: ${report.type}
|
| 47 |
-
- Descrição: ${report.description}
|
| 48 |
-
- Vetor de Ataque: ${report.attackVector}
|
| 49 |
-
${report.exploitablePaths ? `- Caminhos de Exploração:\n * ${report.exploitablePaths.join("\n * ")}` : ""}
|
| 50 |
|
| 51 |
-
|
| 52 |
\`\`\`solidity
|
| 53 |
${oracleContext!.solidityScaffold}
|
| 54 |
-
\`\`\`
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 55 |
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
|
| 58 |
try {
|
| 59 |
const response = await llm.invoke([
|
| 60 |
-
{ role: "system", content:
|
| 61 |
{ role: "user", content: userMessage },
|
| 62 |
]);
|
| 63 |
const solidityCode = extractSolidity(response.content as string);
|
|
@@ -77,14 +166,22 @@ async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
|
|
| 77 |
const isMissingContract = !trimmedCode.includes("contract ExploitTest");
|
| 78 |
const isMissingTest = !trimmedCode.includes("function test_Exploit()");
|
| 79 |
const isPlaceholder = trimmedCode.includes("TODO: implementar exploit");
|
| 80 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
const summary = state.lastError ?? (isMissingCode
|
| 82 |
? "Código Solidity ausente. O LLM não retornou o arquivo do exploit."
|
| 83 |
: isMissingContract
|
| 84 |
? "Contrato ExploitTest não encontrado no arquivo."
|
| 85 |
: isMissingTest
|
| 86 |
? "Função test_Exploit() não encontrada no arquivo."
|
| 87 |
-
:
|
|
|
|
|
|
|
| 88 |
);
|
| 89 |
const status = state.iterations >= MAX_ITERATIONS ? "failed" : "running";
|
| 90 |
return {
|
|
@@ -94,7 +191,7 @@ async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
|
|
| 94 |
};
|
| 95 |
}
|
| 96 |
|
| 97 |
-
const result = await runFoundry(state.pocCode);
|
| 98 |
const analysis = analyzeFoundryLog(result);
|
| 99 |
const noTestsFound = result.combined.includes("No tests found");
|
| 100 |
const summary = noTestsFound
|
|
@@ -118,30 +215,16 @@ async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
|
|
| 118 |
|
| 119 |
return {
|
| 120 |
executionLogs: [result.combined], // reducer append
|
| 121 |
-
lastError: summary,
|
| 122 |
status,
|
| 123 |
};
|
| 124 |
}
|
| 125 |
|
| 126 |
async function reflectNode(state: PoCState): Promise<Partial<PoCState>> {
|
| 127 |
-
|
| 128 |
-
|
| 129 |
-
|
| 130 |
-
}
|
| 131 |
-
|
| 132 |
-
const mockResult = {
|
| 133 |
-
exitCode: 1, timedOut: lastLog.includes("TIMEOUT"),
|
| 134 |
-
stdout: "", stderr: "", combined: lastLog,
|
| 135 |
-
};
|
| 136 |
-
|
| 137 |
-
const analysis = analyzeFoundryLog(mockResult as any);
|
| 138 |
-
|
| 139 |
-
console.log(`[reflectNode] categoria: ${analysis.category}`);
|
| 140 |
-
console.log(`[reflectNode] resumo: ${analysis.summary}`);
|
| 141 |
-
|
| 142 |
-
return {
|
| 143 |
-
lastError: `[${analysis.category.toUpperCase()}] ${analysis.summary}\n\nLinhas relevantes:\n${analysis.relevantLines.join("\n")}`,
|
| 144 |
-
};
|
| 145 |
}
|
| 146 |
|
| 147 |
function routeAfterFoundry(state: PoCState): "reflectNode" | typeof END {
|
|
@@ -153,11 +236,13 @@ function routeAfterFoundry(state: PoCState): "reflectNode" | typeof END {
|
|
| 153 |
|
| 154 |
const graph = new StateGraph(PoCStateAnnotation)
|
| 155 |
.addNode("oracleNode", oracleNode)
|
|
|
|
| 156 |
.addNode("generatePoCNode", generatePoCNode)
|
| 157 |
.addNode("runFoundryNode", runFoundryNode)
|
| 158 |
.addNode("reflectNode", reflectNode)
|
| 159 |
.addEdge(START, "oracleNode")
|
| 160 |
-
.addEdge("oracleNode", "
|
|
|
|
| 161 |
.addEdge("generatePoCNode", "runFoundryNode")
|
| 162 |
.addConditionalEdges("runFoundryNode", routeAfterFoundry, {
|
| 163 |
reflectNode: "reflectNode",
|
|
|
|
| 4 |
import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
|
| 5 |
import { OracleContext } from "./types.js";
|
| 6 |
import { createLLM } from "../../config/llm.ts";
|
| 7 |
+
import {
|
| 8 |
+
SYSTEM_PROMPT,
|
| 9 |
+
ANALYZE_VULNERABILITY_PROMPT,
|
| 10 |
+
POC_INITIAL_PROMPT,
|
| 11 |
+
POC_COMPILE_FIX_PROMPT,
|
| 12 |
+
POC_TEST_FIX_PROMPT
|
| 13 |
+
} from "./prompts/system.js";
|
| 14 |
import { extractSolidity } from "./utils/extractSolidity.js";
|
| 15 |
import { runFoundry } from "./tools/foundryRunner.js";
|
| 16 |
import { analyzeFoundryLog } from "./utils/logAnalyzer.js";
|
| 17 |
+
import { extractConstructor } from "./utils/parserUtils.js";
|
| 18 |
+
import { analyzeSolidityFile } from "../auditor/tools/solidity-analyzer-tool.js";
|
| 19 |
|
| 20 |
+
const MAX_ITERATIONS = 10;
|
| 21 |
|
| 22 |
const llm = createLLM();
|
| 23 |
|
|
|
|
| 25 |
console.log("[oracleNode] gerando scaffold para:", state.report.title);
|
| 26 |
|
| 27 |
const solidityScaffold = generateLocalScaffold(state.report);
|
| 28 |
+
|
| 29 |
+
// Extrair info do constructor para ajudar o LLM no setUp
|
| 30 |
+
const constructorInfo = extractConstructor(state.report.affectedContract.sourceCode, state.report.affectedContract.name);
|
| 31 |
+
|
| 32 |
+
// STEP 3: Automated API Discovery
|
| 33 |
+
console.log("[oracleNode] analisando API do contrato e helpers de teste...");
|
| 34 |
+
const targetContractAPI = await analyzeSolidityFile(state.report.affectedContract.sourceCode, "short");
|
| 35 |
+
|
| 36 |
+
let referenceTestHelpers = "";
|
| 37 |
+
if (state.report.referenceTestCode) {
|
| 38 |
+
referenceTestHelpers = await analyzeSolidityFile(state.report.referenceTestCode, "short");
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
const oracleContext: OracleContext = {
|
| 42 |
+
solidityScaffold,
|
| 43 |
+
constructorInfo: constructorInfo?.parameters,
|
| 44 |
+
targetContractAPI,
|
| 45 |
+
referenceTestHelpers
|
| 46 |
+
};
|
| 47 |
|
| 48 |
+
console.log("[oracleNode] scaffold gerado, context built.");
|
| 49 |
return { oracleContext };
|
| 50 |
}
|
| 51 |
|
| 52 |
+
/**
|
| 53 |
+
* NEW: Multi-Pass Node 1 - Analysis
|
| 54 |
+
*/
|
| 55 |
+
async function analyzeVulnerabilityNode(state: PoCState): Promise<Partial<PoCState>> {
|
| 56 |
+
console.log("[testerAgent] analyzeVulnerabilityNode: analyzing bug...");
|
| 57 |
+
|
| 58 |
+
const userMessage = `Vulnerability Report:
|
| 59 |
+
- Title: ${state.report.title}
|
| 60 |
+
- Type: ${state.report.type}
|
| 61 |
+
- Description: ${state.report.description}
|
| 62 |
+
|
| 63 |
+
### Target Contract Source Code (${state.report.affectedContract.name}):
|
| 64 |
+
\`\`\`solidity
|
| 65 |
+
${state.report.affectedContract.sourceCode}
|
| 66 |
+
\`\`\`
|
| 67 |
+
|
| 68 |
+
### Target Contract API:
|
| 69 |
+
${state.oracleContext!.targetContractAPI}
|
| 70 |
+
|
| 71 |
+
${state.oracleContext!.referenceTestHelpers ? `### Environment Helpers:
|
| 72 |
+
${state.oracleContext!.referenceTestHelpers}` : ""}
|
| 73 |
+
`;
|
| 74 |
+
|
| 75 |
+
const response = await llm.invoke([
|
| 76 |
+
{ role: "system", content: ANALYZE_VULNERABILITY_PROMPT },
|
| 77 |
+
{ role: "user", content: userMessage },
|
| 78 |
+
]);
|
| 79 |
+
|
| 80 |
+
return { vulnerabilityAnalysis: response.content as string };
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
/**
|
| 84 |
+
* Multi-Pass Node 2 - Code Generation (Initial & Fixes)
|
| 85 |
+
*/
|
| 86 |
async function generatePoCNode(state: PoCState): Promise<Partial<PoCState>> {
|
| 87 |
+
const { report, oracleContext, executionLogs, pocCode, iterations, lastError, vulnerabilityAnalysis } = state;
|
| 88 |
const isRetry = iterations > 0;
|
| 89 |
|
| 90 |
+
let currentSystemPrompt = SYSTEM_PROMPT;
|
| 91 |
+
let userMessage = "";
|
| 92 |
|
| 93 |
+
if (!isRetry) {
|
| 94 |
+
// PASS 2: INITIAL GENERATION
|
| 95 |
+
currentSystemPrompt = POC_INITIAL_PROMPT;
|
| 96 |
+
userMessage = `Vulnerability Analysis Plan:
|
| 97 |
+
${vulnerabilityAnalysis}
|
| 98 |
+
|
| 99 |
+
### Contract Source:
|
| 100 |
\`\`\`solidity
|
| 101 |
+
${report.affectedContract.sourceCode}
|
| 102 |
\`\`\`
|
| 103 |
|
| 104 |
+
### API Reference:
|
| 105 |
+
${oracleContext!.targetContractAPI}
|
|
|
|
|
|
|
| 106 |
|
| 107 |
+
${oracleContext!.referenceTestHelpers ? `### Test Helpers:
|
| 108 |
+
${oracleContext!.referenceTestHelpers}` : ""}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 109 |
|
| 110 |
+
### Scaffold:
|
| 111 |
\`\`\`solidity
|
| 112 |
${oracleContext!.solidityScaffold}
|
| 113 |
+
\`\`\`
|
| 114 |
+
`;
|
| 115 |
+
} else {
|
| 116 |
+
// PASS 3+: FIXING ERRORS (BRANCHING)
|
| 117 |
+
const isCompilerError = lastError?.includes("[COMPILER_ERROR]");
|
| 118 |
+
currentSystemPrompt = isCompilerError ? POC_COMPILE_FIX_PROMPT : POC_TEST_FIX_PROMPT;
|
| 119 |
+
|
| 120 |
+
userMessage = `The previous PoC failed.
|
| 121 |
+
|
| 122 |
+
Error Category: ${isCompilerError ? "Compilation Failure" : "Execution/Logic Failure"}
|
| 123 |
+
Forge Output:
|
| 124 |
+
${executionLogs[executionLogs.length - 1]?.slice(0, 3000) ?? "sem logs"}
|
| 125 |
|
| 126 |
+
Previous Code:
|
| 127 |
+
\`\`\`solidity
|
| 128 |
+
${pocCode}
|
| 129 |
+
\`\`\`
|
| 130 |
+
|
| 131 |
+
Analysis of the bug:
|
| 132 |
+
${vulnerabilityAnalysis}
|
| 133 |
+
|
| 134 |
+
Fix the code. Return the entire file.`;
|
| 135 |
+
}
|
| 136 |
+
|
| 137 |
+
console.log(`[testerAgent] generatePoCNode iteração ${iterations + 1}, isRetry=${isRetry}, mode=${isRetry ? (lastError?.includes("[COMPILER_ERROR]") ? "FIX_COMPILE" : "FIX_LOGIC") : "INITIAL"}`);
|
| 138 |
+
|
| 139 |
+
// DEBUG: Output context before sending to LLM
|
| 140 |
+
if (process.env.DEBUG_CONTEXT === "true") {
|
| 141 |
+
console.log("\n" + "=".repeat(20) + " LLM CONTEXT START " + "=".repeat(20));
|
| 142 |
+
console.log("System Prompt:", currentSystemPrompt);
|
| 143 |
+
console.log("User Message:", userMessage);
|
| 144 |
+
console.log("=".repeat(20) + " LLM CONTEXT END " + "=".repeat(20) + "\n");
|
| 145 |
+
}
|
| 146 |
|
| 147 |
try {
|
| 148 |
const response = await llm.invoke([
|
| 149 |
+
{ role: "system", content: currentSystemPrompt },
|
| 150 |
{ role: "user", content: userMessage },
|
| 151 |
]);
|
| 152 |
const solidityCode = extractSolidity(response.content as string);
|
|
|
|
| 166 |
const isMissingContract = !trimmedCode.includes("contract ExploitTest");
|
| 167 |
const isMissingTest = !trimmedCode.includes("function test_Exploit()");
|
| 168 |
const isPlaceholder = trimmedCode.includes("TODO: implementar exploit");
|
| 169 |
+
const isLazyTest = (
|
| 170 |
+
trimmedCode.includes("assertTrue(true") ||
|
| 171 |
+
trimmedCode.includes("assert(true") ||
|
| 172 |
+
trimmedCode.includes("assert(1 == 1")
|
| 173 |
+
) && !trimmedCode.includes("assertEq") && !trimmedCode.includes("assertGt") && !trimmedCode.includes("assertLe") && !trimmedCode.includes("assertGe") && !trimmedCode.includes("assertNotEq");
|
| 174 |
+
|
| 175 |
+
if (isMissingCode || isMissingContract || isMissingTest || isPlaceholder || isLazyTest) {
|
| 176 |
const summary = state.lastError ?? (isMissingCode
|
| 177 |
? "Código Solidity ausente. O LLM não retornou o arquivo do exploit."
|
| 178 |
: isMissingContract
|
| 179 |
? "Contrato ExploitTest não encontrado no arquivo."
|
| 180 |
: isMissingTest
|
| 181 |
? "Função test_Exploit() não encontrada no arquivo."
|
| 182 |
+
: isPlaceholder
|
| 183 |
+
? "Exploit não implementado (placeholder TODO ainda presente)."
|
| 184 |
+
: "Exploit muito fraco (assertTrue(true)). Você deve provar a vulnerabilidade com uma asserção real (ex: assertEq, assertGt)."
|
| 185 |
);
|
| 186 |
const status = state.iterations >= MAX_ITERATIONS ? "failed" : "running";
|
| 187 |
return {
|
|
|
|
| 191 |
};
|
| 192 |
}
|
| 193 |
|
| 194 |
+
const result = await runFoundry(state.pocCode, state.report.customSandboxDir);
|
| 195 |
const analysis = analyzeFoundryLog(result);
|
| 196 |
const noTestsFound = result.combined.includes("No tests found");
|
| 197 |
const summary = noTestsFound
|
|
|
|
| 215 |
|
| 216 |
return {
|
| 217 |
executionLogs: [result.combined], // reducer append
|
| 218 |
+
lastError: passed ? null : `[${analysis.category.toUpperCase()}] ${summary}`,
|
| 219 |
status,
|
| 220 |
};
|
| 221 |
}
|
| 222 |
|
| 223 |
async function reflectNode(state: PoCState): Promise<Partial<PoCState>> {
|
| 224 |
+
// Now reflectNode is simpler as we moved the logic to specialized prompts in generatePoCNode
|
| 225 |
+
// But we still use it to log the reflection
|
| 226 |
+
console.log(`[reflectNode] reflecting on error: ${state.lastError}`);
|
| 227 |
+
return {};
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 228 |
}
|
| 229 |
|
| 230 |
function routeAfterFoundry(state: PoCState): "reflectNode" | typeof END {
|
|
|
|
| 236 |
|
| 237 |
const graph = new StateGraph(PoCStateAnnotation)
|
| 238 |
.addNode("oracleNode", oracleNode)
|
| 239 |
+
.addNode("analyzeVulnerabilityNode", analyzeVulnerabilityNode)
|
| 240 |
.addNode("generatePoCNode", generatePoCNode)
|
| 241 |
.addNode("runFoundryNode", runFoundryNode)
|
| 242 |
.addNode("reflectNode", reflectNode)
|
| 243 |
.addEdge(START, "oracleNode")
|
| 244 |
+
.addEdge("oracleNode", "analyzeVulnerabilityNode")
|
| 245 |
+
.addEdge("analyzeVulnerabilityNode", "generatePoCNode")
|
| 246 |
.addEdge("generatePoCNode", "runFoundryNode")
|
| 247 |
.addConditionalEdges("runFoundryNode", routeAfterFoundry, {
|
| 248 |
reflectNode: "reflectNode",
|
src/agents/tester/index.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { VulnerabilityReport, PoCResult } from "./types.js";
|
|
| 9 |
export async function runPoCGenerator(report: VulnerabilityReport): Promise<PoCResult> {
|
| 10 |
console.log(`[runPoCGenerator] Iniciando para: ${report.id} — ${report.title}`);
|
| 11 |
|
| 12 |
-
const finalState = await testerAgent.invoke({ report });
|
| 13 |
|
| 14 |
const result: PoCResult = {
|
| 15 |
reportId: report.id,
|
|
|
|
| 9 |
export async function runPoCGenerator(report: VulnerabilityReport): Promise<PoCResult> {
|
| 10 |
console.log(`[runPoCGenerator] Iniciando para: ${report.id} — ${report.title}`);
|
| 11 |
|
| 12 |
+
const finalState = await testerAgent.invoke({ report }, { recursionLimit: 100 });
|
| 13 |
|
| 14 |
const result: PoCResult = {
|
| 15 |
reportId: report.id,
|
src/agents/tester/prompts/system.ts
CHANGED
|
@@ -1,96 +1,54 @@
|
|
| 1 |
export const SYSTEM_PROMPT = `You are an expert smart contract security testing specialist. Your mission is to generate executable Proof-of-Concept (PoC) exploits demonstrating vulnerabilities using Foundry.
|
| 2 |
|
| 3 |
-
##
|
| 4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
-
|
| 7 |
-
Parse the vulnerability description provided and analyze the vulnerability type, affected code sections, and potential impact. Analyze the contract logic to understand the root cause before developing exploits.
|
| 8 |
|
| 9 |
-
|
| 10 |
-
Use Foundry exclusively for testing. Utilize Foundry cheatcodes for test control: "vm.prank()" for identity switching, "vm.deal()" for ETH funding, "vm.warp()" for time manipulation, "vm.expectRevert()" for failure testing.
|
| 11 |
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
|
| 21 |
-
|
| 22 |
-
Debug compilation errors and test failures systematically using Forge output. If stuck on the same issue for >3 attempts, shift to a minimal working demonstration—proving the vulnerability exists matters more than setup complexity.
|
| 23 |
|
| 24 |
-
|
| 25 |
-
The assertion in your test MUST prove the vulnerability. For example, if funds are stolen, assert that the vault balance decreased and the attacker balance increased.
|
| 26 |
|
| 27 |
-
##
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
-
|
| 31 |
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
pragma solidity ^0.8.20;
|
| 38 |
-
import "forge-std/Test.sol";
|
| 39 |
-
contract Target { function withdraw(uint256) public {} } // Source code
|
| 40 |
-
contract ExploitTest is Test {
|
| 41 |
-
Target target;
|
| 42 |
-
function setUp() public { target = new Target(); }
|
| 43 |
-
function test_Exploit() public {
|
| 44 |
-
// TODO: implementar exploit aqui
|
| 45 |
-
}
|
| 46 |
-
}
|
| 47 |
-
\`\`\`
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
// SPDX-License-Identifier: UNLICENSED
|
| 52 |
-
pragma solidity ^0.8.20;
|
| 53 |
-
import "forge-std/Test.sol";
|
| 54 |
-
contract Target { function withdraw(uint256) public {} } // Source code
|
| 55 |
|
| 56 |
-
|
| 57 |
-
contract Attacker {
|
| 58 |
-
Target target;
|
| 59 |
-
constructor(address _target) {
|
| 60 |
-
target = Target(_target);
|
| 61 |
-
}
|
| 62 |
-
fallback() external payable {
|
| 63 |
-
if (address(target).balance >= 1 ether) {
|
| 64 |
-
target.withdraw(1 ether);
|
| 65 |
-
}
|
| 66 |
-
}
|
| 67 |
-
function attack() external {
|
| 68 |
-
target.withdraw(1 ether);
|
| 69 |
-
}
|
| 70 |
-
}
|
| 71 |
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
target = new Target();
|
| 78 |
-
}
|
| 79 |
|
| 80 |
-
|
| 81 |
-
vm.startPrank(address(0xBEEF));
|
| 82 |
-
|
| 83 |
-
// 1. Deploy malicious contract
|
| 84 |
-
Attacker attacker = new Attacker(address(target));
|
| 85 |
-
|
| 86 |
-
// 2. Exploit the vulnerability using direct function calls
|
| 87 |
-
attacker.attack();
|
| 88 |
-
|
| 89 |
-
// 3. Verify the exploit succeeded (no special characters in assertion strings)
|
| 90 |
-
assertEq(address(target).balance, 0, "Target contract should be drained");
|
| 91 |
-
|
| 92 |
-
vm.stopPrank();
|
| 93 |
-
}
|
| 94 |
-
}
|
| 95 |
-
\`\`\`
|
| 96 |
`.trim();
|
|
|
|
| 1 |
export const SYSTEM_PROMPT = `You are an expert smart contract security testing specialist. Your mission is to generate executable Proof-of-Concept (PoC) exploits demonstrating vulnerabilities using Foundry.
|
| 2 |
|
| 3 |
+
## General Guidelines
|
| 4 |
+
- Use Foundry exclusively.
|
| 5 |
+
- Use \`vm.prank()\`, \`vm.deal()\`, \`vm.warp()\`, \`vm.expectRevert()\` as needed.
|
| 6 |
+
- **NO PLACEHOLDER TESTS:** Never write a test that only contains \`assertTrue(true)\`. You MUST use concrete assertions to prove the exploit's impact.
|
| 7 |
+
- **Context Compliance:** Reuse existing imports and setup patterns found in the provided code/reference tests.
|
| 8 |
+
- DO NOT rename \`test_Exploit()\`.
|
| 9 |
+
`.trim();
|
| 10 |
|
| 11 |
+
export const ANALYZE_VULNERABILITY_PROMPT = `Analyze the vulnerability in the following Solidity contract and provide a clear understanding of the issue.
|
|
|
|
| 12 |
|
| 13 |
+
Focus on understanding the root cause and mechanism of the vulnerability.
|
|
|
|
| 14 |
|
| 15 |
+
Please provide:
|
| 16 |
+
1. **Clear explanation**: What is the bug?
|
| 17 |
+
2. **Exploit path**: Step-by-step how to trigger it.
|
| 18 |
+
3. **Conditions required**: What state must the contract be in?
|
| 19 |
+
4. **Expected outcome**: What specific assertion will prove the vulnerability exists (and would fail if patched)?
|
| 20 |
|
| 21 |
+
Be concise and technical.
|
| 22 |
+
`.trim();
|
| 23 |
|
| 24 |
+
export const POC_INITIAL_PROMPT = `Based on your vulnerability analysis, generate a comprehensive Proof of Concept (PoC) test that demonstrates the vulnerability.
|
|
|
|
| 25 |
|
| 26 |
+
Sua missão: completar a função test_Exploit() no scaffold fornecido, seguindo o seu plano de análise.
|
|
|
|
| 27 |
|
| 28 |
+
## REGRAS DE QUALIDADE
|
| 29 |
+
- A asserção final DEVE provar a vulnerabilidade.
|
| 30 |
+
- Use o mesmo estilo de imports e setup dos contratos de referência fornecidos.
|
| 31 |
+
- Output APENAS um bloco \`\`\`solidity ... \`\`\` com o arquivo completo.
|
| 32 |
+
`.trim();
|
| 33 |
|
| 34 |
+
export const POC_COMPILE_FIX_PROMPT = `The previous POC test failed to COMPILE. Please fix the compilation errors and regenerate the complete test file.
|
| 35 |
|
| 36 |
+
Focus strictly on:
|
| 37 |
+
- Fixing import statements and dependencies.
|
| 38 |
+
- Correcting Solidity syntax errors or missing members.
|
| 39 |
+
- Ensuring proper contract instantiation and function signatures.
|
| 40 |
+
- Resolving visibility issues.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 41 |
|
| 42 |
+
Return the FULL corrected Solidity file in a \`\`\`solidity\`\`\` block.
|
| 43 |
+
`.trim();
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
|
| 45 |
+
export const POC_TEST_FIX_PROMPT = `The POC test compiled successfully but FAILED during execution (Revert or Assertion failure). Please fix the test logic and regenerate the complete test file.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 46 |
|
| 47 |
+
Focus strictly on:
|
| 48 |
+
- Correcting test logic and assertions to match the vulnerability.
|
| 49 |
+
- Fixing contract setup and initialization (realistic balances, roles).
|
| 50 |
+
- Ensuring proper exploit execution flow (e.g. correct order of calls).
|
| 51 |
+
- Verifying that the vulnerability demonstration is accurate and specific.
|
|
|
|
|
|
|
| 52 |
|
| 53 |
+
Return the FULL corrected Solidity file in a \`\`\`solidity\`\`\` block.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 54 |
`.trim();
|
src/agents/tester/state.ts
CHANGED
|
@@ -14,6 +14,11 @@ export const PoCStateAnnotation = Annotation.Root({
|
|
| 14 |
reducer: (_, y) => y, // overwrite — always latest version
|
| 15 |
}),
|
| 16 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
executionLogs: Annotation<string[]>({
|
| 18 |
default: () => [],
|
| 19 |
reducer: (x, y) => x.concat(y), // append — never lose previous logs
|
|
|
|
| 14 |
reducer: (_, y) => y, // overwrite — always latest version
|
| 15 |
}),
|
| 16 |
|
| 17 |
+
vulnerabilityAnalysis: Annotation<string>({
|
| 18 |
+
default: () => "",
|
| 19 |
+
reducer: (_, y) => y, // overwrite
|
| 20 |
+
}),
|
| 21 |
+
|
| 22 |
executionLogs: Annotation<string[]>({
|
| 23 |
default: () => [],
|
| 24 |
reducer: (x, y) => x.concat(y), // append — never lose previous logs
|
src/agents/tester/tools/foundryRunner.ts
CHANGED
|
@@ -4,7 +4,7 @@ import { writeFile, access } from "fs/promises";
|
|
| 4 |
import { join } from "path";
|
| 5 |
|
| 6 |
const execAsync = promisify(exec);
|
| 7 |
-
const
|
| 8 |
const TIMEOUT_MS = 60_000;
|
| 9 |
|
| 10 |
export interface FoundryResult {
|
|
@@ -18,27 +18,36 @@ export interface FoundryResult {
|
|
| 18 |
/**
|
| 19 |
* Garante que o sandbox Foundry existe e está inicializado.
|
| 20 |
*/
|
| 21 |
-
async function ensureSandbox() {
|
| 22 |
try {
|
| 23 |
-
await access(join(
|
| 24 |
} catch {
|
| 25 |
-
console.log(
|
| 26 |
// Caminho absoluto para o script de setup (assume execução da raiz do projeto)
|
| 27 |
-
await execAsync("./scripts/setup-sandbox.sh");
|
| 28 |
}
|
| 29 |
}
|
| 30 |
|
| 31 |
-
export async function runFoundry(solidityCode: string): Promise<FoundryResult> {
|
| 32 |
-
await ensureSandbox();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
// Escrever o arquivo no sandbox
|
| 35 |
-
|
|
|
|
| 36 |
|
| 37 |
try {
|
| 38 |
const { stdout, stderr } = await execAsync(
|
| 39 |
"forge test --match-contract ExploitTest -vvvv",
|
| 40 |
{
|
| 41 |
-
cwd:
|
| 42 |
timeout: TIMEOUT_MS,
|
| 43 |
env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` }
|
| 44 |
}
|
|
|
|
| 4 |
import { join } from "path";
|
| 5 |
|
| 6 |
const execAsync = promisify(exec);
|
| 7 |
+
const DEFAULT_SANDBOX = process.env.SANDBOX_DIR || "/tmp/poc-sandbox";
|
| 8 |
const TIMEOUT_MS = 60_000;
|
| 9 |
|
| 10 |
export interface FoundryResult {
|
|
|
|
| 18 |
/**
|
| 19 |
* Garante que o sandbox Foundry existe e está inicializado.
|
| 20 |
*/
|
| 21 |
+
async function ensureSandbox(sandboxDir: string) {
|
| 22 |
try {
|
| 23 |
+
await access(join(sandboxDir, "foundry.toml"));
|
| 24 |
} catch {
|
| 25 |
+
console.log(`[foundryRunner] Sandbox em ${sandboxDir} não encontrado. Inicializando...`);
|
| 26 |
// Caminho absoluto para o script de setup (assume execução da raiz do projeto)
|
| 27 |
+
await execAsync("./scripts/setup-sandbox.sh", { env: { ...process.env, SANDBOX_DIR: sandboxDir } });
|
| 28 |
}
|
| 29 |
}
|
| 30 |
|
| 31 |
+
export async function runFoundry(solidityCode: string, sandboxDir: string = DEFAULT_SANDBOX): Promise<FoundryResult> {
|
| 32 |
+
await ensureSandbox(sandboxDir);
|
| 33 |
+
|
| 34 |
+
// Ensure test directory exists
|
| 35 |
+
const testDir = join(sandboxDir, "test");
|
| 36 |
+
try {
|
| 37 |
+
await access(testDir);
|
| 38 |
+
} catch {
|
| 39 |
+
await execAsync(`mkdir -p "${testDir}"`);
|
| 40 |
+
}
|
| 41 |
|
| 42 |
// Escrever o arquivo no sandbox
|
| 43 |
+
const testPath = join(testDir, "Exploit.t.sol");
|
| 44 |
+
await writeFile(testPath, solidityCode, "utf-8");
|
| 45 |
|
| 46 |
try {
|
| 47 |
const { stdout, stderr } = await execAsync(
|
| 48 |
"forge test --match-contract ExploitTest -vvvv",
|
| 49 |
{
|
| 50 |
+
cwd: sandboxDir,
|
| 51 |
timeout: TIMEOUT_MS,
|
| 52 |
env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` }
|
| 53 |
}
|
src/agents/tester/tools/scaffoldGenerator.ts
CHANGED
|
@@ -3,27 +3,35 @@ import { VulnerabilityReport } from "../types.js";
|
|
| 3 |
export function generateLocalScaffold(report: VulnerabilityReport): string {
|
| 4 |
const cheatcodes = report.suggestedCheatcodes?.join(", ") ?? "vm.deal, vm.prank, vm.warp";
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
return `// SPDX-License-Identifier: UNLICENSED
|
| 7 |
-
pragma
|
| 8 |
|
| 9 |
import "forge-std/Test.sol";
|
| 10 |
import "forge-std/console.sol";
|
| 11 |
|
| 12 |
-
|
| 13 |
-
${report.affectedContract.sourceCode}
|
| 14 |
-
// ─────────────────────────────────────────────────────────────────────────────
|
| 15 |
|
| 16 |
contract ExploitTest is Test {
|
| 17 |
${report.affectedContract.name} target;
|
| 18 |
address constant ATTACKER = address(0xBEEF);
|
| 19 |
|
| 20 |
-
// setUp()
|
| 21 |
-
function setUp() public {
|
| 22 |
-
target = new ${report.affectedContract.name}();
|
| 23 |
-
vm.deal(
|
| 24 |
-
vm.deal(ATTACKER, 10 ether);
|
| 25 |
-
vm.label(address(target), "TARGET");
|
| 26 |
-
vm.label(ATTACKER, "ATTACKER");
|
| 27 |
}
|
| 28 |
|
| 29 |
// Vulnerabilidade: ${report.title}
|
|
@@ -32,7 +40,7 @@ contract ExploitTest is Test {
|
|
| 32 |
${report.exploitablePaths ? `// Caminhos de Exploração:\n // - ${report.exploitablePaths.join("\n // - ")}` : ""}
|
| 33 |
// Cheatcodes sugeridos: ${cheatcodes}
|
| 34 |
//
|
| 35 |
-
//
|
| 36 |
function test_Exploit() public {
|
| 37 |
vm.startPrank(ATTACKER);
|
| 38 |
// TODO: implementar exploit aqui
|
|
|
|
| 3 |
export function generateLocalScaffold(report: VulnerabilityReport): string {
|
| 4 |
const cheatcodes = report.suggestedCheatcodes?.join(", ") ?? "vm.deal, vm.prank, vm.warp";
|
| 5 |
|
| 6 |
+
// Extrair pragma do código original para evitar conflitos de versão
|
| 7 |
+
const pragmaMatch = report.affectedContract.sourceCode.match(/pragma solidity ([^;]+);/);
|
| 8 |
+
const pragma = pragmaMatch ? pragmaMatch[0] : "pragma solidity ^0.8.20;";
|
| 9 |
+
|
| 10 |
+
let contractSetup = "";
|
| 11 |
+
if (report.affectedContract.sourceFilePath) {
|
| 12 |
+
// Se temos o caminho do arquivo, importamos ao invés de colar
|
| 13 |
+
contractSetup = `import { ${report.affectedContract.name} } from "../${report.affectedContract.sourceFilePath}";`;
|
| 14 |
+
} else {
|
| 15 |
+
// Fallback: colar o código (pode falhar por causa de imports ausentes no sandbox)
|
| 16 |
+
contractSetup = `// ── Código-fonte do contrato vulnerável ──────────────────────────────────────\n${report.affectedContract.sourceCode}\n// ─────────────────────────────────────────────────────────────────────────────`;
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
return `// SPDX-License-Identifier: UNLICENSED
|
| 20 |
+
${pragma}
|
| 21 |
|
| 22 |
import "forge-std/Test.sol";
|
| 23 |
import "forge-std/console.sol";
|
| 24 |
|
| 25 |
+
${contractSetup}
|
|
|
|
|
|
|
| 26 |
|
| 27 |
contract ExploitTest is Test {
|
| 28 |
${report.affectedContract.name} target;
|
| 29 |
address constant ATTACKER = address(0xBEEF);
|
| 30 |
|
| 31 |
+
// setUp() - O Oracle tentou gerar um básico, mas sinta-se à vontade para ajustar se o contrato for complexo
|
| 32 |
+
function setUp() public virtual {
|
| 33 |
+
// target = new ${report.affectedContract.name}(...); // TODO: ajustar se necessário
|
| 34 |
+
vm.deal(ATTACKER, 100 ether);
|
|
|
|
|
|
|
|
|
|
| 35 |
}
|
| 36 |
|
| 37 |
// Vulnerabilidade: ${report.title}
|
|
|
|
| 40 |
${report.exploitablePaths ? `// Caminhos de Exploração:\n // - ${report.exploitablePaths.join("\n // - ")}` : ""}
|
| 41 |
// Cheatcodes sugeridos: ${cheatcodes}
|
| 42 |
//
|
| 43 |
+
// Implemente test_Exploit() e ajuste o setUp() se o contrato exigir argumentos no constructor.
|
| 44 |
function test_Exploit() public {
|
| 45 |
vm.startPrank(ATTACKER);
|
| 46 |
// TODO: implementar exploit aqui
|
src/agents/tester/types.ts
CHANGED
|
@@ -21,17 +21,23 @@ export interface VulnerabilityReport {
|
|
| 21 |
description: string;
|
| 22 |
affectedContract: {
|
| 23 |
name: string;
|
| 24 |
-
sourceCode: string;
|
|
|
|
| 25 |
};
|
| 26 |
attackVector: string;
|
| 27 |
suggestedCheatcodes?: string[];
|
| 28 |
codeSnippet?: string;
|
| 29 |
location?: string;
|
| 30 |
exploitablePaths?: string[];
|
|
|
|
|
|
|
| 31 |
}
|
| 32 |
|
| 33 |
export interface OracleContext {
|
| 34 |
solidityScaffold: string; // Exploit.t.sol parcial com setUp() pronto
|
|
|
|
|
|
|
|
|
|
| 35 |
}
|
| 36 |
|
| 37 |
export interface PoCResult {
|
|
|
|
| 21 |
description: string;
|
| 22 |
affectedContract: {
|
| 23 |
name: string;
|
| 24 |
+
sourceCode: string;
|
| 25 |
+
sourceFilePath?: string;
|
| 26 |
};
|
| 27 |
attackVector: string;
|
| 28 |
suggestedCheatcodes?: string[];
|
| 29 |
codeSnippet?: string;
|
| 30 |
location?: string;
|
| 31 |
exploitablePaths?: string[];
|
| 32 |
+
customSandboxDir?: string; // Caminho para execução do Forge (opcional)
|
| 33 |
+
referenceTestCode?: string; // Código de um teste existente para referência de setup
|
| 34 |
}
|
| 35 |
|
| 36 |
export interface OracleContext {
|
| 37 |
solidityScaffold: string; // Exploit.t.sol parcial com setUp() pronto
|
| 38 |
+
constructorInfo?: string; // Assinatura do constructor para ajudar no deploy
|
| 39 |
+
targetContractAPI?: string; // Resumo dos métodos e variáveis do contrato alvo
|
| 40 |
+
referenceTestHelpers?: string; // Resumo das funções auxiliares disponíveis no ambiente de teste
|
| 41 |
}
|
| 42 |
|
| 43 |
export interface PoCResult {
|
src/agents/tester/utils/logAnalyzer.ts
CHANGED
|
@@ -23,8 +23,8 @@ export function analyzeFoundryLog(result: FoundryResult): LogAnalysis {
|
|
| 23 |
|
| 24 |
if (result.combined.includes("Compiler run failed")) {
|
| 25 |
const lines = result.combined.split("\n")
|
| 26 |
-
.filter(l => l.includes("Error") || l.includes("error") || l.includes("-->"))
|
| 27 |
-
.slice(0,
|
| 28 |
return {
|
| 29 |
category: "compiler_error",
|
| 30 |
summary: "Erro de compilação Solidity. Verifique: interfaces faltando, assinaturas incorretas, tipos incompatíveis.",
|
|
|
|
| 23 |
|
| 24 |
if (result.combined.includes("Compiler run failed")) {
|
| 25 |
const lines = result.combined.split("\n")
|
| 26 |
+
.filter(l => (l.includes("Error") || l.includes("error") || l.includes("-->")) && !l.includes("Warning"))
|
| 27 |
+
.slice(0, 30);
|
| 28 |
return {
|
| 29 |
category: "compiler_error",
|
| 30 |
summary: "Erro de compilação Solidity. Verifique: interfaces faltando, assinaturas incorretas, tipos incompatíveis.",
|
src/agents/tester/utils/parserUtils.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import * as parser from "@solidity-parser/parser";
|
| 2 |
+
|
| 3 |
+
export interface ConstructorInfo {
|
| 4 |
+
parameters: string;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
export function extractConstructor(sourceCode: string, contractName: string): ConstructorInfo | null {
|
| 8 |
+
try {
|
| 9 |
+
const ast = parser.parse(sourceCode, { range: true });
|
| 10 |
+
let constructorParams = "";
|
| 11 |
+
let found = false;
|
| 12 |
+
|
| 13 |
+
parser.visit(ast, {
|
| 14 |
+
ContractDefinition: (node) => {
|
| 15 |
+
if (node.name === contractName) {
|
| 16 |
+
for (const part of node.subNodes) {
|
| 17 |
+
if (part.type === "FunctionDefinition" && part.isConstructor) {
|
| 18 |
+
found = true;
|
| 19 |
+
if (part.range) {
|
| 20 |
+
constructorParams = sourceCode.slice(part.range[0], part.range[1]).split("{")[0].trim();
|
| 21 |
+
}
|
| 22 |
+
}
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
});
|
| 27 |
+
|
| 28 |
+
if (found) {
|
| 29 |
+
return {
|
| 30 |
+
parameters: constructorParams
|
| 31 |
+
};
|
| 32 |
+
}
|
| 33 |
+
} catch (e) {
|
| 34 |
+
// console.warn("Failed to parse Solidity for constructor:", e);
|
| 35 |
+
}
|
| 36 |
+
return null;
|
| 37 |
+
}
|
src/benchmark/runTesterBenchmark.ts
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import fs from "fs/promises";
|
| 2 |
+
import path from "path";
|
| 3 |
+
import { exec } from "child_process";
|
| 4 |
+
import { promisify } from "util";
|
| 5 |
+
import { runPoCGenerator } from "../agents/tester/index.js";
|
| 6 |
+
import { VulnerabilityReport } from "../agents/tester/types.js";
|
| 7 |
+
import "dotenv/config";
|
| 8 |
+
|
| 9 |
+
const execAsync = promisify(exec);
|
| 10 |
+
const DATASET_PATH = "Proof-of-Patch-only-dataset";
|
| 11 |
+
const METADATA_FILE = path.join(DATASET_PATH, "dataset_metadata.json");
|
| 12 |
+
const SUMMARY_FILE = "data/benchmark_summary.json";
|
| 13 |
+
|
| 14 |
+
/**
|
| 15 |
+
* Extracts the likely vulnerable file path from annotation text.
|
| 16 |
+
* Looks for paths ending in .sol or github links.
|
| 17 |
+
*/
|
| 18 |
+
/**
|
| 19 |
+
* Extracts the likely vulnerable file path from annotation text.
|
| 20 |
+
*/
|
| 21 |
+
function extractVulnerableFilePath(text: string): string | null {
|
| 22 |
+
// Matches GitHub blob links: /blob/branch/path/to/File.sol
|
| 23 |
+
const githubBlobRegex = /\/blob\/[^/]+\/([^#\s]+\.sol)/g;
|
| 24 |
+
let match;
|
| 25 |
+
if ((match = githubBlobRegex.exec(text)) !== null) {
|
| 26 |
+
return match[1];
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Fallback to general .sol paths
|
| 30 |
+
const solPathRegex = /(?:^|[\s])([a-zA-Z0-9._/-]+\.sol)(?:#L\d+)?/g;
|
| 31 |
+
const paths: string[] = [];
|
| 32 |
+
while ((match = solPathRegex.exec(text)) !== null) {
|
| 33 |
+
const p = match[1];
|
| 34 |
+
if (!p.includes("test/") && !p.includes("Test.sol")) {
|
| 35 |
+
paths.push(p);
|
| 36 |
+
}
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
// Prioritize paths containing "src"
|
| 40 |
+
const srcPath = paths.find(p => p.includes("src/"));
|
| 41 |
+
return srcPath || (paths.length > 0 ? paths[0] : null);
|
| 42 |
+
}
|
| 43 |
+
|
| 44 |
+
/**
|
| 45 |
+
* Recursively finds a file by name within a directory, prioritizing src/
|
| 46 |
+
*/
|
| 47 |
+
async function findFileRecursively(dir: string, fileName: string): Promise<string | null> {
|
| 48 |
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
| 49 |
+
const subdirs: string[] = [];
|
| 50 |
+
|
| 51 |
+
// Check files in current dir first
|
| 52 |
+
for (const entry of entries) {
|
| 53 |
+
const fullPath = path.join(dir, entry.name);
|
| 54 |
+
if (entry.isFile() && entry.name === fileName) {
|
| 55 |
+
return fullPath;
|
| 56 |
+
}
|
| 57 |
+
if (entry.isDirectory() && entry.name !== "lib" && entry.name !== "node_modules") {
|
| 58 |
+
subdirs.push(fullPath);
|
| 59 |
+
}
|
| 60 |
+
}
|
| 61 |
+
|
| 62 |
+
// Prioritize "src" subdirectory if it exists
|
| 63 |
+
const srcDir = subdirs.find(d => path.basename(d) === "src");
|
| 64 |
+
if (srcDir) {
|
| 65 |
+
const found = await findFileRecursively(srcDir, fileName);
|
| 66 |
+
if (found) return found;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
// Check other subdirs
|
| 70 |
+
for (const subdir of subdirs) {
|
| 71 |
+
if (path.basename(subdir) === "src") continue; // Already checked
|
| 72 |
+
const found = await findFileRecursively(subdir, fileName);
|
| 73 |
+
if (found) return found;
|
| 74 |
+
}
|
| 75 |
+
|
| 76 |
+
// Fallback to lib if nothing else found
|
| 77 |
+
const libDir = entries.find(e => e.isDirectory() && e.name === "lib");
|
| 78 |
+
if (libDir) {
|
| 79 |
+
return findFileRecursively(path.join(dir, "lib"), fileName);
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
return null;
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
async function main() {
|
| 86 |
+
const metadataContent = await fs.readFile(METADATA_FILE, "utf-8");
|
| 87 |
+
const metadata = JSON.parse(metadataContent);
|
| 88 |
+
const findingsIds = Object.keys(metadata);
|
| 89 |
+
|
| 90 |
+
const limit = process.argv[2] ? parseInt(process.argv[2]) : findingsIds.length;
|
| 91 |
+
|
| 92 |
+
console.log(`[Benchmark] Starting evaluation (Total available: ${findingsIds.length}, Limit: ${limit})...`);
|
| 93 |
+
|
| 94 |
+
const results: any[] = [];
|
| 95 |
+
let processedCount = 0;
|
| 96 |
+
|
| 97 |
+
for (const id of findingsIds) {
|
| 98 |
+
if (processedCount >= limit) break;
|
| 99 |
+
|
| 100 |
+
const finding = metadata[id];
|
| 101 |
+
|
| 102 |
+
if (finding.benchmark_results?.vuln_status === "success" && !process.env.FORCE_RERUN) {
|
| 103 |
+
console.log(`[${id}] Skipping: already successful.`);
|
| 104 |
+
processedCount++;
|
| 105 |
+
continue;
|
| 106 |
+
}
|
| 107 |
+
|
| 108 |
+
console.log(`\n--- [${id}] ${finding.repo_name} ---`);
|
| 109 |
+
processedCount++;
|
| 110 |
+
|
| 111 |
+
try {
|
| 112 |
+
const annotationPath = path.join(DATASET_PATH, finding.annotations);
|
| 113 |
+
let annotationText = "";
|
| 114 |
+
try {
|
| 115 |
+
annotationText = await fs.readFile(annotationPath, "utf-8");
|
| 116 |
+
} catch (e) {
|
| 117 |
+
console.warn(`[${id}] Annotation file not found at ${annotationPath}`);
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
const targetDir = path.join(process.cwd(), DATASET_PATH, finding.target_directory);
|
| 121 |
+
|
| 122 |
+
// STEP 1: Accurate Source Code Resolution
|
| 123 |
+
const extractedPath = extractVulnerableFilePath(annotationText);
|
| 124 |
+
let mainContractPath = "";
|
| 125 |
+
let relativeContractPath = finding.main_contract;
|
| 126 |
+
|
| 127 |
+
if (extractedPath) {
|
| 128 |
+
const directPath = path.join(targetDir, extractedPath);
|
| 129 |
+
try {
|
| 130 |
+
await fs.access(directPath);
|
| 131 |
+
mainContractPath = directPath;
|
| 132 |
+
relativeContractPath = extractedPath;
|
| 133 |
+
} catch {
|
| 134 |
+
const fileName = path.basename(extractedPath);
|
| 135 |
+
console.log(`[${id}] File not found at ${extractedPath}, searching for ${fileName} recursively...`);
|
| 136 |
+
const foundPath = await findFileRecursively(targetDir, fileName);
|
| 137 |
+
if (foundPath) {
|
| 138 |
+
mainContractPath = foundPath;
|
| 139 |
+
relativeContractPath = path.relative(targetDir, foundPath);
|
| 140 |
+
}
|
| 141 |
+
}
|
| 142 |
+
}
|
| 143 |
+
|
| 144 |
+
if (!mainContractPath) {
|
| 145 |
+
mainContractPath = path.join(targetDir, finding.main_contract);
|
| 146 |
+
relativeContractPath = finding.main_contract;
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
console.log(`[${id}] Using source file: ${relativeContractPath}`);
|
| 150 |
+
|
| 151 |
+
let sourceCode = "";
|
| 152 |
+
try {
|
| 153 |
+
sourceCode = await fs.readFile(mainContractPath, "utf-8");
|
| 154 |
+
} catch (e) {
|
| 155 |
+
console.warn(`[${id}] Contract not found at ${mainContractPath}, falling back to metadata.main_contract`);
|
| 156 |
+
try {
|
| 157 |
+
sourceCode = await fs.readFile(path.join(targetDir, finding.main_contract), "utf-8");
|
| 158 |
+
} catch (e2) {
|
| 159 |
+
throw new Error(`Could not find any source code for ${id}`);
|
| 160 |
+
}
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
const tempVulnDir = path.join(process.cwd(), "temp_vuln_run", id);
|
| 164 |
+
console.log(`[${id}] Preparing isolated sandbox at ${tempVulnDir}...`);
|
| 165 |
+
await execAsync(`mkdir -p temp_vuln_run && rm -rf ${tempVulnDir} && cp -r ${targetDir} ${tempVulnDir}`);
|
| 166 |
+
await execAsync(`rm -f ${tempVulnDir}/.git`);
|
| 167 |
+
|
| 168 |
+
// STEP 1.5: Reference Test Resolution
|
| 169 |
+
let referenceTestCode = "";
|
| 170 |
+
if (finding.test_fix_commands) {
|
| 171 |
+
const match = finding.test_fix_commands.match(/--match-path\s+([^\s]+)/);
|
| 172 |
+
if (match) {
|
| 173 |
+
const testPath = path.join(targetDir, match[1]);
|
| 174 |
+
try {
|
| 175 |
+
referenceTestCode = await fs.readFile(testPath, "utf-8");
|
| 176 |
+
console.log(`[${id}] Found reference test at ${match[1]}`);
|
| 177 |
+
} catch {
|
| 178 |
+
console.warn(`[${id}] Could not read reference test at ${testPath}`);
|
| 179 |
+
}
|
| 180 |
+
}
|
| 181 |
+
}
|
| 182 |
+
|
| 183 |
+
const report: VulnerabilityReport = {
|
| 184 |
+
id: id,
|
| 185 |
+
title: `${finding.repo_name} - ${id}`,
|
| 186 |
+
severity: (finding.impact?.toLowerCase() || "medium") as any,
|
| 187 |
+
type: finding.expected_vulnerability || "unknown",
|
| 188 |
+
description: annotationText,
|
| 189 |
+
referenceTestCode: referenceTestCode,
|
| 190 |
+
affectedContract: {
|
| 191 |
+
name: relativeContractPath.split("/").pop()!.replace(".sol", ""),
|
| 192 |
+
sourceCode: sourceCode,
|
| 193 |
+
sourceFilePath: relativeContractPath
|
| 194 |
+
},
|
| 195 |
+
attackVector: "Vulnerability analysis from dataset annotations.",
|
| 196 |
+
customSandboxDir: tempVulnDir
|
| 197 |
+
};
|
| 198 |
+
|
| 199 |
+
console.log(`[${id}] Generating PoC and running on VULNERABLE version...`);
|
| 200 |
+
const resultVuln = await runPoCGenerator(report);
|
| 201 |
+
|
| 202 |
+
if (process.env.DEBUG_CONTEXT === "true") {
|
| 203 |
+
console.log("\n" + "=".repeat(20) + " GENERATED POC START " + "=".repeat(20));
|
| 204 |
+
console.log(resultVuln.solidityCode);
|
| 205 |
+
console.log("=".repeat(20) + " GENERATED POC END " + "=".repeat(20) + "\n");
|
| 206 |
+
}
|
| 207 |
+
|
| 208 |
+
let statusPatch = "not_tested";
|
| 209 |
+
|
| 210 |
+
if (resultVuln.status === "success") {
|
| 211 |
+
console.log(`[${id}] Running PoC on PATCHED version to verify specificity...`);
|
| 212 |
+
|
| 213 |
+
const tempPatchDir = path.join(process.cwd(), "temp_patch_run", id);
|
| 214 |
+
try {
|
| 215 |
+
await execAsync(`mkdir -p temp_patch_run && rm -rf ${tempPatchDir} && cp -r ${targetDir} ${tempPatchDir}`);
|
| 216 |
+
await execAsync(`rm -f ${tempPatchDir}/.git`);
|
| 217 |
+
|
| 218 |
+
const patchSourceDir = path.join(process.cwd(), DATASET_PATH, finding.patch);
|
| 219 |
+
await execAsync(`cp -rv ${patchSourceDir}/* ${tempPatchDir}/ || true`);
|
| 220 |
+
|
| 221 |
+
const { runFoundry } = await import("../agents/tester/tools/foundryRunner.js");
|
| 222 |
+
const patchExec = await runFoundry(resultVuln.solidityCode, tempPatchDir);
|
| 223 |
+
|
| 224 |
+
const passedOnPatch = patchExec.exitCode === 0 && patchExec.stdout.includes("ok");
|
| 225 |
+
statusPatch = passedOnPatch ? "success" : "failed";
|
| 226 |
+
|
| 227 |
+
if (statusPatch === "failed") {
|
| 228 |
+
await execAsync(`rm -rf ${tempPatchDir}`);
|
| 229 |
+
}
|
| 230 |
+
} catch (e: any) {
|
| 231 |
+
console.error(`[${id}] Patch run error:`, e.message);
|
| 232 |
+
statusPatch = "error";
|
| 233 |
+
}
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
const reproducible = resultVuln.status === "success";
|
| 237 |
+
const specific = resultVuln.status === "success" && statusPatch === "failed";
|
| 238 |
+
|
| 239 |
+
finding.benchmark_results = {
|
| 240 |
+
vuln_status: resultVuln.status,
|
| 241 |
+
patch_status: statusPatch,
|
| 242 |
+
reproducibility: reproducible,
|
| 243 |
+
specificity: specific,
|
| 244 |
+
iterations: resultVuln.iterations,
|
| 245 |
+
timestamp: new Date().toISOString(),
|
| 246 |
+
};
|
| 247 |
+
|
| 248 |
+
if (resultVuln.status === "failed") {
|
| 249 |
+
finding.benchmark_results.last_vuln_error = resultVuln.executionLogs[resultVuln.executionLogs.length - 1]?.slice(0, 500);
|
| 250 |
+
}
|
| 251 |
+
|
| 252 |
+
results.push({
|
| 253 |
+
id,
|
| 254 |
+
reproducible,
|
| 255 |
+
specific,
|
| 256 |
+
iterations: resultVuln.iterations
|
| 257 |
+
});
|
| 258 |
+
|
| 259 |
+
await fs.writeFile(METADATA_FILE, JSON.stringify(metadata, null, 2));
|
| 260 |
+
console.log(`[${id}] Result: Reproducible=${reproducible}, Specific=${specific}`);
|
| 261 |
+
|
| 262 |
+
} catch (err: any) {
|
| 263 |
+
console.error(`[${id}] Fatal Error:`, err.message);
|
| 264 |
+
finding.benchmark_results = {
|
| 265 |
+
status: "error",
|
| 266 |
+
error: err.message,
|
| 267 |
+
timestamp: new Date().toISOString()
|
| 268 |
+
};
|
| 269 |
+
await fs.writeFile(METADATA_FILE, JSON.stringify(metadata, null, 2));
|
| 270 |
+
}
|
| 271 |
+
}
|
| 272 |
+
|
| 273 |
+
const total = results.length;
|
| 274 |
+
const reproCount = results.filter(r => r.reproducible).length;
|
| 275 |
+
const specCount = results.filter(r => r.specific).length;
|
| 276 |
+
const avgIter = total > 0 ? results.reduce((acc, r) => acc + r.iterations, 0) / total : 0;
|
| 277 |
+
|
| 278 |
+
const summary = {
|
| 279 |
+
timestamp: new Date().toISOString(),
|
| 280 |
+
total_processed: total,
|
| 281 |
+
reproducibility_rate: total > 0 ? (reproCount / total) * 100 : 0,
|
| 282 |
+
specificity_rate: reproCount > 0 ? (specCount / reproCount) * 100 : 0,
|
| 283 |
+
overall_ground_truth_rate: total > 0 ? (specCount / total) * 100 : 0,
|
| 284 |
+
average_iterations: avgIter
|
| 285 |
+
};
|
| 286 |
+
|
| 287 |
+
console.log("\n" + "=".repeat(50));
|
| 288 |
+
console.log("BENCHMARK SUMMARY");
|
| 289 |
+
console.log("=".repeat(50));
|
| 290 |
+
console.log(`Total Findings: ${total}`);
|
| 291 |
+
console.log(`Reproducibility: ${summary.reproducibility_rate.toFixed(1)}% (${reproCount}/${total})`);
|
| 292 |
+
console.log(`Specificity: ${summary.specificity_rate.toFixed(1)}% (${specCount}/${reproCount})`);
|
| 293 |
+
console.log(`Overall Success: ${summary.overall_ground_truth_rate.toFixed(1)}% (Verified Ground Truth)`);
|
| 294 |
+
console.log(`Avg Iterations: ${avgIter.toFixed(2)}`);
|
| 295 |
+
console.log("=".repeat(50));
|
| 296 |
+
|
| 297 |
+
await fs.mkdir(path.dirname(SUMMARY_FILE), { recursive: true });
|
| 298 |
+
await fs.writeFile(SUMMARY_FILE, JSON.stringify({ summary, details: results }, null, 2));
|
| 299 |
+
console.log(`Summary saved to ${SUMMARY_FILE}`);
|
| 300 |
+
}
|
| 301 |
+
|
| 302 |
+
main().catch(console.error);
|