Tales-Cunha commited on
Commit
8bf5d01
·
1 Parent(s): a852b48

refactor: remove old files

Browse files
src/agents/tester/README.md CHANGED
@@ -1,109 +1,60 @@
1
- # Agente Gerador de PoCs (Tester)
2
 
3
- Este agente é responsável por validar vulnerabilidades identificadas pelo **Agente Auditor** através da geração automática de exploits em Solidity (*Proof of Concepts* - PoCs) e execução em um ambiente sandbox utilizando **Foundry**.
4
 
5
  ## 1. Visão Geral
6
 
7
- O agente implementa um **loop ReAct** (Gerar Executar Refletir) orquestrado via **LangGraph**. Diferente de abordagens tradicionais, ele utiliza um componente **Oracle** para preparar o scaffold do teste, permitindo que o LLM foque exclusivamente na lógica do exploit.
8
 
9
- ### Fluxo Multi-agente
10
- ```mermaid
11
- graph LR
12
- Coder[Agente Gerador] -- "Código Fonte" --> Auditor
13
- Auditor[Agente Auditor] -- "Findings (JSON)" --> Tester
14
- Tester[Agente de PoCs] -- "PoCResult (Verificado)" --> Final[Projeto Validado]
15
- ```
16
-
17
- ### Principais Funcionalidades:
18
- - **Sandbox Autônomo:** O agente detecta e inicializa o ambiente Foundry (`/tmp/poc-sandbox`) automaticamente no primeiro uso.
19
- - **Scaffold Automático:** Gera o arquivo `Exploit.t.sol` com o contrato vítima já instanciado e financiado.
20
- - **Loop de Auto-correção:** Se o exploit falhar, o agente analisa os logs e tenta corrigir o código por até 5 iterações.
21
- - **Integração com DeepSeek:** Utiliza o modelo `deepseek-v4-pro` via OpenRouter.
22
 
23
- ## 2. Arquitetura
24
 
25
- O fluxo de execução segue o grafo definido em `agent.ts`:
26
 
27
- 1. **Oracle Node:** Recebe o relatório de vulnerabilidade e gera o scaffold Solidity inicial.
28
- 2. **Generate PoC Node:** O LLM completa a função `test_Exploit()` com base no scaffold e na descrição da falha.
29
- 3. **Run Foundry Node:** Escreve o código no sandbox e executa `forge test`.
30
- 4. **Reflect Node:** Em caso de falha, analisa o output do Forge, classifica o erro e fornece feedback para o próximo ciclo de geração.
 
31
 
32
- ## 3. Estrutura de Arquivos
33
 
34
- ```
35
  src/agents/tester/
36
- ├── agent.ts # Definição do grafo LangGraph e lógica dos nodes
37
- ├── state.ts # Estado interno do agente (PoCStateAnnotation)
38
- ├── types.ts # Interfaces de entrada (Finding) e saída (PoCResult)
39
- ├── index.ts # Entry point público (runPoCGenerator)
40
-
41
- ├── tools/
42
- ├── scaffoldGenerator.ts # Gerador de boilerplate Foundry
43
- └── foundryRunner.ts # Executor de comandos shell (forge)
44
-
45
- ├── prompts/
46
- └── system.ts # Instruções especializadas para o LLM
47
-
48
- └── utils/
49
- ├── extractSolidity.ts # Parser de blocos de código
50
- └── logAnalyzer.ts # Classificador de erros de execução
51
  ```
52
 
53
- ## 4. Integração e Uso
54
 
55
- ### Fluxo de Dados (Input/Output)
56
-
57
- O agente recebe um objeto `VulnerabilityReport`. Como o **Agente Auditor** gera objetos do tipo `Finding`, é necessário realizar um mapeamento (veja `src/index.ts` para o adapter).
58
-
59
- #### Estrutura de Entrada (`VulnerabilityReport`)
60
- ```typescript
61
- interface VulnerabilityReport {
62
- id: string; // Identificador único do report
63
- severity: string; // "high", "medium", "low"
64
- title: string; // Título curto da falha
65
- description: string; // Descrição técnica detalhada
66
- affectedContract: {
67
- name: string; // Nome da classe do contrato
68
- sourceCode: string; // Código-fonte completo (Solidity)
69
- };
70
- attackVector: string; // Descrição do caminho de ataque
71
- exploitablePaths?: string[]; // (Opcional) Passos detalhados
72
- }
73
- ```
74
 
