Tales-Cunha commited on
Commit
3f143e9
·
1 Parent(s): 15ab6b4

feat: integrate the agents

Browse files
Dockerfile CHANGED
@@ -20,6 +20,14 @@ RUN npm run build
20
  FROM node:22-slim
21
  WORKDIR /app
22
 
 
 
 
 
 
 
 
 
23
  COPY package.json package-lock.json* ./
24
  COPY patches/ ./patches/
25
  RUN npm install --omit=dev --ignore-scripts
@@ -30,4 +38,8 @@ COPY --from=frontend-build /app/frontend/dist ./frontend/dist
30
  ENV PORT=7860
31
  EXPOSE 7860
32
 
 
 
 
 
33
  CMD ["node", "dist/server.js"]
 
20
  FROM node:22-slim
21
  WORKDIR /app
22
 
23
+ # Install Foundry dependencies
24
+ RUN apt-get update && apt-get install -y curl git && rm -rf /var/lib/apt/lists/*
25
+
26
+ # Install Foundry
27
+ RUN curl -L https://foundry.paradigm.xyz | bash
28
+ ENV PATH="/root/.foundry/bin:${PATH}"
29
+ RUN foundryup
30
+
31
  COPY package.json package-lock.json* ./
32
  COPY patches/ ./patches/
33
  RUN npm install --omit=dev --ignore-scripts
 
38
  ENV PORT=7860
39
  EXPOSE 7860
40
 
41
+ # Ensure scripts are executable
42
+ COPY scripts/ ./scripts/
43
+ RUN chmod +x scripts/*.sh
44
+
45
  CMD ["node", "dist/server.js"]
frontend/src/App.tsx CHANGED
@@ -20,7 +20,11 @@ interface AgentResult {
20
  compilationErrors?: string[];
21
  reviewSummary?: string;
22
  findings?: Finding[];
23
- results?: unknown[];
 
 
 
 
24
  }
25
 
26
  export function App() {
@@ -220,13 +224,38 @@ export function App() {
220
  {testerResult && (
221
  <section style={styles.section}>
222
  <h2 style={styles.sectionTitle}>🧪 Agente Tester</h2>
223
- <div style={styles.codeBox}>
224
- <pre style={styles.code}>
225
- {testerResult.results && testerResult.results.length > 0
226
- ? JSON.stringify(testerResult.results, null, 2)
227
- : "Nenhum resultado de teste gerado."}
228
- </pre>
 
 
 
229
  </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
230
  </section>
231
  )}
232
  </div>
 
20
  compilationErrors?: string[];
21
  reviewSummary?: string;
22
  findings?: Finding[];
23
+ // Tester fields
24
+ status?: string;
25
+ pocCode?: string;
26
+ executionLogs?: string[];
27
+ iterations?: number;
28
  }
29
 
30
  export function App() {
 
224
  {testerResult && (
225
  <section style={styles.section}>
226
  <h2 style={styles.sectionTitle}>🧪 Agente Tester</h2>
227
+ <div style={styles.resultBox}>
228
+ <p style={styles.resultText}>
229
+ <strong>Status:</strong>{" "}
230
+ <span style={{ color: testerResult.status === "success" ? "#22c55e" : "#ef4444" }}>
231
+ {testerResult.status?.toUpperCase()}
232
+ </span>
233
+ <br />
234
+ <strong>Iterações:</strong> {testerResult.iterations}
235
+ </p>
236
  </div>
237
+
238
+ {testerResult.pocCode && (
239
+ <>
240
+ <h3 style={styles.subTitle}>Proof of Concept (Exploit)</h3>
241
+ <div style={styles.codeBox}>
242
+ <pre style={styles.code}>{testerResult.pocCode}</pre>
243
+ </div>
244
+ </>
245
+ )}
246
+
247
+ {testerResult.executionLogs && testerResult.executionLogs.length > 0 && (
248
+ <>
249
+ <h3 style={styles.subTitle}>Logs de Execução (Foundry)</h3>
250
+ <div style={styles.logBox}>
251
+ {testerResult.executionLogs.map((log, i) => (
252
+ <div key={i} style={styles.logLine}>
253
+ {log}
254
+ </div>
255
+ ))}
256
+ </div>
257
+ </>
258
+ )}
259
  </section>
260
  )}
261
  </div>
scripts/setup-sandbox.sh CHANGED
@@ -2,9 +2,13 @@
2
  set -e
3
 
4
  SANDBOX="/tmp/poc-sandbox"
5
- FORGE_BIN="$HOME/.foundry/bin/forge"
6
 
7
- echo "Inicializando sandbox Foundry em $SANDBOX..."
 
 
 
 
 
8
  rm -rf "$SANDBOX"
9
  mkdir -p "$SANDBOX"
10
  cd "$SANDBOX"
 
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
8
+ FORGE_BIN=$(which forge || echo "forge")
9
+ fi
10
+
11
+ echo "Inicializando sandbox Foundry em $SANDBOX usando $FORGE_BIN..."
12
  rm -rf "$SANDBOX"
13
  mkdir -p "$SANDBOX"
14
  cd "$SANDBOX"
src/agents/auditor/agent.ts CHANGED
@@ -24,8 +24,6 @@ import {
24
  } from "./config.ts";
25
  import { matchLines } from "./utils.ts";
26
 
27
- const llm = createLLM();
28
-
29
  const walkDirectory = (dir: string, depth: number, solFiles: string[], docFiles: string[]) => {
30
  if (depth > MAX_DEPTH) return;
31
 
@@ -121,17 +119,29 @@ const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
121
  parts.push(`### ${filePath}\n\`\`\`solidity\n${source}\n\`\`\``);
122
  }
123
 
124
- const model = llm.withStructuredOutput(z.object({ context: z.string() }));
125
- const result = await model.invoke([new SystemMessage(GATHER_CONTEXT_PROMPT), new HumanMessage(parts.join("\n\n"))]);
 
 
126
 
127
- logger.info(`gatherContext: context built (${parts.join("\n\n").length} chars)`);
128
- logger.debug(`gatherContext: full context:\n${parts.join("\n\n")}`);
129
 
130
- return { repoContext: result.context };
131
  };
132
 
133
  const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
134
- const model = llm.withStructuredOutput(z.object({ findings: z.array(CandidateFindingSchema) }));
 
 
 
 
 
 
 
 
 
 
135
 
136
  const previousFeedback =
137
  state.judgeReviews.length > 0
@@ -185,6 +195,7 @@ const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
185
  };
186
 
187
  const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
 
188
  if (state.candidateFindings.length === 0) {
189
  logger.info("judgeFindings: no candidate findings to review, skipping LLM call");
190
  return {
@@ -194,7 +205,16 @@ const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
194
  };
195
  }
196
 
197
- const model = llm.withStructuredOutput(JudgeReviewSchema);
 
 
 
 
 
 
 
 
 
198
 
199
  logger.info(`judgeFindings: reviewing ${state.candidateFindings.length} candidate finding(s) in parallel`);
200
 
@@ -210,12 +230,13 @@ const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
210
  const findingText = `[Finding ${i + 1}] ${finding.title}\nSeverity: ${finding.severity}\nDescription: ${finding.description}\nLocation: ${finding.path} lines ${finding.location}\nCode:\n\`\`\`solidity\n${finding.codeSnippet}\n\`\`\``;
211
 
212
  logger.debug(`judgeFindings: reviewing finding ${i + 1}: ${finding.title}`);
213
- return model.invoke([
214
  new SystemMessage(JUDGE_FINDINGS_PROMPT),
215
  new HumanMessage(
216
  `Contract (${finding.path}):\n\n${source}\n\nProtocol Context:\n${state.repoContext}\n\nFinding to Review:\n\n${findingText}`,
217
  ),
218
  ]);
 
219
  }),
220
  );
221
 
@@ -234,6 +255,7 @@ const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
234
 
235
  const falsePositiveCount = state.candidateFindings.length - findings.length;
236
 
 
237
  logger.info(`judgeFindings: ${findings.length} confirmed, ${falsePositiveCount} false positive(s)`);
238
  logger.debug(`judgeFindings: reviews:\n${JSON.stringify(reviews, null, 2)}`);
239
 
 
24
  } from "./config.ts";
25
  import { matchLines } from "./utils.ts";
26
 
 
 
27
  const walkDirectory = (dir: string, depth: number, solFiles: string[], docFiles: string[]) => {
28
  if (depth > MAX_DEPTH) return;
29
 
 
119
  parts.push(`### ${filePath}\n\`\`\`solidity\n${source}\n\`\`\``);
120
  }
121
 
122
+ const llm = createLLM();
123
+ const result = await llm.invoke([new SystemMessage(GATHER_CONTEXT_PROMPT), new HumanMessage(parts.join("\n\n"))]);
124
+
125
+ const repoContext = typeof result.content === "string" ? result.content : JSON.stringify(result.content);
126
 
127
+ logger.info(`gatherContext: context built (${repoContext.length} chars)`);
128
+ logger.debug(`gatherContext: full context:\n${repoContext}`);
129
 
130
+ return { repoContext };
131
  };
132
 
133
  const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
134
+ console.log("[auditorAgent] Iniciando findVulnerabilities...");
135
+ const llm = createLLM();
136
+ const model = llm.withStructuredOutput(
137
+ z.object({
138
+ findings: z.array(CandidateFindingSchema),
139
+ }),
140
+ {
141
+ name: "vulnerability_report",
142
+ method: "jsonSchema",
143
+ },
144
+ );
145
 
146
  const previousFeedback =
147
  state.judgeReviews.length > 0
 
195
  };
196
 
197
  const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
198
+ console.log(`[auditorAgent] Iniciando judgeFindings para ${state.candidateFindings.length} candidatos...`);
199
  if (state.candidateFindings.length === 0) {
200
  logger.info("judgeFindings: no candidate findings to review, skipping LLM call");
201
  return {
 
205
  };
206
  }
207
 
208
+ const llm = createLLM();
209
+ const model = llm.withStructuredOutput(
210
+ z.object({
211
+ review_result: JudgeReviewSchema,
212
+ }),
213
+ {
214
+ name: "judge_review",
215
+ method: "jsonSchema",
216
+ },
217
+ );
218
 
219
  logger.info(`judgeFindings: reviewing ${state.candidateFindings.length} candidate finding(s) in parallel`);
220
 
 
230
  const findingText = `[Finding ${i + 1}] ${finding.title}\nSeverity: ${finding.severity}\nDescription: ${finding.description}\nLocation: ${finding.path} lines ${finding.location}\nCode:\n\`\`\`solidity\n${finding.codeSnippet}\n\`\`\``;
231
 
232
  logger.debug(`judgeFindings: reviewing finding ${i + 1}: ${finding.title}`);
233
+ const result = await model.invoke([
234
  new SystemMessage(JUDGE_FINDINGS_PROMPT),
235
  new HumanMessage(
236
  `Contract (${finding.path}):\n\n${source}\n\nProtocol Context:\n${state.repoContext}\n\nFinding to Review:\n\n${findingText}`,
237
  ),
238
  ]);
239
+ return result.review_result;
240
  }),
241
  );
242
 
 
255
 
256
  const falsePositiveCount = state.candidateFindings.length - findings.length;
257
 
258
+ console.log(`[auditorAgent] Concluído: ${findings.length} confirmados, ${falsePositiveCount} falsos positivos.`);
259
  logger.info(`judgeFindings: ${findings.length} confirmed, ${falsePositiveCount} false positive(s)`);
260
  logger.debug(`judgeFindings: reviews:\n${JSON.stringify(reviews, null, 2)}`);
261
 
src/agents/auditor/prompts.ts CHANGED
@@ -24,7 +24,9 @@ Seja preciso e exaustivo — quanto mais rico o contexto, com mais precisão as
24
 
25
  export const FIND_VULNERABILITIES_PROMPT = `Você é um auditor especialista em segurança de smart contracts com foco em Solidity. Analise sistematicamente o código-fonte do contrato e o contexto do protocolo para identificar vulnerabilidades de segurança.
26
 
27
- Para cada vulnerabilidade, forneça TODOS os seguintes campos:
 
 
28
 
29
  - **title**: Nome curto e preciso (ex.: "Reentrância em withdraw", "Controle de acesso ausente em setFee").
30
  - **description**: Explique o comportamento ESPERADO versus o comportamento OBSERVADO (vulnerável) em 2 a 4 frases.
@@ -38,7 +40,9 @@ Se feedback do juiz de uma iteração anterior for fornecido, remova os falsos p
38
 
39
  export const JUDGE_FINDINGS_PROMPT = `Você é um revisor rigoroso de segurança de smart contracts. Avalie cada vulnerabilidade candidata submetida pelo auditor e determine se é um verdadeiro positivo ou um falso positivo.
40
 
41
- Para cada achado, forneça TODOS os seguintes campos:
 
 
42
 
43
  - **review**: Análise detalhada (3 a 6 frases) explicando por que a vulnerabilidade é ou não real. Referencie código específico, invariantes do protocolo, pré-condições e controles mitigadores.
44
  - **isFalsePositive**: true se o achado NÃO for explorável na prática; false se for uma vulnerabilidade real.
 
24
 
25
  export const FIND_VULNERABILITIES_PROMPT = `Você é um auditor especialista em segurança de smart contracts com foco em Solidity. Analise sistematicamente o código-fonte do contrato e o contexto do protocolo para identificar vulnerabilidades de segurança.
26
 
27
+ Sua resposta deve ser um objeto JSON contendo uma lista de achados sob a chave "findings".
28
+
29
+ Para cada vulnerabilidade em "findings", forneça TODOS os seguintes campos:
30
 
31
  - **title**: Nome curto e preciso (ex.: "Reentrância em withdraw", "Controle de acesso ausente em setFee").
32
  - **description**: Explique o comportamento ESPERADO versus o comportamento OBSERVADO (vulnerável) em 2 a 4 frases.
 
40
 
41
  export const JUDGE_FINDINGS_PROMPT = `Você é um revisor rigoroso de segurança de smart contracts. Avalie cada vulnerabilidade candidata submetida pelo auditor e determine se é um verdadeiro positivo ou um falso positivo.
42
 
43
+ Sua resposta deve ser um objeto JSON contendo a revisão sob a chave "review_result".
44
+
45
+ Para o achado fornecido, preencha os seguintes campos em "review_result":
46
 
47
  - **review**: Análise detalhada (3 a 6 frases) explicando por que a vulnerabilidade é ou não real. Referencie código específico, invariantes do protocolo, pré-condições e controles mitigadores.
48
  - **isFalsePositive**: true se o achado NÃO for explorável na prática; false se for uma vulnerabilidade real.
src/agents/tester/agent.ts CHANGED
@@ -12,7 +12,7 @@ import { analyzeFoundryLog } from "./utils/logAnalyzer.js";
12
  const MAX_ITERATIONS = 5;
13
 
14
  const llm = new ChatOpenRouter({
15
- model: "deepseek/deepseek-v4-flash",
16
  temperature: 0.2,
17
  apiKey: process.env.OPENROUTER_API_KEY,
18
  });
@@ -57,7 +57,7 @@ Scaffold (complete APENAS test_Exploit):
57
  ${oracleContext!.solidityScaffold}
58
  \`\`\``;
59
 
60
- console.log(`[generatePoCNode] iteração ${iterations + 1}, isRetry=${isRetry}`);
61
 
62
  try {
63
  const response = await llm.invoke([
@@ -65,20 +65,24 @@ ${oracleContext!.solidityScaffold}
65
  { role: "user", content: userMessage },
66
  ]);
67
  const solidityCode = extractSolidity(response.content as string);
68
- console.log("[generatePoCNode] Solidity extraído, tamanho:", solidityCode.length);
69
  return { pocCode: solidityCode, iterations: 1 };
70
  } catch (err) {
71
- console.error("[generatePoCNode] falha:", (err as Error).message);
72
  return { iterations: 1, lastError: `Erro na geração/extração: ${(err as Error).message}` };
73
  }
74
  }
75
 
76
  async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
 
77
  const result = await runFoundry(state.pocCode);
78
  const analysis = analyzeFoundryLog(result);
79
  const passed = result.exitCode === 0 && result.stdout.includes("ok");
80
 
81
- console.log(`[runFoundryNode] exitCode=${result.exitCode}, passed=${passed}`);
 
 
 
82
 
83
  return {
84
  executionLogs: [result.combined], // reducer append
 
12
  const MAX_ITERATIONS = 5;
13
 
14
  const llm = new ChatOpenRouter({
15
+ model: process.env.OPENROUTER_MODEL || "deepseek/deepseek-v4-flash",
16
  temperature: 0.2,
17
  apiKey: process.env.OPENROUTER_API_KEY,
18
  });
 
57
  ${oracleContext!.solidityScaffold}
58
  \`\`\``;
59
 
60
+ console.log(`[testerAgent] generatePoCNode iteração ${iterations + 1}, isRetry=${isRetry}`);
61
 
62
  try {
63
  const response = await llm.invoke([
 
65
  { role: "user", content: userMessage },
66
  ]);
67
  const solidityCode = extractSolidity(response.content as string);
68
+ console.log("[testerAgent] Solidity extraído, tamanho:", solidityCode.length);
69
  return { pocCode: solidityCode, iterations: 1 };
70
  } catch (err) {
71
+ console.error("[testerAgent] falha na geração:", (err as Error).message);
72
  return { iterations: 1, lastError: `Erro na geração/extração: ${(err as Error).message}` };
73
  }
74
  }
75
 
76
  async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
77
+ console.log("[testerAgent] Executando runFoundryNode...");
78
  const result = await runFoundry(state.pocCode);
79
  const analysis = analyzeFoundryLog(result);
80
  const passed = result.exitCode === 0 && result.stdout.includes("ok");
81
 
82
+ console.log(`[testerAgent] Resultado Foundry: exitCode=${result.exitCode}, passed=${passed}`);
83
+ if (!passed) {
84
+ console.log(`[testerAgent] Falha detectada: ${analysis.summary}`);
85
+ }
86
 
87
  return {
88
  executionLogs: [result.combined], // reducer append
src/config/llm.ts CHANGED
@@ -1,5 +1,6 @@
1
  import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
2
  import { ChatAnthropic } from "@langchain/anthropic";
 
3
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
4
 
5
  export type LLMProvider = "google" | "openrouter" | "anthropic";
@@ -8,17 +9,20 @@ export function createLLM(overrideProvider?: LLMProvider): BaseChatModel {
8
  const provider = overrideProvider || (process.env.LLM_PROVIDER as LLMProvider) || "google";
9
 
10
  switch (provider) {
11
- case "openrouter": {
12
- const { ChatOpenRouter } = require("@langchain/openrouter");
13
  return new ChatOpenRouter({
14
- model: process.env.OPENROUTER_MODEL || "google/gemini-2.5-flash",
15
  temperature: 0.2,
16
- }) as BaseChatModel;
17
- }
 
 
 
18
  case "anthropic":
19
  return new ChatAnthropic({
20
  model: process.env.ANTHROPIC_MODEL || "claude-sonnet-4-6",
21
  temperature: 0.2,
 
22
  });
23
  case "google":
24
  default:
@@ -26,6 +30,7 @@ export function createLLM(overrideProvider?: LLMProvider): BaseChatModel {
26
  apiKey: process.env.GOOGLE_API_KEY || "",
27
  model: process.env.MODEL_NAME || "gemini-2.5-flash",
28
  temperature: 0.2,
 
29
  });
30
  }
31
  }
 
1
  import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
2
  import { ChatAnthropic } from "@langchain/anthropic";
3
+ import { ChatOpenRouter } from "@langchain/openrouter";
4
  import type { BaseChatModel } from "@langchain/core/language_models/chat_models";
5
 
6
  export type LLMProvider = "google" | "openrouter" | "anthropic";
 
9
  const provider = overrideProvider || (process.env.LLM_PROVIDER as LLMProvider) || "google";
10
 
11
  switch (provider) {
12
+ case "openrouter":
 
13
  return new ChatOpenRouter({
14
+ model: process.env.OPENROUTER_MODEL || "openai/gpt-4o-mini",
15
  temperature: 0.2,
16
+ apiKey: process.env.OPENROUTER_API_KEY,
17
+ maxTokens: 4096,
18
+ });
19
+
20
+
21
  case "anthropic":
22
  return new ChatAnthropic({
23
  model: process.env.ANTHROPIC_MODEL || "claude-sonnet-4-6",
24
  temperature: 0.2,
25
+ maxTokens: 4096,
26
  });
27
  case "google":
28
  default:
 
30
  apiKey: process.env.GOOGLE_API_KEY || "",
31
  model: process.env.MODEL_NAME || "gemini-2.5-flash",
32
  temperature: 0.2,
33
+ maxOutputTokens: 4096,
34
  });
35
  }
36
  }
src/index.ts CHANGED
@@ -9,6 +9,7 @@ import { coderAgent } from "./agents/coder/agent.js";
9
  import { testerAgent } from "./agents/tester/agent.js";
10
  import { logger } from "./logger.js";
11
  import type { VulnerabilityReport, Finding } from "./agents/tester/types.js";
 
12
 
13
  const inputPath = process.argv[2];
14
 
@@ -42,33 +43,6 @@ for (const f of auditorResult.findings) {
42
  logger.info(` [${f.severity.toUpperCase()}] ${f.title} — ${f.location}`);
43
  }
44
 
45
- function mapFindingToReport(finding: any, sourceCode: string): VulnerabilityReport {
46
- const title = finding.title || finding.type || "Unknown vulnerability";
47
- const description = finding.description || "No description provided by auditor.";
48
-
49
- const nameMatch = finding.path?.match(/([^\/]+)\.sol$/);
50
- const contractName = nameMatch ? nameMatch[1] : "TargetContract";
51
-
52
- const exploitablePaths = finding.judgeReview?.exploitablePaths || [];
53
-
54
- return {
55
- id: title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 50),
56
- severity: (finding.severity === "high" || finding.severity === "medium" || finding.severity === "low")
57
- ? finding.severity : "low",
58
- type: finding.type || "custom",
59
- title,
60
- description,
61
- affectedContract: {
62
- name: contractName,
63
- sourceCode,
64
- },
65
- attackVector: exploitablePaths[0] ?? "Unknown vector",
66
- exploitablePaths,
67
- codeSnippet: finding.codeSnippet,
68
- location: finding.location
69
- };
70
- }
71
-
72
  if (auditorResult.findings.length > 0) {
73
  const finding = auditorResult.findings[0];
74
  const report = mapFindingToReport(finding, coderResult.contract);
 
9
  import { testerAgent } from "./agents/tester/agent.js";
10
  import { logger } from "./logger.js";
11
  import type { VulnerabilityReport, Finding } from "./agents/tester/types.js";
12
+ import { mapFindingToReport } from "./utils/mapFinding.js";
13
 
14
  const inputPath = process.argv[2];
15
 
 
43
  logger.info(` [${f.severity.toUpperCase()}] ${f.title} — ${f.location}`);
44
  }
45
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
  if (auditorResult.findings.length > 0) {
47
  const finding = auditorResult.findings[0];
48
  const report = mapFindingToReport(finding, coderResult.contract);
src/server.ts CHANGED
@@ -12,6 +12,7 @@ import { serveStatic } from "@hono/node-server/serve-static";
12
  import { coderAgent } from "./agents/coder/agent.ts";
13
  import { auditorAgent } from "./agents/auditor/agent.ts";
14
  import { testerAgent } from "./agents/tester/agent.ts";
 
15
 
16
  const app = new Hono();
17
 
@@ -77,18 +78,25 @@ app.post("/api/run", (c) => {
77
 
78
  // === TESTER ===
79
  await send("log", "[Tester] Gerando testes de prova de conceito...");
80
- const testerResult = await testerAgent.invoke({
81
- solidityFiles: [coderResult.contract],
82
- vulnerability: auditorResult.findings[0] ?? {},
83
- });
84
- await send("log", `[Tester] ${testerResult.results.length} resultado(s) de teste.`);
85
 
86
- await send(
87
- "tester",
88
- JSON.stringify({
89
- results: testerResult.results,
90
- }),
91
- );
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  await send("log", "Pipeline concluído.");
94
  await send("done", "ok");
 
12
  import { coderAgent } from "./agents/coder/agent.ts";
13
  import { auditorAgent } from "./agents/auditor/agent.ts";
14
  import { testerAgent } from "./agents/tester/agent.ts";
15
+ import { mapFindingToReport } from "./utils/mapFinding.js";
16
 
17
  const app = new Hono();
18
 
 
78
 
79
  // === TESTER ===
80
  await send("log", "[Tester] Gerando testes de prova de conceito...");
 
 
 
 
 
81
 
82
+ if (auditorResult.findings.length > 0) {
83
+ const report = mapFindingToReport(auditorResult.findings[0], coderResult.contract);
84
+ const testerResult = await testerAgent.invoke({ report });
85
+
86
+ await send("log", `[Tester] Execução concluída com status: ${testerResult.status}`);
87
+ await send(
88
+ "tester",
89
+ JSON.stringify({
90
+ status: testerResult.status,
91
+ pocCode: testerResult.pocCode,
92
+ executionLogs: testerResult.executionLogs,
93
+ iterations: testerResult.iterations,
94
+ }),
95
+ );
96
+ } else {
97
+ await send("log", "[Tester] Nenhuma vulnerabilidade para testar.");
98
+ await send("tester", JSON.stringify({ results: [] }));
99
+ }
100
 
101
  await send("log", "Pipeline concluído.");
102
  await send("done", "ok");
src/utils/mapFinding.ts ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import type { Finding, VulnerabilityReport } from "../agents/tester/types.js";
2
+
3
+ /**
4
+ * Mapeia um achado (Finding) do Auditor para um relatório de vulnerabilidade (VulnerabilityReport)
5
+ * compatível com o Gerador de PoCs (Tester).
6
+ */
7
+ export function mapFindingToReport(finding: any, sourceCode: string): VulnerabilityReport {
8
+ const title = finding.title || finding.type || "Unknown vulnerability";
9
+ const description = finding.description || "No description provided by auditor.";
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: (finding.severity === "high" || finding.severity === "medium" || finding.severity === "low")
19
+ ? finding.severity : "low",
20
+ type: finding.type || "custom",
21
+ title,
22
+ description,
23
+ affectedContract: {
24
+ name: contractName,
25
+ sourceCode,
26
+ },
27
+ attackVector: exploitablePaths[0] ?? "Unknown vector",
28
+ exploitablePaths,
29
+ codeSnippet: finding.codeSnippet,
30
+ location: finding.location
31
+ };
32
+ }