Spaces:
Runtime error
Runtime error
Tales-Cunha commited on
Commit ·
6389903
1
Parent(s): 69b971b
feat: Implementar `scaffoldGenerator` and Implementar `oracleNode`
Browse files- src/agents/tester/agent.ts +9 -2
- src/agents/tester/tools/scaffoldGenerator.ts +42 -0
- src/agents/tester/types.ts +18 -0
- src/index.ts +39 -25
- tests/scaffold.test.ts +35 -0
src/agents/tester/agent.ts
CHANGED
|
@@ -1,9 +1,16 @@
|
|
| 1 |
import { StateGraph, END, START } from "@langchain/langgraph";
|
| 2 |
import { PoCStateAnnotation, PoCState } from "./state.js";
|
|
|
|
|
|
|
| 3 |
|
| 4 |
async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
|
| 5 |
-
console.log("[oracleNode]
|
| 6 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
}
|
| 8 |
|
| 9 |
async function generatePoCNode(state: PoCState): Promise<Partial<PoCState>> {
|
|
|
|
| 1 |
import { StateGraph, END, START } from "@langchain/langgraph";
|
| 2 |
import { PoCStateAnnotation, PoCState } from "./state.js";
|
| 3 |
+
import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
|
| 4 |
+
import { OracleContext } from "./types.js";
|
| 5 |
|
| 6 |
async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
|
| 7 |
+
console.log("[oracleNode] gerando scaffold para:", state.report.title);
|
| 8 |
+
|
| 9 |
+
const solidityScaffold = generateLocalScaffold(state.report);
|
| 10 |
+
const oracleContext: OracleContext = { solidityScaffold };
|
| 11 |
+
|
| 12 |
+
console.log("[oracleNode] scaffold gerado, tamanho:", solidityScaffold.length, "chars");
|
| 13 |
+
return { oracleContext };
|
| 14 |
}
|
| 15 |
|
| 16 |
async function generatePoCNode(state: PoCState): Promise<Partial<PoCState>> {
|
src/agents/tester/tools/scaffoldGenerator.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { VulnerabilityReport } from "../types.js";
|
| 2 |
+
|
| 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 solidity ^0.8.20;
|
| 8 |
+
|
| 9 |
+
import "forge-std/Test.sol";
|
| 10 |
+
import "forge-std/console.sol";
|
| 11 |
+
|
| 12 |
+
// ── Código-fonte do contrato vulnerável ──────────────────────────────────────
|
| 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() gerado automaticamente pelo Oracle — NÃO MODIFICAR
|
| 21 |
+
function setUp() public {
|
| 22 |
+
target = new ${report.affectedContract.name}();
|
| 23 |
+
vm.deal(address(target), 100 ether);
|
| 24 |
+
vm.deal(ATTACKER, 10 ether);
|
| 25 |
+
vm.label(address(target), "TARGET");
|
| 26 |
+
vm.label(ATTACKER, "ATTACKER");
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
// Vulnerabilidade: ${report.title}
|
| 30 |
+
// Tipo: ${report.type}
|
| 31 |
+
// Vetor: ${report.attackVector}
|
| 32 |
+
${report.exploitablePaths ? `// Caminhos de Exploração:\n // - ${report.exploitablePaths.join("\n // - ")}` : ""}
|
| 33 |
+
// Cheatcodes sugeridos: ${cheatcodes}
|
| 34 |
+
//
|
| 35 |
+
// COMPLETE APENAS ESTA FUNÇÃO — não altere setUp() nem os campos acima
|
| 36 |
+
function test_Exploit() public {
|
| 37 |
+
vm.startPrank(ATTACKER);
|
| 38 |
+
// TODO: implementar exploit aqui
|
| 39 |
+
vm.stopPrank();
|
| 40 |
+
}
|
| 41 |
+
}`.trim();
|
| 42 |
+
}
|
src/agents/tester/types.ts
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
export interface VulnerabilityReport {
|
| 2 |
id: string;
|
| 3 |
severity: "critical" | "high" | "medium" | "low";
|
|
@@ -10,6 +25,9 @@ export interface VulnerabilityReport {
|
|
| 10 |
};
|
| 11 |
attackVector: string;
|
| 12 |
suggestedCheatcodes?: string[];
|
|
|
|
|
|
|
|
|
|
| 13 |
}
|
| 14 |
|
| 15 |
export interface OracleContext {
|
|
|
|
| 1 |
+
export interface Finding {
|
| 2 |
+
title: string;
|
| 3 |
+
description: string;
|
| 4 |
+
recommendation: string;
|
| 5 |
+
severity: "high" | "medium" | "low";
|
| 6 |
+
codeSnippet: string;
|
| 7 |
+
location: string;
|
| 8 |
+
path: string;
|
| 9 |
+
judgeReview: {
|
| 10 |
+
review: string;
|
| 11 |
+
confidence: number;
|
| 12 |
+
exploitablePaths: string[];
|
| 13 |
+
};
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
export interface VulnerabilityReport {
|
| 17 |
id: string;
|
| 18 |
severity: "critical" | "high" | "medium" | "low";
|
|
|
|
| 25 |
};
|
| 26 |
attackVector: string;
|
| 27 |
suggestedCheatcodes?: string[];
|
| 28 |
+
codeSnippet?: string;
|
| 29 |
+
location?: string;
|
| 30 |
+
exploitablePaths?: string[];
|
| 31 |
}
|
| 32 |
|
| 33 |
export interface OracleContext {
|
src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
| 1 |
import "dotenv/config";
|
| 2 |
|
| 3 |
-
import { auditorAgent } from "./agents/auditor/agent.
|
| 4 |
-
import { coderAgent } from "./agents/coder/agent.
|
| 5 |
-
import { testerAgent } from "./agents/tester/agent.
|
| 6 |
-
import type { VulnerabilityReport } from "./agents/tester/types.
|
| 7 |
|
| 8 |
const requirements = ["ERC20 token", "pausable", "ownable"];
|
| 9 |
|
|
@@ -15,24 +15,38 @@ const auditorResult = await auditorAgent.invoke({ solidityFile: coderResult.cont
|
|
| 15 |
console.log("\n======= Auditor =======");
|
| 16 |
// console.log(auditorResult.vulnerabilities);
|
| 17 |
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
import "dotenv/config";
|
| 2 |
|
| 3 |
+
import { auditorAgent } from "./agents/auditor/agent.js";
|
| 4 |
+
import { coderAgent } from "./agents/coder/agent.js";
|
| 5 |
+
import { testerAgent } from "./agents/tester/agent.js";
|
| 6 |
+
import type { VulnerabilityReport, Finding } from "./agents/tester/types.js";
|
| 7 |
|
| 8 |
const requirements = ["ERC20 token", "pausable", "ownable"];
|
| 9 |
|
|
|
|
| 15 |
console.log("\n======= Auditor =======");
|
| 16 |
// console.log(auditorResult.vulnerabilities);
|
| 17 |
|
| 18 |
+
function mapFindingToReport(finding: Finding, sourceCode: string): VulnerabilityReport {
|
| 19 |
+
// Extract contract name from path (e.g., "contracts/CafeToken.sol" -> "CafeToken")
|
| 20 |
+
const nameMatch = finding.path.match(/([^\/]+)\.sol$/);
|
| 21 |
+
const contractName = nameMatch ? nameMatch[1] : "TargetContract";
|
| 22 |
+
|
| 23 |
+
return {
|
| 24 |
+
id: finding.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50),
|
| 25 |
+
severity: finding.severity === "high" ? "high" : finding.severity === "medium" ? "medium" : "low",
|
| 26 |
+
type: "custom",
|
| 27 |
+
title: finding.title,
|
| 28 |
+
description: finding.description,
|
| 29 |
+
affectedContract: {
|
| 30 |
+
name: contractName,
|
| 31 |
+
sourceCode: sourceCode,
|
| 32 |
+
},
|
| 33 |
+
attackVector: finding.judgeReview.exploitablePaths[0] || "Unknown vector",
|
| 34 |
+
exploitablePaths: finding.judgeReview.exploitablePaths,
|
| 35 |
+
codeSnippet: finding.codeSnippet,
|
| 36 |
+
location: finding.location
|
| 37 |
+
};
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
if (auditorResult.vulnerabilities.length > 0) {
|
| 41 |
+
const finding = auditorResult.vulnerabilities[0] as Finding;
|
| 42 |
+
const report = mapFindingToReport(finding, coderResult.contract);
|
| 43 |
+
|
| 44 |
+
const testerResult = await testerAgent.invoke({ report });
|
| 45 |
+
|
| 46 |
+
console.log("\n======= Tester =======");
|
| 47 |
+
console.log("Status:", testerResult.status);
|
| 48 |
+
console.log("Iterations:", testerResult.iterations);
|
| 49 |
+
} else {
|
| 50 |
+
console.log("\n======= Tester =======");
|
| 51 |
+
console.log("Nenhuma vulnerabilidade encontrada pelo Auditor.");
|
| 52 |
+
}
|
tests/scaffold.test.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { generateLocalScaffold } from "../src/agents/tester/tools/scaffoldGenerator.js";
|
| 2 |
+
|
| 3 |
+
const mockReport = {
|
| 4 |
+
id: "t1",
|
| 5 |
+
severity: "high" as const,
|
| 6 |
+
type: "reentrancy",
|
| 7 |
+
title: "Reentrancy in withdraw()",
|
| 8 |
+
description: "withdraw() sends ETH before zeroing balance",
|
| 9 |
+
attackVector: "Malicious callback",
|
| 10 |
+
affectedContract: {
|
| 11 |
+
name: "VulnerableBank",
|
| 12 |
+
sourceCode: `
|
| 13 |
+
pragma solidity ^0.8.20;
|
| 14 |
+
contract VulnerableBank {
|
| 15 |
+
mapping(address=>uint) public balances;
|
| 16 |
+
function withdraw() external {
|
| 17 |
+
uint a = balances[msg.sender];
|
| 18 |
+
(bool ok,) = msg.sender.call{value:a}("");
|
| 19 |
+
require(ok); balances[msg.sender] = 0;
|
| 20 |
+
}
|
| 21 |
+
}`
|
| 22 |
+
}
|
| 23 |
+
};
|
| 24 |
+
|
| 25 |
+
const scaffold = generateLocalScaffold(mockReport);
|
| 26 |
+
console.log("--- Scaffold Output ---");
|
| 27 |
+
console.log(scaffold);
|
| 28 |
+
console.log("--- End Scaffold ---");
|
| 29 |
+
|
| 30 |
+
console.assert(scaffold.includes("contract ExploitTest is Test"), "Scaffold missing ExploitTest");
|
| 31 |
+
console.assert(scaffold.includes("VulnerableBank target"), "Scaffold missing target declaration");
|
| 32 |
+
console.assert(scaffold.includes("function setUp()"), "Scaffold missing setUp");
|
| 33 |
+
console.assert(scaffold.includes("function test_Exploit()"), "Scaffold missing test_Exploit");
|
| 34 |
+
|
| 35 |
+
console.log("Scaffold generator test passed");
|