75
- #### Estrutura de Saída (`PoCResult`)
76
- ```typescript
77
- interface PoCResult {
78
- reportId: string;
79
- status: "success" | "failed" | "timeout";
80
- solidityCode: string; // Conteúdo final do Exploit.t.sol
81
- executionLogs: string[]; // Logs brutos de todas as iterações
82
- iterations: number; // Total de tentativas realizadas
83
- }
84
  ```
85
 
86
- ### Exemplo de Integração
87
- ```typescript
88
- import { runPoCGenerator } from "./src/agents/tester";
89
-
90
- // O orquestrador mapeia o Finding + Código Fonte para o Report
91
- const result = await runPoCGenerator(report);
92
- ```
93
-
94
- ### Pré-requisitos
95
- - **Foundry:** `forge` deve estar instalado e acessível. O agente busca em `~/.foundry/bin` e no PATH padrão.
96
- - **API Key:** `OPENROUTER_API_KEY` deve estar configurada no arquivo `.env`.
97
-
98
- ## 5. Avaliação de Resultados
99
-
100
- O `PoCResult` retorna um status que indica a validade da vulnerabilidade ou a eficácia de um patch:
101
 
102
- | Status | Significado | Ação Recomendada |
103
- | :--- | :--- | :--- |
104
- | **`success`** | Exploit executou e passou na assertion. | Vulnerabilidade confirmada. |
105
- | **`failed`** | Exploit falhou após 5 tentativas. | Verificar `executionLogs` para erro de lógica ou compilação. |
106
- | **`timeout`** | Forge excedeu 60 segundos. | Possível loop infinito no contrato ou exploit. |
107
 
108
- ## 6. Base Acadêmica
109
- A implementação deste agente foi inspirada no framework **PoCo** (Bergman et al., KTH 2025), adaptada para execução local determinística e suporte multi-agente.
 
1
+ # PoCo Agent (Proof-of-Concept Agent)
2
 
3
+ Este diretório contém a implementação principal do **Agente PoCo**, uma arquitetura autônoma baseada no framework **LangGraph**, desenvolvida para atuar como um auditor de segurança e desenvolvedor de exploits (Proof of Concepts) em Smart Contracts.
4
 
5
  ## 1. Visão Geral
6
 
7
+ O Agente recebe como entrada um relatório de vulnerabilidade (escrito por um auditor humano) e o código-fonte do contrato afetado. O objetivo do agente é explorar iterativamente o ambiente local usando o framework **Foundry** até conseguir escrever um arquivo `Exploit.t.sol` que prove matematicamente que a vulnerabilidade descrita é explorável (roubando fundos, burlando acessos, etc).
8
 
9
+ Diferente de scripts sequenciais convencionais, este agente emprega um **Loop ReAct** (Raciocínio e Ação) iterativo:
10
+ 1. **Lê e entende** o contexto.
11
+ 2. **Planeja** uma estratégia de ataque em múltiplos passos.
12
+ 3. **Escreve** o código no disco.
13
+ 4. **Compila e testa** localmente via terminal.
14
+ 5. **Analisa o erro** de compilação ou de lógica e auto-corrige o exploit na próxima iteração.
 
 
 
 
 
 
 
15
 
16
+ ## 2. Componentes da Arquitetura
17
 
18
+ O sistema é orquestrado através de uma Máquina de Estados Finita (Graph) no `graph.ts`, composta por 5 nós fundamentais:
19
 
20
+ * **`contextNode`**: de entrada. Carrega o relatório original do auditor e injeta no estado global do agente.
21
+ * **`routerNode`**: Formata as restrições do ambiente e monta o `System Prompt` que define a persona do LLM.
22
+ * **`pocoAgentNode`**: O motor cognitivo. Utiliza o modelo de linguagem avançado (ex: Claude 3.5 Sonnet) para raciocinar sobre as falhas e escolher qual ferramenta invocar.
23
+ * **`pocoToolsNode`**: O executor mecânico das ferramentas. Acessa o FileSystem (`read_file`, `write_file`) e o terminal (`smart_contract_test`, `smart_contract_compile`).
24
+ * **`trackToolCallsNode`**: Intercepta a saída do teste. Se o teste passar (`Test Passed Successfully!`), ele interrompe o grafo prematuramente definindo o status de `success`. Se falhar, devolve o feedback de erro para o `pocoAgentNode` tentar novamente, até o limite de 30 iterações.
25
 
