Uanderson Silva commited on
Commit
48c7a9b
·
1 Parent(s): 4c144c5

unify logs under custom logger

Browse files
src/agents/auditor/agent.ts CHANGED
@@ -57,7 +57,7 @@ const walkDirectory = (dir: string, depth: number, solFiles: string[], docFiles:
57
  };
58
 
59
  const defineScope: GraphNode<typeof AuditorState> = async (state) => {
60
- logger.info(`defineScope: walking repo at ${state.repoPath}`);
61
 
62
  const solFiles: string[] = [];
63
  const docFiles: string[] = [];
@@ -66,16 +66,16 @@ const defineScope: GraphNode<typeof AuditorState> = async (state) => {
66
 
67
  const fileTree = buildRepoTree(state.repoPath);
68
 
69
- logger.info(`defineScope: found ${solFiles.length} Solidity file(s), ${docFiles.length} doc file(s)`);
70
- logger.debug(`defineScope: Solidity files: ${JSON.stringify(solFiles)}`);
71
- logger.debug(`defineScope: doc files: ${JSON.stringify(docFiles)}`);
72
- logger.debug(`defineScope: file tree:\n${fileTree}`);
73
 
74
  return { scope: solFiles, docs: docFiles, fileTree };
75
  };
76
 
77
  const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
78
- logger.info(`gatherContext: processing ${state.scope.length} Solidity file(s) and ${state.docs.length} doc file(s)`);
79
 
80
  const readFile = (filePath: string): string => {
81
  try {
@@ -124,8 +124,8 @@ const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
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
  };
@@ -144,7 +144,7 @@ const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
144
  : null;
145
 
146
  logger.info(
147
- `findVulnerabilities: invoking LLM for ${state.scope.length} file(s) in parallel (iteration ${state.reflectionCount + 1})`,
148
  );
149
 
150
  const allFindings = await Promise.all(
@@ -162,7 +162,7 @@ const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
162
  userMessage += `\n\nJudge feedback from previous iteration (iteration ${state.reflectionCount}):\n${previousFeedback}\n\nRevise your findings accordingly.`;
163
  }
164
 
165
- logger.debug(`findVulnerabilities: processing ${filePath}`);
166
 
167
  const result = await model.invoke([
168
  new SystemMessage(FIND_VULNERABILITIES_PROMPT),
@@ -178,15 +178,15 @@ const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
178
  );
179
 
180
  const candidateFindings = allFindings.flat();
181
- logger.info(`findVulnerabilities: LLM returned ${candidateFindings.length} total candidate finding(s)`);
182
- logger.debug(`findVulnerabilities: findings:\n${JSON.stringify(candidateFindings, null, 2)}`);
183
 
184
  return { candidateFindings };
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 {
191
  judgeReviews: [],
192
  findings: [],
@@ -196,7 +196,7 @@ const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
196
 
197
  const model = llm.withStructuredOutput(JudgeReviewSchema);
198
 
199
- logger.info(`judgeFindings: reviewing ${state.candidateFindings.length} candidate finding(s) in parallel`);
200
 
201
  const reviews = await Promise.all(
202
  state.candidateFindings.map(async (finding, i) => {
@@ -209,7 +209,7 @@ const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
209
 
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(
@@ -234,8 +234,8 @@ 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
 
240
  return {
241
  judgeReviews: reviews,
 
57
  };
58
 
59
  const defineScope: GraphNode<typeof AuditorState> = async (state) => {
60
+ logger.info(`[Auditor] defineScope: percorrendo repositório em ${state.repoPath}`);
61
 
62
  const solFiles: string[] = [];
63
  const docFiles: string[] = [];
 
66
 
67
  const fileTree = buildRepoTree(state.repoPath);
68
 
69
+ logger.info(`[Auditor] defineScope: encontrado(s) ${solFiles.length} arquivo(s) Solidity e ${docFiles.length} arquivo(s) de documentação`);
70
+ logger.debug(`[Auditor] defineScope: arquivos Solidity: ${JSON.stringify(solFiles)}`);
71
+ logger.debug(`[Auditor] defineScope: arquivos de documentação: ${JSON.stringify(docFiles)}`);
72
+ logger.debug(`[Auditor] defineScope: árvore de arquivos:\n${fileTree}`);
73
 
74
  return { scope: solFiles, docs: docFiles, fileTree };
75
  };
76
 
77
  const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
78
+ logger.info(`[Auditor] gatherContext: processando ${state.scope.length} arquivo(s) Solidity e ${state.docs.length} arquivo(s) de documentação`);
79
 
80
  const readFile = (filePath: string): string => {
81
  try {
 
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(`[Auditor] gatherContext: contexto construído (${parts.join("\n\n").length} caracteres)`);
128
+ logger.debug(`[Auditor] gatherContext: contexto completo:\n${parts.join("\n\n")}`);
129
 
130
  return { repoContext: result.context };
131
  };
 
144
  : null;
145
 
146
  logger.info(
147
+ `[Auditor] findVulnerabilities: invocando LLM para ${state.scope.length} arquivo(s) em paralelo (iteração ${state.reflectionCount + 1})`,
148
  );
149
 
150
  const allFindings = await Promise.all(
 
162
  userMessage += `\n\nJudge feedback from previous iteration (iteration ${state.reflectionCount}):\n${previousFeedback}\n\nRevise your findings accordingly.`;
163
  }
164
 
165
+ logger.debug(`[Auditor] findVulnerabilities: processando ${filePath}`);
166
 
167
  const result = await model.invoke([
168
  new SystemMessage(FIND_VULNERABILITIES_PROMPT),
 
178
  );
179
 
180
  const candidateFindings = allFindings.flat();
181
+ logger.info(`[Auditor] findVulnerabilities: LLM retornou ${candidateFindings.length} finding(s) candidato(s) no total`);
182
+ logger.debug(`[Auditor] findVulnerabilities: findings:\n${JSON.stringify(candidateFindings, null, 2)}`);
183
 
184
  return { candidateFindings };
185
  };
186
 
187
  const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
188
  if (state.candidateFindings.length === 0) {
189
+ logger.info("[Auditor] judgeFindings: sem findings candidatos para revisar, pulando chamada ao LLM");
190
  return {
191
  judgeReviews: [],
192
  findings: [],
 
196
 
197
  const model = llm.withStructuredOutput(JudgeReviewSchema);
198
 
199
+ logger.info(`[Auditor] judgeFindings: revisando ${state.candidateFindings.length} finding(s) candidato(s) em paralelo`);
200
 
201
  const reviews = await Promise.all(
202
  state.candidateFindings.map(async (finding, i) => {
 
209
 
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(`[Auditor] judgeFindings: revisando finding ${i + 1}: ${finding.title}`);
213
  return model.invoke([
214
  new SystemMessage(JUDGE_FINDINGS_PROMPT),
215
  new HumanMessage(
 
234
 
235
  const falsePositiveCount = state.candidateFindings.length - findings.length;
236
 
237
+ logger.info(`[Auditor] judgeFindings: ${findings.length} confirmado(s), ${falsePositiveCount} falso(s) positivo(s)`);
238
+ logger.debug(`[Auditor] judgeFindings: revisões:\n${JSON.stringify(reviews, null, 2)}`);
239
 
240
  return {
241
  judgeReviews: reviews,
src/agents/tester/agent.ts CHANGED
@@ -1,25 +1,26 @@
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";
5
- import { OracleContext } from "./types.js";
6
  import { createLLM } from "../../config/llm.ts";
7
  import { SYSTEM_PROMPT } from "./prompts/system.js";
8
  import { extractSolidity } from "./utils/extractSolidity.js";
9
  import { runFoundry } from "./tools/foundryRunner.js";
10
  import { analyzeFoundryLog } from "./utils/logAnalyzer.js";
 
11
 
12
  const MAX_ITERATIONS = 5;
13
 
14
  const llm = createLLM();
15
 
16
  async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
17
- console.log("[oracleNode] gerando scaffold para:", state.report.title);
18
 
19
  const solidityScaffold = generateLocalScaffold(state.report);
20
  const oracleContext: OracleContext = { solidityScaffold };
21
 
22
- console.log("[oracleNode] scaffold gerado, tamanho:", solidityScaffold.length, "chars");
23
  return { oracleContext };
24
  }
25
 
@@ -53,7 +54,7 @@ Scaffold (complete APENAS test_Exploit):
53
  ${oracleContext!.solidityScaffold}
54
  \`\`\``;
55
 
56
- console.log(`[testerAgent] generatePoCNode iteração ${iterations + 1}, isRetry=${isRetry}`);
57
 
58
  try {
59
  const response = await llm.invoke([
@@ -61,16 +62,16 @@ ${oracleContext!.solidityScaffold}
61
  { role: "user", content: userMessage },
62
  ]);
63
  const solidityCode = extractSolidity(response.content as string);
64
- console.log("[testerAgent] Solidity extraído, tamanho:", solidityCode.length);
65
  return { pocCode: solidityCode, iterations: 1 };
66
  } catch (err) {
67
- console.error("[testerAgent] falha na geração:", (err as Error).message);
68
  return { iterations: 1, lastError: `Erro na geração/extração: ${(err as Error).message}` };
69
  }
70
  }
71
 
72
  async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
73
- console.log("[testerAgent] Executando runFoundryNode...");
74
 
75
  const trimmedCode = state.pocCode.trim();
76
  const isMissingCode = trimmedCode.length === 0;
@@ -78,14 +79,15 @@ async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
78
  const isMissingTest = !trimmedCode.includes("function test_Exploit()");
79
  const isPlaceholder = trimmedCode.includes("TODO: implementar exploit");
80
  if (isMissingCode || isMissingContract || isMissingTest || isPlaceholder) {
81
- const summary = state.lastError ?? (isMissingCode
82
- ? "Código Solidity ausente. O LLM não retornou o arquivo do exploit."
83
- : isMissingContract
84
- ? "Contrato ExploitTest não encontrado no arquivo."
85
- : isMissingTest
86
- ? "Função test_Exploit() não encontrada no arquivo."
87
- : "Exploit não implementado (placeholder TODO ainda presente)."
88
- );
 
89
  const status = state.iterations >= MAX_ITERATIONS ? "failed" : "running";
90
  return {
91
  executionLogs: [summary],
@@ -94,30 +96,24 @@ async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
94
  };
95
  }
96
 
97
- const result = await runFoundry(state.pocCode);
98
  const analysis = analyzeFoundryLog(result);
99
  const noTestsFound = result.combined.includes("No tests found");
100
  const summary = noTestsFound
101
  ? "Forge não encontrou nenhum teste. Verifique se o contrato se chama ExploitTest e se existe test_Exploit()."
102
  : analysis.summary;
103
- const passed = result.exitCode === 0 && result.stdout.includes("ok") && !noTestsFound;
104
  const isLastAttempt = state.iterations >= MAX_ITERATIONS;
105
 
106
- const status = passed
107
- ? "success"
108
- : result.timedOut
109
- ? "timeout"
110
- : isLastAttempt
111
- ? "failed"
112
- : "running";
113
 
114
- console.log(`[testerAgent] Resultado Foundry: exitCode=${result.exitCode}, passed=${passed}`);
115
  if (!passed) {
116
- console.log(`[testerAgent] Falha detectada: ${analysis.summary}`);
117
  }
118
 
119
  return {
120
- executionLogs: [result.combined], // reducer append
121
  lastError: summary,
122
  status,
123
  };
@@ -130,14 +126,17 @@ async function reflectNode(state: PoCState): Promise<Partial<PoCState>> {
130
  }
131
 
132
  const mockResult = {
133
- exitCode: 1, timedOut: lastLog.includes("TIMEOUT"),
134
- stdout: "", stderr: "", combined: lastLog,
 
 
 
135
  };
136
 
137
  const analysis = analyzeFoundryLog(mockResult as any);
138
 
139
- console.log(`[reflectNode] categoria: ${analysis.category}`);
140
- console.log(`[reflectNode] resumo: ${analysis.summary}`);
141
 
142
  return {
143
  lastError: `[${analysis.category.toUpperCase()}] ${analysis.summary}\n\nLinhas relevantes:\n${analysis.relevantLines.join("\n")}`,
 
 
1
  import { StateGraph, END, START } from "@langchain/langgraph";
2
+
3
+ import { PoCStateAnnotation, type PoCState } from "./state.js";
4
  import { generateLocalScaffold } from "./tools/scaffoldGenerator.js";
5
+ import type { OracleContext } from "./types.js";
6
  import { createLLM } from "../../config/llm.ts";
7
  import { SYSTEM_PROMPT } from "./prompts/system.js";
8
  import { extractSolidity } from "./utils/extractSolidity.js";
9
  import { runFoundry } from "./tools/foundryRunner.js";
10
  import { analyzeFoundryLog } from "./utils/logAnalyzer.js";
11
+ import { logger } from "../../logger.ts";
12
 
13
  const MAX_ITERATIONS = 5;
14
 
15
  const llm = createLLM();
16
 
17
  async function oracleNode(state: PoCState): Promise<Partial<PoCState>> {
18
+ logger.info(`[Tester] oracleNode: gerando scaffold para: ${state.report.title}`);
19
 
20
  const solidityScaffold = generateLocalScaffold(state.report);
21
  const oracleContext: OracleContext = { solidityScaffold };
22
 
23
+ logger.info(`[Tester] oracleNode: scaffold gerado, tamanho: ${solidityScaffold.length} chars`);
24
  return { oracleContext };
25
  }
26
 
 
54
  ${oracleContext!.solidityScaffold}
55
  \`\`\``;
56
 
57
+ logger.info(`[Tester] generatePoCNode: iteração ${iterations + 1}, isRetry=${isRetry}`);
58
 
59
  try {
60
  const response = await llm.invoke([
 
62
  { role: "user", content: userMessage },
63
  ]);
64
  const solidityCode = extractSolidity(response.content as string);
65
+ logger.info(`[Tester] generatePoCNode: Solidity extraído, tamanho: ${solidityCode.length}`);
66
  return { pocCode: solidityCode, iterations: 1 };
67
  } catch (err) {
68
+ logger.error(`[Tester] generatePoCNode: falha na geração: ${(err as Error).message}`);
69
  return { iterations: 1, lastError: `Erro na geração/extração: ${(err as Error).message}` };
70
  }
71
  }
72
 
73
  async function runFoundryNode(state: PoCState): Promise<Partial<PoCState>> {
74
+ logger.info("[Tester] runFoundryNode: executando...");
75
 
76
  const trimmedCode = state.pocCode.trim();
77
  const isMissingCode = trimmedCode.length === 0;
 
79
  const isMissingTest = !trimmedCode.includes("function test_Exploit()");
80
  const isPlaceholder = trimmedCode.includes("TODO: implementar exploit");
81
  if (isMissingCode || isMissingContract || isMissingTest || isPlaceholder) {
82
+ const summary =
83
+ state.lastError ??
84
+ (isMissingCode
85
+ ? "Código Solidity ausente. O LLM não retornou o arquivo do exploit."
86
+ : isMissingContract
87
+ ? "Contrato ExploitTest não encontrado no arquivo."
88
+ : isMissingTest
89
+ ? "Função test_Exploit() não encontrada no arquivo."
90
+ : "Exploit não implementado (placeholder TODO ainda presente).");
91
  const status = state.iterations >= MAX_ITERATIONS ? "failed" : "running";
92
  return {
93
  executionLogs: [summary],
 
96
  };
97
  }
98
 
99
+ const result = await runFoundry(state.pocCode);
100
  const analysis = analyzeFoundryLog(result);
101
  const noTestsFound = result.combined.includes("No tests found");
102
  const summary = noTestsFound
103
  ? "Forge não encontrou nenhum teste. Verifique se o contrato se chama ExploitTest e se existe test_Exploit()."
104
  : analysis.summary;
105
+ const passed = result.exitCode === 0 && result.stdout.includes("ok") && !noTestsFound;
106
  const isLastAttempt = state.iterations >= MAX_ITERATIONS;
107
 
108
+ const status = passed ? "success" : result.timedOut ? "timeout" : isLastAttempt ? "failed" : "running";
 
 
 
 
 
 
109
 
110
+ logger.info(`[Tester] runFoundryNode: resultado Foundry: exitCode=${result.exitCode}, passed=${passed}`);
111
  if (!passed) {
112
+ logger.info(`[Tester] runFoundryNode: falha detectada: ${analysis.summary}`);
113
  }
114
 
115
  return {
116
+ executionLogs: [result.combined], // reducer append
117
  lastError: summary,
118
  status,
119
  };
 
126
  }
127
 
128
  const mockResult = {
129
+ exitCode: 1,
130
+ timedOut: lastLog.includes("TIMEOUT"),
131
+ stdout: "",
132
+ stderr: "",
133
+ combined: lastLog,
134
  };
135
 
136
  const analysis = analyzeFoundryLog(mockResult as any);
137
 
138
+ logger.info(`[Tester] reflectNode: categoria: ${analysis.category}`);
139
+ logger.info(`[Tester] reflectNode: resumo: ${analysis.summary}`);
140
 
141
  return {
142
  lastError: `[${analysis.category.toUpperCase()}] ${analysis.summary}\n\nLinhas relevantes:\n${analysis.relevantLines.join("\n")}`,
src/agents/tester/index.ts CHANGED
@@ -1,5 +1,6 @@
1
  import { testerAgent } from "./agent.js";
2
  import { VulnerabilityReport, PoCResult } from "./types.js";
 
3
 
4
  /**
5
  * Entry point para o Agente Gerador de PoCs.
@@ -7,19 +8,19 @@ import { VulnerabilityReport, PoCResult } from "./types.js";
7
  * @returns PoCResult contendo o código do exploit e o status da execução.
8
  */
9
  export async function runPoCGenerator(report: VulnerabilityReport): Promise<PoCResult> {
10
- console.log(`[runPoCGenerator] Iniciando para: ${report.id} — ${report.title}`);
11
 
12
  const finalState = await testerAgent.invoke({ report });
13
 
14
  const result: PoCResult = {
15
- reportId: report.id,
16
- status: finalState.status === "running" ? "failed" : finalState.status,
17
- solidityCode: finalState.pocCode,
18
  executionLogs: finalState.executionLogs,
19
- iterations: finalState.iterations,
20
  };
21
 
22
- console.log(`[runPoCGenerator] Concluído — status=${result.status}, iterações=${result.iterations}`);
23
  return result;
24
  }
25
 
 
1
  import { testerAgent } from "./agent.js";
2
  import { VulnerabilityReport, PoCResult } from "./types.js";
3
+ import { logger } from "../../logger.js";
4
 
5
  /**
6
  * Entry point para o Agente Gerador de PoCs.
 
8
  * @returns PoCResult contendo o código do exploit e o status da execução.
9
  */
10
  export async function runPoCGenerator(report: VulnerabilityReport): Promise<PoCResult> {
11
+ logger.info(`[Tester] runPoCGenerator: iniciando para: ${report.id} — ${report.title}`);
12
 
13
  const finalState = await testerAgent.invoke({ report });
14
 
15
  const result: PoCResult = {
16
+ reportId: report.id,
17
+ status: finalState.status === "running" ? "failed" : finalState.status,
18
+ solidityCode: finalState.pocCode,
19
  executionLogs: finalState.executionLogs,
20
+ iterations: finalState.iterations,
21
  };
22
 
23
+ logger.info(`[Tester] runPoCGenerator: concluído — status=${result.status}, iterações=${result.iterations}`);
24
  return result;
25
  }
26
 
src/agents/tester/tools/foundryRunner.ts CHANGED
@@ -3,7 +3,9 @@ import { promisify } from "util";
3
  import { writeFile, access } from "fs/promises";
4
  import { join } from "path";
5
 
6
- const execAsync = promisify(exec);
 
 
7
  const SANDBOX = process.env.SANDBOX_DIR || "/tmp/poc-sandbox";
8
  const TIMEOUT_MS = 60_000;
9
 
@@ -22,7 +24,7 @@ async function ensureSandbox() {
22
  try {
23
  await access(join(SANDBOX, "foundry.toml"));
24
  } catch {
25
- console.log("[foundryRunner] Sandbox não encontrado. Inicializando...");
26
  // Caminho absoluto para o script de setup (assume execução da raiz do projeto)
27
  await execAsync("./scripts/setup-sandbox.sh");
28
  }
@@ -30,19 +32,16 @@ async function ensureSandbox() {
30
 
31
  export async function runFoundry(solidityCode: string): Promise<FoundryResult> {
32
  await ensureSandbox();
33
-
34
  // Escrever o arquivo no sandbox
35
  await writeFile(`${SANDBOX}/test/Exploit.t.sol`, solidityCode, "utf-8");
36
 
37
  try {
38
- const { stdout, stderr } = await execAsync(
39
- "forge test --match-contract ExploitTest -vvvv",
40
- {
41
- cwd: SANDBOX,
42
- timeout: TIMEOUT_MS,
43
- env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` }
44
- }
45
- );
46
  return {
47
  exitCode: 0,
48
  stdout,
@@ -53,7 +52,9 @@ export async function runFoundry(solidityCode: string): Promise<FoundryResult> {
53
  } catch (err: any) {
54
  if (err.killed || err.signal === "SIGTERM") {
55
  return {
56
- exitCode: -1, stdout: "", stderr: "Forge timed out",
 
 
57
  combined: `TIMEOUT após ${TIMEOUT_MS / 1000}s`,
58
  timedOut: true,
59
  };
 
3
  import { writeFile, access } from "fs/promises";
4
  import { join } from "path";
5
 
6
+ import { logger } from "../../../logger.js";
7
+
8
+ const execAsync = promisify(exec);
9
  const SANDBOX = process.env.SANDBOX_DIR || "/tmp/poc-sandbox";
10
  const TIMEOUT_MS = 60_000;
11
 
 
24
  try {
25
  await access(join(SANDBOX, "foundry.toml"));
26
  } catch {
27
+ logger.info("[Tester] foundryRunner: sandbox não encontrado, inicializando...");
28
  // Caminho absoluto para o script de setup (assume execução da raiz do projeto)
29
  await execAsync("./scripts/setup-sandbox.sh");
30
  }
 
32
 
33
  export async function runFoundry(solidityCode: string): Promise<FoundryResult> {
34
  await ensureSandbox();
35
+
36
  // Escrever o arquivo no sandbox
37
  await writeFile(`${SANDBOX}/test/Exploit.t.sol`, solidityCode, "utf-8");
38
 
39
  try {
40
+ const { stdout, stderr } = await execAsync("forge test --match-contract ExploitTest -vvvv", {
41
+ cwd: SANDBOX,
42
+ timeout: TIMEOUT_MS,
43
+ env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` },
44
+ });
 
 
 
45
  return {
46
  exitCode: 0,
47
  stdout,
 
52
  } catch (err: any) {
53
  if (err.killed || err.signal === "SIGTERM") {
54
  return {
55
+ exitCode: -1,
56
+ stdout: "",
57
+ stderr: "Forge timed out",
58
  combined: `TIMEOUT após ${TIMEOUT_MS / 1000}s`,
59
  timedOut: true,
60
  };
src/config/llm.ts CHANGED
@@ -16,8 +16,6 @@ export function createLLM(overrideProvider?: LLMProvider): BaseChatModel {
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",
 
16
  apiKey: process.env.OPENROUTER_API_KEY,
17
  maxTokens: 4096,
18
  });
 
 
19
  case "anthropic":
20
  return new ChatAnthropic({
21
  model: process.env.ANTHROPIC_MODEL || "claude-sonnet-4-6",
src/logger.ts CHANGED
@@ -1,5 +1,6 @@
1
  import fs from "node:fs";
2
  import path from "node:path";
 
3
 
4
  import winston from "winston";
5
 
@@ -15,6 +16,27 @@ const lineFormat = printf(({ level, message, timestamp: ts, stack }) => {
15
  return stack ? `${base}\n${stack}` : base;
16
  });
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  export const logger = winston.createLogger({
19
  level: "debug",
20
  transports: [
@@ -25,5 +47,10 @@ export const logger = winston.createLogger({
25
  filename: path.join(logsDir, `app-${runTimestamp}.log`),
26
  format: combine(timestamp(), errors({ stack: true }), lineFormat),
27
  }),
 
 
 
 
 
28
  ],
29
  });
 
1
  import fs from "node:fs";
2
  import path from "node:path";
3
+ import { Writable } from "node:stream";
4
 
5
  import winston from "winston";
6
 
 
16
  return stack ? `${base}\n${stack}` : base;
17
  });
18
 
19
+ type LogSink = (message: string) => void | Promise<void>;
20
+
21
+ let activeSink: LogSink | null = null;
22
+
23
+ export function setLogSink(sink: LogSink): void {
24
+ activeSink = sink;
25
+ }
26
+
27
+ export function clearLogSink(): void {
28
+ activeSink = null;
29
+ }
30
+
31
+ const sinkStream = new Writable({
32
+ write(chunk: Buffer, _encoding: string, callback: () => void) {
33
+ if (activeSink) {
34
+ void activeSink(chunk.toString().trim());
35
+ }
36
+ callback();
37
+ },
38
+ });
39
+
40
  export const logger = winston.createLogger({
41
  level: "debug",
42
  transports: [
 
47
  filename: path.join(logsDir, `app-${runTimestamp}.log`),
48
  format: combine(timestamp(), errors({ stack: true }), lineFormat),
49
  }),
50
+ new winston.transports.Stream({
51
+ stream: sinkStream,
52
+ level: "info",
53
+ format: winston.format.printf(({ message }) => String(message)),
54
+ }),
55
  ],
56
  });
src/server.ts CHANGED
@@ -13,6 +13,7 @@ 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
 
@@ -35,16 +36,16 @@ app.post("/api/run", (c) => {
35
  };
36
 
37
  try {
 
 
38
  // === CODER ===
39
- await send("log", "[Coder] Gerando smart contract a partir dos requisitos...");
40
  const coderResult = await coderAgent.invoke({ requirements: [requirements] });
41
 
42
- await send("log", "[Coder] Contrato gerado com sucesso.");
43
-
44
  if (coderResult.compilationErrors.length > 0) {
45
- await send("log", `[Coder] Erros de compilação restantes: ${coderResult.compilationErrors.length}`);
46
  } else {
47
- await send("log", "[Coder] Contrato compilado sem erros.");
48
  }
49
 
50
  await send(
@@ -62,11 +63,11 @@ app.post("/api/run", (c) => {
62
  writeFileSync(resolve(outputDir, "Contract.sol"), coderResult.contract, "utf-8");
63
  writeFileSync(resolve(outputDir, "README.md"), requirements, "utf-8");
64
 
65
- await send("log", "[Auditor] Iniciando auditoria de segurança...");
66
  const auditorResult = await auditorAgent.invoke({ repoPath: outputDir });
67
- await send("log", `[Auditor] ${auditorResult.findings.length} vulnerabilidade(s) encontrada(s).`);
68
  for (const f of auditorResult.findings) {
69
- await send("log", `[Auditor] [${f.severity.toUpperCase()}] ${f.title} — ${f.location}`);
70
  }
71
 
72
  await send(
@@ -77,14 +78,14 @@ app.post("/api/run", (c) => {
77
  );
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
-
88
  // Garante que o objeto enviado tem exatamente o que o front espera
89
  await send(
90
  "tester",
@@ -96,15 +97,17 @@ app.post("/api/run", (c) => {
96
  }),
97
  );
98
  } else {
99
- await send("log", "[Tester] Nenhuma vulnerabilidade para testar.");
100
  await send("tester", JSON.stringify({ status: "skipped", iterations: 0 }));
101
  }
102
 
103
- await send("log", "Pipeline concluído.");
104
  await send("done", "ok");
105
  } catch (err) {
106
  const message = err instanceof Error ? err.message : String(err);
107
  await send("error", message);
 
 
108
  }
109
  });
110
  });
 
13
  import { auditorAgent } from "./agents/auditor/agent.ts";
14
  import { testerAgent } from "./agents/tester/agent.ts";
15
  import { mapFindingToReport } from "./utils/mapFinding.js";
16
+ import { logger, setLogSink, clearLogSink } from "./logger.ts";
17
 
18
  const app = new Hono();
19
 
 
36
  };
37
 
38
  try {
39
+ setLogSink((msg) => send("log", msg));
40
+
41
  // === CODER ===
42
+ logger.info("[Coder] Gerando smart contract a partir dos requisitos...");
43
  const coderResult = await coderAgent.invoke({ requirements: [requirements] });
44
 
 
 
45
  if (coderResult.compilationErrors.length > 0) {
46
+ logger.info(`[Coder] Erros de compilação restantes: ${coderResult.compilationErrors.length}`);
47
  } else {
48
+ logger.info("[Coder] Contrato compilado sem erros.");
49
  }
50
 
51
  await send(
 
63
  writeFileSync(resolve(outputDir, "Contract.sol"), coderResult.contract, "utf-8");
64
  writeFileSync(resolve(outputDir, "README.md"), requirements, "utf-8");
65
 
66
+ logger.info("[Auditor] Iniciando auditoria de segurança...");
67
  const auditorResult = await auditorAgent.invoke({ repoPath: outputDir });
68
+ logger.info(`[Auditor] ${auditorResult.findings.length} vulnerabilidade(s) encontrada(s).`);
69
  for (const f of auditorResult.findings) {
70
+ logger.info(`[Auditor] [${f.severity.toUpperCase()}] ${f.title} — ${f.location}`);
71
  }
72
 
73
  await send(
 
78
  );
79
 
80
  // === TESTER ===
81
+ logger.info("[Tester] Gerando testes de prova de conceito...");
82
 
83
  if (auditorResult.findings.length > 0) {
84
  const report = mapFindingToReport(auditorResult.findings[0], coderResult.contract);
85
  const testerResult = await testerAgent.invoke({ report });
86
 
87
+ logger.info(`[Tester] Execução concluída com status: ${testerResult.status}`);
88
+
89
  // Garante que o objeto enviado tem exatamente o que o front espera
90
  await send(
91
  "tester",
 
97
  }),
98
  );
99
  } else {
100
+ logger.info("[Tester] Nenhuma vulnerabilidade para testar.");
101
  await send("tester", JSON.stringify({ status: "skipped", iterations: 0 }));
102
  }
103
 
104
+ logger.info("Pipeline concluído.");
105
  await send("done", "ok");
106
  } catch (err) {
107
  const message = err instanceof Error ? err.message : String(err);
108
  await send("error", message);
109
+ } finally {
110
+ clearLogSink();
111
  }
112
  });
113
  });