Spaces:
Runtime error
Runtime error
Tales-Cunha commited on
Commit ·
4e2e30e
1
Parent(s): c01b9cd
docs: create tester architecture definition
Browse files- Created Definicao_Agente_Tester.md mapping the 4 phases of the Tester
(Oracle, Analyze, Generate, Run) mimicking the Auditor's PDF structure.
- Also includes the previous uncommitted production gap fixes (mapFinding context pass, customSandboxDir, reduced max iterations).
- AGENT_PROGRESS.md +125 -0
- Definicao_Agente_Tester.md +96 -0
- src/agents/tester/agent.ts +27 -4
- src/server.ts +7 -1
- src/utils/mapFinding.ts +58 -14
AGENT_PROGRESS.md
CHANGED
|
@@ -236,3 +236,128 @@ Docker image needs rebuild after code changes.
|
|
| 236 |
| Overall Ground Truth | 0.0% | ~20% |
|
| 237 |
| Avg Iterations | 7.55 | <7.0 |
|
| 238 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 236 |
| Overall Ground Truth | 0.0% | ~20% |
|
| 237 |
| Avg Iterations | 7.55 | <7.0 |
|
| 238 |
|
| 239 |
+
|
| 240 |
+
---
|
| 241 |
+
|
| 242 |
+
## Production vs Benchmark Gap Analysis
|
| 243 |
+
|
| 244 |
+
### What the production pipeline actually sends to the tester
|
| 245 |
+
|
| 246 |
+
```
|
| 247 |
+
User request
|
| 248 |
+
│
|
| 249 |
+
▼ Coder Agent
|
| 250 |
+
coderResult.contract ← a single Solidity string (the generated contract)
|
| 251 |
+
│
|
| 252 |
+
▼ Auditor Agent (receives repoPath with Contract.sol + README.md)
|
| 253 |
+
auditorResult.findings[0] ← ONE finding
|
| 254 |
+
│
|
| 255 |
+
▼ mapFindingToReport() ← the ONLY bridge between auditor and tester
|
| 256 |
+
```
|
| 257 |
+
|
| 258 |
+
**`mapFindingToReport` currently passes to the tester:**
|
| 259 |
+
| Field | Source | Used by tester? |
|
| 260 |
+
|---|---|---|
|
| 261 |
+
| `id` | derived from title | ✅ identifier only |
|
| 262 |
+
| `severity` | `finding.severity` | label only |
|
| 263 |
+
| `type` | `finding.type` | ✅ in analysis prompt |
|
| 264 |
+
| `title` | `finding.title` | ✅ in analysis prompt |
|
| 265 |
+
| `description` | `finding.description` | ✅ in analysis prompt |
|
| 266 |
+
| `affectedContract.sourceCode` | `coderResult.contract` | ✅ shown to LLM |
|
| 267 |
+
| `affectedContract.name` | extracted from path | ✅ |
|
| 268 |
+
| `attackVector` | `exploitablePaths[0]` | ✅ in analysis prompt |
|
| 269 |
+
| `exploitablePaths` | `judgeReview.exploitablePaths` | ✅ passed |
|
| 270 |
+
| `codeSnippet` | `finding.codeSnippet` | ✅ passed |
|
| 271 |
+
| `location` | `finding.location` | ✅ passed |
|
| 272 |
+
|
| 273 |
+
**What the auditor produces but the tester NEVER receives:**
|
| 274 |
+
| Auditor field | Value | Why it would help the tester |
|
| 275 |
+
|---|---|---|
|
| 276 |
+
| `finding.recommendation` | Human-readable fix suggestion | Tells tester what the PATCH would look like → key for specific assertions |
|
| 277 |
+
| `finding.judgeReview.review` | Judge's analysis of exploitability | More precise attack reasoning than just description |
|
| 278 |
+
| `finding.judgeReview.confidence` | 0-100 confidence score | Tester could skip low-confidence findings |
|
| 279 |
+
| `auditorResult.repoContext` | Full structured protocol context | Gives tester knowledge of cross-contract interactions |
|
| 280 |
+
| `auditorResult.fileTree` | Directory tree of the repo | Helps tester find the right imports |
|
| 281 |
+
| All other `.sol` files | Source of ALL contracts | Tester only gets ONE contract; misses dependencies |
|
| 282 |
+
|
| 283 |
+
**Missing in production but present in benchmark:**
|
| 284 |
+
| Field | Benchmark | Production |
|
| 285 |
+
|---|---|---|
|
| 286 |
+
| `customSandboxDir` | ✅ real project folder | ❌ NOT SET (uses generic /tmp/poc-sandbox) |
|
| 287 |
+
| `referenceTestCode` | ✅ real test files | ❌ NOT SET |
|
| 288 |
+
| `patchDiff` | ✅ computed from dataset | ❌ N/A (no patch in production) |
|
| 289 |
+
|
| 290 |
+
**The critical gap:** In production, `customSandboxDir` is null/undefined, so:
|
| 291 |
+
- BFS test file selection → SKIPPED
|
| 292 |
+
- projectContextExtractor → SKIPPED
|
| 293 |
+
- dependencyStubber → SKIPPED
|
| 294 |
+
- Test file cleanup → SKIPPED
|
| 295 |
+
- The tester runs in the generic `/tmp/poc-sandbox` with NO project context
|
| 296 |
+
|
| 297 |
+
All the improvements that boosted benchmark from 27% → 54% are benchmark-only.
|
| 298 |
+
|
| 299 |
+
---
|
| 300 |
+
|
| 301 |
+
## Model vs Agent Quality Plateau
|
| 302 |
+
|
| 303 |
+
**How to tell them apart:**
|
| 304 |
+
|
| 305 |
+
| Symptom | Model limit | Agent limit |
|
| 306 |
+
|---|---|---|
|
| 307 |
+
| Correct assertion but setup wrong | | ✅ Agent can fix |
|
| 308 |
+
| Wrong assertion (generic, too broad) | ✅ Model limit | Could improve with better prompting |
|
| 309 |
+
| Compile errors even with MINIMAL_INTERFACE | | ✅ Agent can fix |
|
| 310 |
+
| Passes vulnerable but also passes patched | ✅ Model semantics | Partially agent (patch context) |
|
| 311 |
+
| Correct overall but random failures across runs | ✅ Temperature/stochastic | |
|
| 312 |
+
|
| 313 |
+
**Current evidence points:**
|
| 314 |
+
- gemini-3.1-flash-lite is a very small/cheap model → likely hitting model ceiling for complex semantic reasoning
|
| 315 |
+
- The compile errors are 100% agent-fixable (and we mostly did)
|
| 316 |
+
- Specificity failures: partly agent bug (patch not applied) + partly model (generic assertions)
|
| 317 |
+
|
| 318 |
+
**Test: try 3 cases with a stronger model to see the ceiling:**
|
| 319 |
+
```bash
|
| 320 |
+
OPENROUTER_MODEL="google/gemini-2.0-flash" BENCHMARK_LIMIT=3 npx tsx src/benchmark/runTesterBenchmark.ts
|
| 321 |
+
```
|
| 322 |
+
If specificity jumps to >50% with the stronger model, it's the model. If not, it's the agent prompting.
|
| 323 |
+
|
| 324 |
+
---
|
| 325 |
+
|
| 326 |
+
## Next Actions (Priority Order)
|
| 327 |
+
|
| 328 |
+
### 1. Fix production gap — `mapFindingToReport` (HIGH VALUE, LOW EFFORT)
|
| 329 |
+
Add the missing rich context from the auditor to what the tester receives:
|
| 330 |
+
|
| 331 |
+
```typescript
|
| 332 |
+
export function mapFindingToReport(finding: any, sourceCode: string,
|
| 333 |
+
repoContext?: string): VulnerabilityReport {
|
| 334 |
+
return {
|
| 335 |
+
// ... existing fields
|
| 336 |
+
description: [
|
| 337 |
+
finding.description,
|
| 338 |
+
finding.recommendation ? `\nFix recommendation: ${finding.recommendation}` : "",
|
| 339 |
+
finding.judgeReview?.review ? `\nJudge analysis: ${finding.judgeReview.review}` : "",
|
| 340 |
+
repoContext ? `\nProtocol context: ${repoContext.slice(0, 1000)}` : "",
|
| 341 |
+
].filter(Boolean).join("\n"),
|
| 342 |
+
// The recommendation tells the tester what should NOT work after the fix
|
| 343 |
+
// which is the key for writing a specific assertion
|
| 344 |
+
};
|
| 345 |
+
}
|
| 346 |
+
```
|
| 347 |
+
|
| 348 |
+
### 2. Pass `repoPath` as `customSandboxDir` in production
|
| 349 |
+
In server.ts, pass `outputDir` (where Contract.sol was written) as `customSandboxDir`:
|
| 350 |
+
```typescript
|
| 351 |
+
const report = mapFindingToReport(auditorResult.findings[0], coderResult.contract,
|
| 352 |
+
auditorResult.repoContext);
|
| 353 |
+
report.customSandboxDir = outputDir; // ← enables all context improvements
|
| 354 |
+
```
|
| 355 |
+
Since production uses a single Contract.sol with no remappings or test files, the context
|
| 356 |
+
extractor will find no remappings (graceful fallback), and the dep stubber will run a
|
| 357 |
+
probe but find nothing missing (also fine).
|
| 358 |
+
|
| 359 |
+
### 3. Lower MAX_ITERATIONS from 10 to 6
|
| 360 |
+
Credits are limited. 10 iterations is too many for flash-lite which repeats itself after ~5.
|
| 361 |
+
|
| 362 |
+
### 4. Run full benchmark to measure Run 4
|
| 363 |
+
After the test file cleanup fix + patch fix are confirmed working.
|
Definicao_Agente_Tester.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Agente Gerador de PoCs (Tester) - Definição do Projeto
|
| 2 |
+
|
| 3 |
+
Este documento detalha a arquitetura e o fluxo de funcionamento do **Agente Gerador de PoCs (Tester)**, seguindo a mesma estrutura de definição utilizada para o Agente Auditor.
|
| 4 |
+
|
| 5 |
+
---
|
| 6 |
+
|
| 7 |
+
## Diagrama do Tester
|
| 8 |
+
|
| 9 |
+
O Tester é implementado como um `StateGraph` (LangGraph) que recebe o relatório de vulnerabilidade e itera para gerar, compilar e validar um teste executável (Proof of Concept) usando o framework Foundry.
|
| 10 |
+
|
| 11 |
+
```mermaid
|
| 12 |
+
graph TD
|
| 13 |
+
A[Vulnerability Report] -->|Entrada| B(Fase 1: Preparação do Oráculo)
|
| 14 |
+
B --> C(Fase 2: Análise da Vulnerabilidade)
|
| 15 |
+
C --> D(Fase 3: Geração do PoC)
|
| 16 |
+
D --> E(Fase 4: Execução no Foundry)
|
| 17 |
+
|
| 18 |
+
E -->|Sucesso| F[Fim - PoC Validado]
|
| 19 |
+
E -->|Falha| G{Iterações < Max?}
|
| 20 |
+
|
| 21 |
+
G -->|Sim| H(Reflection - Analisar Erro)
|
| 22 |
+
H --> D
|
| 23 |
+
G -->|Não| I[Fim - Falha Timeout]
|
| 24 |
+
```
|
| 25 |
+
|
| 26 |
+
---
|
| 27 |
+
|
| 28 |
+
## Fase 1: Preparação do Oráculo (`oracleNode`)
|
| 29 |
+
|
| 30 |
+
O objetivo desta fase é preparar todo o contexto do repositório para garantir que o LLM tenha as informações corretas de importação e de estado antes de gerar código.
|
| 31 |
+
|
| 32 |
+
1. **Geração do Scaffold:** Cria a estrutura inicial do arquivo de teste (`Exploit.t.sol`), incluindo a função `setUp()` baseada no construtor do contrato alvo.
|
| 33 |
+
2. **Extração de Contexto do Projeto:**
|
| 34 |
+
- Lê o `remappings.txt` do projeto para resolver caminhos de bibliotecas (ex: `@openzeppelin/`).
|
| 35 |
+
- Usa BFS (Breadth-First Search) para encontrar o arquivo `.t.sol` existente mais complexo e extrair seus `imports` como referência de padrão.
|
| 36 |
+
3. **Isolamento e Limpeza:** Remove testes nativos do projeto do diretório `test/` do Foundry para evitar conflitos de compilação quando bibliotecas não estão presentes.
|
| 37 |
+
4. **Criação de Stubs (Dependências Faltantes):** Executa um `forge build` rápido. Se pacotes externos (`node_modules`, `lib/caviar`) estiverem ausentes no ambiente, o agente gera contratos falsos (Stubs) com as interfaces mínimas necessárias para que a compilação prossiga.
|
| 38 |
+
5. **Análise de API (AST):** Gera a assinatura completa (funções, eventos, erros) do contrato alvo para guiar o LLM.
|
| 39 |
+
|
| 40 |
+
---
|
| 41 |
+
|
| 42 |
+
## Fase 2: Análise da Vulnerabilidade (`analyzeVulnerabilityNode`)
|
| 43 |
+
|
| 44 |
+
Esta fase interpreta o relatório do Auditor (e o código fonte) para traçar uma estratégia de ataque antes de escrever o teste.
|
| 45 |
+
|
| 46 |
+
1. **Recebe o Contexto:** Lê o `VulnerabilityReport` contendo:
|
| 47 |
+
- Descrição da falha
|
| 48 |
+
- Revisão do Juiz (explicação técnica do Auditor)
|
| 49 |
+
- *Exploitable Paths* (passo-a-passo sugerido pelo Auditor)
|
| 50 |
+
- Contexto geral do protocolo
|
| 51 |
+
- Diferença de código da correção (*Patch Diff*, se executado via benchmark).
|
| 52 |
+
2. **Definição da Estratégia (LLM):** Pede ao LLM para responder 4 perguntas críticas:
|
| 53 |
+
- Qual a causa raiz?
|
| 54 |
+
- Quais as condições de ativação (precondições)?
|
| 55 |
+
- Qual a sequência exata de chamadas para o exploit?
|
| 56 |
+
- **Qual asserção (`assert`) provará a vulnerabilidade?** (Ex: *o saldo roubado deve ser maior que 0*, ou *a chamada deve reverter com X*).
|
| 57 |
+
|
| 58 |
+
---
|
| 59 |
+
|
| 60 |
+
## Fase 3: Geração do PoC (`generatePoCNode`)
|
| 61 |
+
|
| 62 |
+
É aqui que o código Solidity do exploit é efetivamente escrito. Esta fase adapta o prompt dependendo do estado atual do loop de reflexão.
|
| 63 |
+
|
| 64 |
+
1. **Modo `INITIAL`:** (Primeira tentativa) Gera o código usando o Scaffold do oráculo e a estratégia traçada.
|
| 65 |
+
2. **Modo `FIX_COMPILE`:** (Se falhou ao compilar) Recebe o erro exato do compilador (linha e arquivo). É instruído a verificar imports e tipos.
|
| 66 |
+
3. **Modo `MINIMAL_INTERFACE`:** (Escape Hatch) **Se a compilação falhar 3 vezes seguidas**, o agente abandona os imports de repositório e injeta interfaces cruas (ex: `interface IERC20 { ... }`) direto no arquivo de teste. Isso salva execuções que falhariam por dependências quebradas.
|
| 67 |
+
4. **Modo `FIX_LOGIC`:** (Se compilou, mas o teste reverteu) É instruído a verificar a ordem das chamadas, permissões (`vm.prank`), saldo de setup (`vm.deal`) e analisar os *traces* do EVM. Contém padrões prontos para erros clássicos (ex: *Reentrancy callback*, *Unchecked return values*).
|
| 68 |
+
|
| 69 |
+
---
|
| 70 |
+
|
| 71 |
+
## Fase 4: Execução no Foundry (`runFoundryNode` & `reflectNode`)
|
| 72 |
+
|
| 73 |
+
1. **Sanitização do Código:** Extrai o código Solidity do output do LLM. Valida se o contrato se chama `ExploitTest` e a função principal é `test_Exploit()`.
|
| 74 |
+
2. **Execução Isolada:** Roda o teste dentro de um container/diretório temporário (`customSandboxDir`) usando o comando `forge test`.
|
| 75 |
+
3. **Análise de Logs (`logAnalyzer`):**
|
| 76 |
+
- Parseia a saída bruta do Forge.
|
| 77 |
+
- Categoriza o erro em: `COMPILER_ERROR`, `ASSERTION_FAILED`, `REVERT_NO_MESSAGE`, `SETUP_FAILED`, etc.
|
| 78 |
+
- Extrai as linhas cruciais do erro (ex: `Error (6275): Source "src/Token.sol" not found`).
|
| 79 |
+
4. **Decisão:**
|
| 80 |
+
- **Passou:** Retorna sucesso e o código final.
|
| 81 |
+
- **Falhou:** Envia o log sumarizado de volta para a Fase 3 (via `reflectNode`) iterando até o `MAX_ITERATIONS` (atualmente 6).
|
| 82 |
+
|
| 83 |
+
---
|
| 84 |
+
|
| 85 |
+
## Resultados (Avaliação do Benchmark PoCo)
|
| 86 |
+
|
| 87 |
+
Para avaliar a resiliência do agente e comprovar a arquitetura, ele foi testado contra o dataset público **ASSERT-KTH/Proof-of-Patch** (22 vulnerabilidades reais auditadas, com correções validadas).
|
| 88 |
+
|
| 89 |
+
**Métricas:**
|
| 90 |
+
* **Reproducibility Rate (Taxa de Reprodução):** Capacidade do agente compilar e rodar um PoC que passe na versão vulnerável.
|
| 91 |
+
* **Specificity Rate (Taxa de Especificidade):** Garantia de que o teste criado *falha* quando executado contra o código já corrigido (prova de que a asserção mirou no bug real, e não em falsos positivos genéricos).
|
| 92 |
+
|
| 93 |
+
**Progressão de Resultados:**
|
| 94 |
+
1. **Baseline (Sem Oráculo e sem Reflection inteligente):** ~27% de reprodução (falhas massivas de compilação por imports errados).
|
| 95 |
+
2. **Com Oráculo (Remappings + BFS + Stubs):** As falhas de compilação caíram drasticamente.
|
| 96 |
+
3. **Com `MINIMAL_INTERFACE` e Prompts Refinados:** Atingiu **54.5%** de reprodução em projetos do mundo real, comprovando a eficácia da adaptação dinâmica do agente perante falhas repetidas.
|
src/agents/tester/agent.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
| 1 |
import "dotenv/config";
|
|
|
|
|
|
|
|
|
|
| 2 |
import { StateGraph, END, START } from "@langchain/langgraph";
|
| 3 |
import { PoCStateAnnotation, PoCState } from "./state.js";
|
| 4 |
import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
|
|
@@ -20,7 +23,7 @@ import { createMissingDependencyStubs } from "./utils/dependencyStubber.js";
|
|
| 20 |
import { analyzeSolidityFile } from "../auditor/tools/solidity-analyzer-tool.js";
|
| 21 |
import { extractConstructor } from "./utils/parserUtils.js";
|
| 22 |
|
| 23 |
-
const MAX_ITERATIONS =
|
| 24 |
|
| 25 |
const llm = createLLM();
|
| 26 |
|
|
@@ -41,7 +44,6 @@ async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
|
|
| 41 |
referenceTestHelpers = await analyzeSolidityFile(state.report.referenceTestCode, "short");
|
| 42 |
}
|
| 43 |
|
| 44 |
-
// STEP 4: Extract project-level context (remappings, existing test imports)
|
| 45 |
let projectRemappings = "";
|
| 46 |
let projectTestImports = "";
|
| 47 |
let projectTestFilePath: string | null = null;
|
|
@@ -58,8 +60,28 @@ async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
|
|
| 58 |
console.warn("[oracleNode] could not extract project context:", (e as Error).message);
|
| 59 |
}
|
| 60 |
|
| 61 |
-
// STEP 5:
|
| 62 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
try {
|
| 64 |
await createMissingDependencyStubs(state.report.customSandboxDir);
|
| 65 |
} catch (e) {
|
|
@@ -67,6 +89,7 @@ async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
|
|
| 67 |
}
|
| 68 |
}
|
| 69 |
|
|
|
|
| 70 |
const oracleContext: OracleContext = {
|
| 71 |
solidityScaffold,
|
| 72 |
constructorInfo: constructorInfo?.parameters,
|
|
|
|
| 1 |
import "dotenv/config";
|
| 2 |
+
import fs from "fs/promises";
|
| 3 |
+
import path from "path";
|
| 4 |
+
|
| 5 |
import { StateGraph, END, START } from "@langchain/langgraph";
|
| 6 |
import { PoCStateAnnotation, PoCState } from "./state.js";
|
| 7 |
import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
|
|
|
|
| 23 |
import { analyzeSolidityFile } from "../auditor/tools/solidity-analyzer-tool.js";
|
| 24 |
import { extractConstructor } from "./utils/parserUtils.js";
|
| 25 |
|
| 26 |
+
const MAX_ITERATIONS = 6;
|
| 27 |
|
| 28 |
const llm = createLLM();
|
| 29 |
|
|
|
|
| 44 |
referenceTestHelpers = await analyzeSolidityFile(state.report.referenceTestCode, "short");
|
| 45 |
}
|
| 46 |
|
|
|
|
| 47 |
let projectRemappings = "";
|
| 48 |
let projectTestImports = "";
|
| 49 |
let projectTestFilePath: string | null = null;
|
|
|
|
| 60 |
console.warn("[oracleNode] could not extract project context:", (e as Error).message);
|
| 61 |
}
|
| 62 |
|
| 63 |
+
// STEP 5: Remove existing project test files from sandbox test/ directory.
|
| 64 |
+
// Forge compiles ALL .t.sol files even when only running Exploit.t.sol.
|
| 65 |
+
// Existing tests often import missing deps (@prb/test, lib/caviar, etc.)
|
| 66 |
+
// causing compilation failures even when our Exploit.t.sol is clean.
|
| 67 |
+
// We already extracted the import context we needed — now clean up.
|
| 68 |
+
try {
|
| 69 |
+
const testDir = path.join(state.report.customSandboxDir, "test");
|
| 70 |
+
const testEntries = await fs.readdir(testDir, { withFileTypes: true }).catch(() => []);
|
| 71 |
+
let removed = 0;
|
| 72 |
+
for (const entry of testEntries) {
|
| 73 |
+
if (entry.isFile() && entry.name.endsWith(".t.sol") && entry.name !== "Exploit.t.sol") {
|
| 74 |
+
await fs.unlink(path.join(testDir, entry.name));
|
| 75 |
+
removed++;
|
| 76 |
+
}
|
| 77 |
+
}
|
| 78 |
+
if (removed > 0) console.log(`[oracleNode] Removed ${removed} existing test files from sandbox (avoids missing dep conflicts)`);
|
| 79 |
+
} catch (e) {
|
| 80 |
+
console.warn("[oracleNode] test cleanup failed (non-fatal):", (e as Error).message);
|
| 81 |
+
}
|
| 82 |
+
|
| 83 |
+
// STEP 6: Pre-flight dependency stub creation
|
| 84 |
+
// After removing conflicting test files, create stubs for any remaining missing deps
|
| 85 |
try {
|
| 86 |
await createMissingDependencyStubs(state.report.customSandboxDir);
|
| 87 |
} catch (e) {
|
|
|
|
| 89 |
}
|
| 90 |
}
|
| 91 |
|
| 92 |
+
|
| 93 |
const oracleContext: OracleContext = {
|
| 94 |
solidityScaffold,
|
| 95 |
constructorInfo: constructorInfo?.parameters,
|
src/server.ts
CHANGED
|
@@ -80,9 +80,15 @@ app.post("/api/run", (c) => {
|
|
| 80 |
await send("log", "[Tester] Gerando testes de prova de conceito...");
|
| 81 |
|
| 82 |
if (auditorResult.findings.length > 0) {
|
| 83 |
-
const report = mapFindingToReport(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 84 |
const testerResult = await testerAgent.invoke({ report });
|
| 85 |
|
|
|
|
| 86 |
await send("log", `[Tester] Execução concluída com status: ${testerResult.status}`);
|
| 87 |
|
| 88 |
// Garante que o objeto enviado tem exatamente o que o front espera
|
|
|
|
| 80 |
await send("log", "[Tester] Gerando testes de prova de conceito...");
|
| 81 |
|
| 82 |
if (auditorResult.findings.length > 0) {
|
| 83 |
+
const report = mapFindingToReport(
|
| 84 |
+
auditorResult.findings[0],
|
| 85 |
+
coderResult.contract,
|
| 86 |
+
auditorResult.repoContext // ← now forwarded to tester
|
| 87 |
+
);
|
| 88 |
+
report.customSandboxDir = outputDir; // ← tester runs in real project sandbox
|
| 89 |
const testerResult = await testerAgent.invoke({ report });
|
| 90 |
|
| 91 |
+
|
| 92 |
await send("log", `[Tester] Execução concluída com status: ${testerResult.status}`);
|
| 93 |
|
| 94 |
// Garante que o objeto enviado tem exatamente o que o front espera
|
src/utils/mapFinding.ts
CHANGED
|
@@ -1,32 +1,76 @@
|
|
| 1 |
-
import type {
|
| 2 |
|
| 3 |
/**
|
| 4 |
-
*
|
| 5 |
-
*
|
|
|
|
|
|
|
| 6 |
*/
|
| 7 |
-
export function mapFindingToReport(
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
|
|
|
|
|
|
|
|
|
| 11 |
const nameMatch = finding.path?.match(/([^\/]+)\.sol$/);
|
| 12 |
const contractName = nameMatch ? nameMatch[1] : "TargetContract";
|
| 13 |
|
| 14 |
-
const exploitablePaths = finding.judgeReview?.exploitablePaths || [];
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
return {
|
| 17 |
id: title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50),
|
| 18 |
-
severity:
|
| 19 |
-
|
| 20 |
-
type: finding.type || "custom",
|
| 21 |
title,
|
| 22 |
-
description,
|
| 23 |
affectedContract: {
|
| 24 |
name: contractName,
|
| 25 |
sourceCode,
|
|
|
|
| 26 |
},
|
| 27 |
-
attackVector: exploitablePaths[0] ?? "Unknown
|
| 28 |
exploitablePaths,
|
| 29 |
codeSnippet: finding.codeSnippet,
|
| 30 |
-
location: finding.location
|
|
|
|
| 31 |
};
|
| 32 |
}
|
|
|
|
| 1 |
+
import type { VulnerabilityReport } from "../agents/tester/types.js";
|
| 2 |
|
| 3 |
/**
|
| 4 |
+
* Maps a Finding from the Auditor to a VulnerabilityReport for the Tester.
|
| 5 |
+
* Enriches the description with all available auditor context:
|
| 6 |
+
* judge review, recommendation, exploit paths — giving the tester
|
| 7 |
+
* maximum information to generate a precise, specific PoC.
|
| 8 |
*/
|
| 9 |
+
export function mapFindingToReport(
|
| 10 |
+
finding: any,
|
| 11 |
+
sourceCode: string,
|
| 12 |
+
repoContext?: string
|
| 13 |
+
): VulnerabilityReport {
|
| 14 |
+
const title = finding.title || "Unknown vulnerability";
|
| 15 |
+
|
| 16 |
const nameMatch = finding.path?.match(/([^\/]+)\.sol$/);
|
| 17 |
const contractName = nameMatch ? nameMatch[1] : "TargetContract";
|
| 18 |
|
| 19 |
+
const exploitablePaths: string[] = finding.judgeReview?.exploitablePaths || [];
|
| 20 |
+
|
| 21 |
+
// Build a rich description combining all auditor context
|
| 22 |
+
const descriptionParts: string[] = [
|
| 23 |
+
finding.description || "No description provided by auditor.",
|
| 24 |
+
];
|
| 25 |
+
|
| 26 |
+
if (finding.judgeReview?.review) {
|
| 27 |
+
descriptionParts.push(`\n## Judge Analysis\n${finding.judgeReview.review}`);
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
if (finding.recommendation) {
|
| 31 |
+
descriptionParts.push(`\n## Recommended Fix\n${finding.recommendation}`);
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
if (exploitablePaths.length > 0) {
|
| 35 |
+
descriptionParts.push(`\n## Exploit Paths (step-by-step)\n${exploitablePaths.map((p, i) => `${i + 1}. ${p}`).join("\n")}`);
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
if (repoContext) {
|
| 39 |
+
descriptionParts.push(`\n## Protocol Context\n${repoContext.slice(0, 1500)}`);
|
| 40 |
+
}
|
| 41 |
+
|
| 42 |
+
// Infer vulnerability type from title/description when auditor doesn't provide one
|
| 43 |
+
const inferType = (): string => {
|
| 44 |
+
const text = `${title} ${finding.description || ""}`.toLowerCase();
|
| 45 |
+
if (text.includes("reentr")) return "reentrancy";
|
| 46 |
+
if (text.includes("access control") || text.includes("unauthorized")) return "access control";
|
| 47 |
+
if (text.includes("overflow") || text.includes("underflow")) return "arithmetic";
|
| 48 |
+
if (text.includes("flash loan")) return "flash loan";
|
| 49 |
+
if (text.includes("oracle") || text.includes("price manipul")) return "oracle manipulation";
|
| 50 |
+
if (text.includes("denial of service") || text.includes("dos")) return "denial of service";
|
| 51 |
+
if (text.includes("front.run") || text.includes("sandwich")) return "front-running";
|
| 52 |
+
return "logic error";
|
| 53 |
+
};
|
| 54 |
+
|
| 55 |
+
const severity = ["critical", "high", "medium", "low"].includes(finding.severity)
|
| 56 |
+
? finding.severity
|
| 57 |
+
: "medium";
|
| 58 |
|
| 59 |
return {
|
| 60 |
id: title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50),
|
| 61 |
+
severity: severity as VulnerabilityReport["severity"],
|
| 62 |
+
type: inferType(),
|
|
|
|
| 63 |
title,
|
| 64 |
+
description: descriptionParts.join("\n"),
|
| 65 |
affectedContract: {
|
| 66 |
name: contractName,
|
| 67 |
sourceCode,
|
| 68 |
+
sourceFilePath: finding.path,
|
| 69 |
},
|
| 70 |
+
attackVector: exploitablePaths[0] ?? finding.description?.slice(0, 120) ?? "Unknown",
|
| 71 |
exploitablePaths,
|
| 72 |
codeSnippet: finding.codeSnippet,
|
| 73 |
+
location: finding.location,
|
| 74 |
+
suggestedCheatcodes: [],
|
| 75 |
};
|
| 76 |
}
|