26
+ ## 3. Estrutura de Diretórios
27
 
28
+ ```text
29
  src/agents/tester/
30
+ ├── index.ts # Entrypoint da biblioteca, orquestra e dispara o grafo LangGraph.
31
+ ├── agent.ts # Definição e wrapper do agente para integração externa.
32
+ ├── graph.ts # A topologia da rede ReAct (nodes e edges).
33
+ ├── state.ts # Interface de Estado global que trafega entre os nós do grafo.
34
+ ├── types.ts # Tipagens TypeScript (Report, Vulnerability, etc).
35
+ ├── tools/ # (Depreciado) Ferramentas antigas de suporte.
36
+ ├── utils/ # Scripts utilitários e stubs de dependências.
37
+ └── nodes/
38
+ ├── context.ts # Setup inicial e parser do contexto.
39
+ ├── router.ts # Montagem do prompt base.
40
+ └── pocoAgent.ts # Chamada direta à API do LLM com as Tools associadas.
 
 
 
 
41
  ```
42
 
43
+ ## 4. Como Executar
44
 
45
+ O agente não é chamado isoladamente pelo usuário, mas sim invocado pelo orquestrador principal de Benchmark ou pela CLI da ferramenta. Para avaliar a eficácia do agente, recomenda-se executar os scripts do Benchmark na raiz do projeto:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ ```bash
48
+ # Executa a avaliação em cima do dataset Hard (Proof-of-Patch)
49
+ DEBUG_CONTEXT=true FORCE_RERUN=true npx tsx src/benchmark/runTesterBenchmark.ts 100
 
 
 
 
 
 
50
  ```
51
 
52
+ ## 5. Ferramentas (Tools)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
53
 
54
+ A maestria do agente vem de seu arsenal de ferramentas (`tools.ts`), que operam com alta precisão cirúrgica para economizar tokens:
55
+ * `read_file`, `list_dir`: Explorar a árvore de contratos vulneráveis.
56
+ * `todo_planner`: Criar uma lista de tarefas persistente para orientar a memória de longo prazo durante as 30 iterações.
57
+ * `write_file`, `edit_file`: Gerar ou alterar partes específicas do exploit de forma isolada.
58
+ * `smart_contract_compile`, `smart_contract_test`: Interagir diretamente com a CLI do `forge` para compilar ou rodar os testes, com os logs canalizados de volta para o agente.
59
 
60
+ > **Nota Metodológica:** O agente assume que está operando em um repositório configurado e funcional. Se as dependências do repositório alvo (ex: submódulos do foundry) estiverem quebradas ou faltantes fisicamente no disco, o agente tentará alucinar "Mocks" arquiteturais para forçar o projeto a compilar, o que foge do escopo do teste da vulnerabilidade. Sempre garanta que o projeto alvo passa por um `forge build` limpo antes de submetê-lo à auditoria.
 
src/agents/tester/graph.ts CHANGED
@@ -1,8 +1,7 @@
1
  import { StateGraph, END, START } from "@langchain/langgraph";
2
  import { ToolNode } from "@langchain/langgraph/prebuilt";
3
  import { PoCStateAnnotation, PoCState } from "./state.js";
4
- import { oracleNode } from "./nodes/oracle.js";
5
- import { routerNode } from "./nodes/router.js";
6
  import { pocoAgentNode } from "./nodes/pocoAgent.js";
7
  import { pocoTools } from "./tools.js";
8
 
@@ -54,15 +53,13 @@ function routeAfterTools(state: PoCState): "pocoAgentNode" | typeof END {
54
  }
55
 
56
  const graphBuilder = new StateGraph(PoCStateAnnotation)
57
- .addNode("oracleNode", oracleNode)
58
- .addNode("routerNode", routerNode)
59
  .addNode("pocoAgentNode", pocoAgentNode)
60
  .addNode("pocoToolsNode", pocoToolsNode)
61
  .addNode("trackToolCallsNode", trackToolCallsNode)
62
 
63
- .addEdge(START, "oracleNode")
64
- .addEdge("oracleNode", "routerNode")
65
- .addEdge("routerNode", "pocoAgentNode")
66
 
67
  // ReAct Loop Routing
68
  .addConditionalEdges("pocoAgentNode", routeAfterAgent, {
 
1
  import { StateGraph, END, START } from "@langchain/langgraph";
2
  import { ToolNode } from "@langchain/langgraph/prebuilt";
3
  import { PoCStateAnnotation, PoCState } from "./state.js";
4
+ import { contextNode } from "./nodes/context.js";
 
5
  import { pocoAgentNode } from "./nodes/pocoAgent.js";
6
  import { pocoTools } from "./tools.js";
7
 
 
53
  }
54
 
55
  const graphBuilder = new StateGraph(PoCStateAnnotation)
56
+ .addNode("contextNode", contextNode)
 
57
  .addNode("pocoAgentNode", pocoAgentNode)
58
  .addNode("pocoToolsNode", pocoToolsNode)
59
  .addNode("trackToolCallsNode", trackToolCallsNode)
60
 
61
+ .addEdge(START, "contextNode")
62
+ .addEdge("contextNode", "pocoAgentNode")
 
63
 
64
  // ReAct Loop Routing
65
  .addConditionalEdges("pocoAgentNode", routeAfterAgent, {
src/agents/tester/nodes/context.ts ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import fs from "fs/promises";
2
+ import path from "path";
3
+ import { PoCState } from "../state.js";
4
+
5
+ export async function contextNode(state: PoCState): Promise<Partial<PoCState>> {
6
+ console.log("[contextNode] Preparando ambiente de testes para:", state.report.title);
7
+
8
+ if (state.report.customSandboxDir) {
9
+ try {
10
+ const testDir = path.join(state.report.customSandboxDir, "test");
11
+ const testEntries = await fs.readdir(testDir, { withFileTypes: true }).catch(() => []);
12
+ let removed = 0;
13
+ for (const entry of testEntries) {
14
+ if (entry.isFile() && entry.name.endsWith(".t.sol") && entry.name !== "Exploit.t.sol") {
15
+ await fs.unlink(path.join(testDir, entry.name));
16
+ removed++;
17
+ }
18
+ }
19
+ if (removed > 0) {
20
+ console.log(`[contextNode] Limpos ${removed} arquivos de teste antigos.`);
21
+ }
22
+ } catch (e) {
23
+ console.warn("[contextNode] falha na limpeza do diretório de testes:", (e as Error).message);
24
+ }
25
+ }
26
+
27
+ return {
28
+ templateCode: "",
29
+ pocCode: "",
30
+ infrastructurePhase: false
31
+ };
32
+ }
src/agents/tester/nodes/oracle.ts DELETED
@@ -1,72 +0,0 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
- import { PoCState } from "../state.js";
4
- import { generateLocalScaffold } from "../tools/scaffoldGenerator.js";
5
- import { extractConstructor } from "../utils/parserUtils.js";
6
- import { analyzeSolidityFile } from "../../auditor/tools/solidity-analyzer-tool.js";
7
- import { extractProjectContext } from "../utils/projectContextExtractor.js";
8
- import { OracleContext } from "../types.js";
9
-
10
- export async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
11
- console.log("[oracleNode] gerando scaffold para:", state.report.title);
12
-
13
- const solidityScaffold = generateLocalScaffold(state.report);
14
-
15
- const constructorInfo = extractConstructor(state.report.affectedContract.sourceCode, state.report.affectedContract.name);
16
-
17
- console.log("[oracleNode] analisando API do contrato e helpers de teste...");
18
- const targetContractAPI = await analyzeSolidityFile(state.report.affectedContract.sourceCode, "short");
19
-
20
- let referenceTestHelpers = "";
21
- if (state.report.referenceTestCode) {
22
- referenceTestHelpers = await analyzeSolidityFile(state.report.referenceTestCode, "short");
23
- }
24
-
25
- let projectRemappings = "";
26
- let projectTestImports = "";
27
- let projectTestFilePath: string | null = null;
28
- if (state.report.customSandboxDir) {
29
- console.log("[oracleNode] extracting project context (remappings, test imports)...");
30
- try {
31
- const projectCtx = await extractProjectContext(state.report.customSandboxDir);
32
- projectRemappings = projectCtx.remappings;
33
- projectTestImports = projectCtx.existingTestImports;
34
- projectTestFilePath = projectCtx.existingTestFilePath;
35
- } catch (e) {
36
- console.warn("[oracleNode] could not extract project context:", (e as Error).message);
37
- }
38
-
39
- try {
40
- const testDir = path.join(state.report.customSandboxDir, "test");
41
- const testEntries = await fs.readdir(testDir, { withFileTypes: true }).catch(() => []);
42
- let removed = 0;
43
- for (const entry of testEntries) {
44
- if (entry.isFile() && entry.name.endsWith(".t.sol") && entry.name !== "Exploit.t.sol") {
45
- await fs.unlink(path.join(testDir, entry.name));
46
- removed++;
47
- }
48
- }
49
- } catch (e) {
50
- console.warn("[oracleNode] test cleanup failed:", (e as Error).message);
51
- }
52
- }
53
-
54
- const oracleContext: OracleContext = {
55
- solidityScaffold,
56
- constructorInfo: constructorInfo?.parameters,
57
- targetContractAPI,
58
- referenceTestHelpers,
59
- projectRemappings,
60
- projectTestImports,
61
- projectTestFilePath,
62
- };
63
-
64
- // No longer generating static template. We leave it to the agent to build the setup.
65
- console.log("[oracleNode] scaffold generation skipped. Context built.");
66
- return {
67
- oracleContext,
68
- templateCode: "",
69
- pocCode: "",
70
- infrastructurePhase: false
71
- };
72
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agents/tester/nodes/router.ts DELETED
@@ -1,32 +0,0 @@
1
- import { PoCState } from "../state.js";
2
-
3
- export const CATEGORY_REENTRANCY = "REENTRANCY";
4
- export const CATEGORY_ACCESS_CONTROL = "ACCESS_CONTROL";
5
- export const CATEGORY_ARITHMETIC = "ARITHMETIC";
6
- export const CATEGORY_LOGIC = "LOGIC";
7
- export const CATEGORY_DEFAULT = "DEFAULT";
8
-
9
- export async function routerNode(state: PoCState): Promise<Partial<PoCState>> {
10
- console.log("[routerNode] Classifying vulnerability deterministically...");
11
-
12
- const type = (state.report.type || "").toLowerCase();
13
- const desc = (state.report.description || "").toLowerCase();
14
-
15
- const combined = `${type} ${desc}`;
16
-
17
- let category = CATEGORY_DEFAULT;
18
-
19
- if (combined.includes("reentrancy") || combined.includes("re-entrancy") || combined.includes("fallback")) {
20
- category = CATEGORY_REENTRANCY;
21
- } else if (combined.includes("access control") || combined.includes("unauthorized") || combined.includes("onlyowner") || combined.includes("permission")) {
22
- category = CATEGORY_ACCESS_CONTROL;
23
- } else if (combined.includes("overflow") || combined.includes("underflow") || combined.includes("arithmetic") || combined.includes("math")) {
24
- category = CATEGORY_ARITHMETIC;
25
- } else if (combined.includes("logic") || combined.includes("validation") || combined.includes("bypass")) {
26
- category = CATEGORY_LOGIC;
27
- }
28
-
29
- console.log(`[routerNode] Classified as: ${category}`);
30
-
31
- return { vulnerabilityCategory: category };
32
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agents/tester/state.ts CHANGED
@@ -1,15 +1,10 @@
1
  import { Annotation } from "@langchain/langgraph";
2
  import { BaseMessage } from "@langchain/core/messages";
3
- import { VulnerabilityReport, OracleContext } from "./types.js";
4
 
5
  export const PoCStateAnnotation = Annotation.Root({
6
  report: Annotation<VulnerabilityReport>(),
7
 
8
- oracleContext: Annotation<OracleContext | null>({
9
- default: () => null,
10
- reducer: (_, y) => y, // overwrite — filled once by oracleNode
11
- }),
12
-
13
  pocCode: Annotation<string>({
14
  default: () => "",
15
  reducer: (_, y) => y, // overwrite — full combined file
@@ -25,11 +20,6 @@ export const PoCStateAnnotation = Annotation.Root({
25
  reducer: (_, y) => y, // overwrite — only the hack logic
26
  }),
27
 
28
- vulnerabilityCategory: Annotation<string>({
29
- default: () => "",
30
- reducer: (_, y) => y, // overwrite
31
- }),
32
-
33
  infrastructurePhase: Annotation<boolean>({
34
  default: () => true,
35
  reducer: (_, y) => y, // overwrite — true while fixing imports
 
1
  import { Annotation } from "@langchain/langgraph";
2
  import { BaseMessage } from "@langchain/core/messages";
3
+ import { VulnerabilityReport } from "./types.js";
4
 
5
  export const PoCStateAnnotation = Annotation.Root({
6
  report: Annotation<VulnerabilityReport>(),
7
 
 
 
 
 
 
8
  pocCode: Annotation<string>({
9
  default: () => "",
10
  reducer: (_, y) => y, // overwrite — full combined file
 
20
  reducer: (_, y) => y, // overwrite — only the hack logic
21
  }),
22
 
 
 
 
 
 
23
  infrastructurePhase: Annotation<boolean>({
24
  default: () => true,
25
  reducer: (_, y) => y, // overwrite — true while fixing imports
src/agents/tester/tools/scaffoldGenerator.ts DELETED
@@ -1,50 +0,0 @@
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
- // 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}
38
- // Tipo: ${report.type}
39
- // Vetor: ${report.attackVector}
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
47
- vm.stopPrank();
48
- }
49
- }`.trim();
50
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agents/tester/types.ts CHANGED
@@ -34,15 +34,7 @@ export interface VulnerabilityReport {
34
  patchDiff?: string; // Unified diff of the patch (vulnerable vs patched) for specificity guidance
35
  }
36
 
37
- export interface OracleContext {
38
- solidityScaffold: string; // Exploit.t.sol parcial com setUp() pronto
39
- constructorInfo?: string; // Assinatura do constructor para ajudar no deploy
40
- targetContractAPI?: string; // Resumo dos métodos e variáveis do contrato alvo
41
- referenceTestHelpers?: string; // Resumo das funções auxiliares disponíveis no ambiente de teste
42
- projectRemappings?: string; // Content of remappings.txt for correct import paths
43
- projectTestImports?: string; // Import lines from an existing test file in the project
44
- projectTestFilePath?: string | null; // Path to the reference test file used
45
- }
46
 
47
  export interface PoCResult {
48
  reportId: string;
 
34
  patchDiff?: string; // Unified diff of the patch (vulnerable vs patched) for specificity guidance
35
  }
36
 
37
+
 
 
 
 
 
 
 
 
38
 
39
  export interface PoCResult {
40
  reportId: string;
src/agents/tester/utils/dependencyStubber.ts DELETED
@@ -1,85 +0,0 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
- import { exec } from "child_process";
4
- import { promisify } from "util";
5
-
6
- const execAsync = promisify(exec);
7
-
8
- /**
9
- * Runs a minimal forge build probe to detect missing source files,
10
- * then creates minimal stub contracts at those exact paths.
11
- * This unblocks projects that use deep submodule dependencies (e.g. lib/caviar/lib/oracle/...)
12
- * or node_modules imports that are not present in the sandbox.
13
- */
14
- export async function createMissingDependencyStubs(sandboxDir: string): Promise<void> {
15
- // Run forge build on just the project (no test files), capture errors
16
- let combined = "";
17
- try {
18
- const { stdout, stderr } = await execAsync(
19
- `cd "${sandboxDir}" && forge build --no-cache 2>&1 || true`,
20
- {
21
- timeout: 60_000,
22
- env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` }
23
- }
24
- );
25
- combined = stdout + stderr;
26
- } catch (e: any) {
27
- combined = e.stdout || e.stderr || e.message || "";
28
- }
29
-
30
- // Extract all "Source X not found" paths
31
- const missingPaths: string[] = [];
32
- const sourceNotFoundRegex = /Source "([^"]+)" not found/g;
33
- let match: RegExpExecArray | null;
34
- while ((match = sourceNotFoundRegex.exec(combined)) !== null) {
35
- const missing = match[1];
36
- if (!missingPaths.includes(missing)) {
37
- missingPaths.push(missing);
38
- }
39
- }
40
-
41
- if (missingPaths.length === 0) return;
42
-
43
- console.log(`[oracleNode] Detected ${missingPaths.length} missing dependencies, creating stubs...`);
44
-
45
- // Detect Solidity version used in the project (for the stub pragma)
46
- let pragmaVersion = "^0.8.0";
47
- try {
48
- const toml = await fs.readFile(path.join(sandboxDir, "foundry.toml"), "utf-8");
49
- const vMatch = toml.match(/solc[_-]?version\s*=\s*"([^"]+)"/);
50
- if (vMatch) pragmaVersion = vMatch[1];
51
- } catch { /* use default */ }
52
-
53
- for (const missing of missingPaths) {
54
- // Build the stub path inside the sandbox
55
- const stubPath = path.join(sandboxDir, missing);
56
-
57
- // Skip if file already exists
58
- try {
59
- await fs.access(stubPath);
60
- continue; // already exists
61
- } catch { /* doesn't exist, create it */ }
62
-
63
- // Skip forge-std — it should always be available
64
- if (missing.startsWith("forge-std/") || missing.startsWith("lib/forge-std/")) continue;
65
-
66
- try {
67
- await fs.mkdir(path.dirname(stubPath), { recursive: true });
68
-
69
- // Generate a minimal stub that satisfies the import
70
- const contractName = path.basename(missing, ".sol");
71
- const stubContent = `// SPDX-License-Identifier: MIT
72
- // AUTO-GENERATED STUB — replaces missing dependency: ${missing}
73
- pragma solidity ${pragmaVersion};
74
-
75
- // Minimal stub to satisfy missing import
76
- contract ${contractName} {}
77
- interface I${contractName} {}
78
- `;
79
- await fs.writeFile(stubPath, stubContent);
80
- console.log(` [stub] Created: ${missing}`);
81
- } catch (e) {
82
- console.warn(` [stub] Failed to create ${missing}:`, (e as Error).message);
83
- }
84
- }
85
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/agents/tester/utils/projectContextExtractor.ts DELETED
@@ -1,156 +0,0 @@
1
- import fs from "fs/promises";
2
- import path from "path";
3
-
4
- export interface ProjectContext {
5
- remappings: string; // Content of remappings.txt or foundry.toml [profile.default.remappings]
6
- existingTestImports: string; // First few import lines from an existing test file
7
- foundryTomlProfile: string; // Relevant foundry.toml settings (src, libs)
8
- existingTestFilePath: string | null; // Relative path to an existing test file for reference
9
- }
10
-
11
- /**
12
- * Extracts project-level context needed for correct import paths in PoC tests.
13
- * Reads remappings.txt, foundry.toml, and the first existing test file in the project.
14
- */
15
- export async function extractProjectContext(sandboxDir: string): Promise<ProjectContext> {
16
- let remappings = "";
17
- let foundryTomlProfile = "";
18
- let existingTestImports = "";
19
- let existingTestFilePath: string | null = null;
20
-
21
- // 1. Read remappings.txt
22
- try {
23
- const remappingsPath = path.join(sandboxDir, "remappings.txt");
24
- remappings = await fs.readFile(remappingsPath, "utf-8");
25
- } catch {
26
- // fallback: try to extract from foundry.toml
27
- }
28
-
29
- // 2. Read foundry.toml for additional context
30
- try {
31
- const foundryTomlPath = path.join(sandboxDir, "foundry.toml");
32
- const tomlContent = await fs.readFile(foundryTomlPath, "utf-8");
33
- // Extract relevant lines (src, libs, remappings)
34
- const relevantLines = tomlContent
35
- .split("\n")
36
- .filter(l =>
37
- l.includes("src") ||
38
- l.includes("libs") ||
39
- l.includes("remapping") ||
40
- l.includes("[profile")
41
- )
42
- .slice(0, 20)
43
- .join("\n");
44
- foundryTomlProfile = relevantLines;
45
-
46
- // If no remappings.txt, try to extract from foundry.toml remappings array
47
- if (!remappings) {
48
- const remappingMatch = tomlContent.match(/remappings\s*=\s*\[([\s\S]*?)\]/);
49
- if (remappingMatch) {
50
- remappings = remappingMatch[1]
51
- .split(",")
52
- .map(s => s.trim().replace(/^["']|["']$/g, ""))
53
- .filter(Boolean)
54
- .join("\n");
55
- }
56
- }
57
- } catch {
58
- // ignore
59
- }
60
-
61
- // 3. Find an existing test file to use as import reference
62
- try {
63
- const testDir = path.join(sandboxDir, "test");
64
- const testFile = await findFirstTestFile(testDir);
65
- if (testFile) {
66
- existingTestFilePath = path.relative(sandboxDir, testFile);
67
- const testContent = await fs.readFile(testFile, "utf-8");
68
- // Extract the first 20 lines which typically contain imports
69
- existingTestImports = testContent
70
- .split("\n")
71
- .slice(0, 25)
72
- .filter(l => l.startsWith("import") || l.startsWith("pragma") || l.startsWith("//") || l.startsWith("contract") || l.startsWith("abstract"))
73
- .join("\n");
74
- }
75
- } catch {
76
- // ignore
77
- }
78
-
79
- return { remappings, existingTestImports, foundryTomlProfile, existingTestFilePath };
80
- }
81
-
82
- /**
83
- * Recursively finds the best reference test file in the test directory.
84
- * Strategy:
85
- * 1. Collect ALL .t.sol files (excluding Exploit.t.sol) across all subdirs (BFS)
86
- * 2. Pick the one with the most import lines (most context-rich)
87
- * 3. Fallback to .sol files that have "import" statements (e.g. BaseTest.sol, Fixture.sol)
88
- * 4. Skip pure mock contracts (files in "mock" directories or named *Mock.sol)
89
- */
90
- async function findFirstTestFile(dir: string): Promise<string | null> {
91
- const tSolFiles: string[] = [];
92
- const solFiles: string[] = [];
93
-
94
- // BFS collect all files
95
- const queue = [dir];
96
- let depth = 0;
97
- while (queue.length > 0 && depth < 4) {
98
- const currentDepth: string[] = [...queue];
99
- queue.length = 0;
100
- depth++;
101
- for (const currentDir of currentDepth) {
102
- let entries: import('fs').Dirent[];
103
- try {
104
- entries = await fs.readdir(currentDir, { withFileTypes: true }) as import('fs').Dirent[];
105
- } catch {
106
- continue;
107
- }
108
- for (const entry of entries) {
109
- const fullPath = path.join(currentDir, entry.name);
110
- const name = entry.name as string;
111
- if (entry.isFile()) {
112
- if (name === "Exploit.t.sol") continue; // skip our own file
113
- if (name.endsWith(".t.sol")) {
114
- tSolFiles.push(fullPath);
115
- } else if (name.endsWith(".sol")) {
116
- // Skip mock contracts
117
- const isMock = name.toLowerCase().includes("mock") || currentDir.toLowerCase().includes("mock");
118
- if (!isMock) {
119
- solFiles.push(fullPath);
120
- }
121
- }
122
- } else if (entry.isDirectory()) {
123
- queue.push(fullPath);
124
- }
125
- }
126
- }
127
- }
128
-
129
- // Pick the .t.sol file with the most import lines (richest context)
130
- if (tSolFiles.length > 0) {
131
- let bestFile = tSolFiles[0];
132
- let bestImportCount = 0;
133
- for (const f of tSolFiles.slice(0, 10)) { // check up to 10
134
- try {
135
- const content = await fs.readFile(f, "utf-8");
136
- const importCount = (content.match(/^import/gm) ?? []).length;
137
- if (importCount > bestImportCount) {
138
- bestImportCount = importCount;
139
- bestFile = f;
140
- }
141
- } catch { /* skip */ }
142
- }
143
- return bestFile;
144
- }
145
-
146
- // Fallback: .sol files that have import statements (like BaseTest.sol, Fixture.sol)
147
- for (const f of solFiles.slice(0, 10)) {
148
- try {
149
- const content = await fs.readFile(f, "utf-8");
150
- if (content.includes("import ")) return f;
151
- } catch { /* skip */ }
152
- }
153
-
154
- return null;
155
- }
156
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
src/benchmark/runTesterBenchmark.ts CHANGED
@@ -206,8 +206,8 @@ async function main() {
206
  continue;
207
  }
208
 
209
- const allowedIds = ["008", "020", "041", "054", "070", "077"];
210
- if (!allowedIds.includes(id)) {
211
  continue;
212
  }
213
 
 
206
  continue;
207
  }
208
 
209
+ const targetIds = ["020"];
210
+ if (!targetIds.includes(id)) {
211
  continue;
212
  }
213