Spaces:
Runtime error
Runtime error
| import "dotenv/config"; | |
| import { mkdirSync, writeFileSync, readFileSync } from "node:fs"; | |
| import { resolve } from "node:path"; | |
| import { tmpdir } from "node:os"; | |
| import { serve } from "@hono/node-server"; | |
| import { Hono } from "hono"; | |
| import { cors } from "hono/cors"; | |
| import { streamSSE } from "hono/streaming"; | |
| import { serveStatic } from "@hono/node-server/serve-static"; | |
| import { coderAgent } from "./agents/coder/agent.ts"; | |
| import { auditorAgent } from "./agents/auditor/agent.ts"; | |
| import { testerAgent } from "./agents/tester/agent.ts"; | |
| import { mapFindingToReport } from "./utils/mapFinding.js"; | |
| import { createEmptyFoundryProject } from "./utils/forgeSandbox.js"; | |
| import { logger, setLogSink, clearLogSink, setStepSink, clearStepSink } from "./logger.ts"; | |
| const app = new Hono(); | |
| app.use("/api/*", cors()); | |
| app.post("/api/run", (c) => { | |
| return streamSSE(c, async (stream) => { | |
| const body = await c.req.json<{ requirements: string }>(); | |
| const requirements = body.requirements?.trim(); | |
| if (!requirements) { | |
| await stream.writeSSE({ event: "error", data: "Requisitos não fornecidos." }); | |
| return; | |
| } | |
| let eventId = 0; | |
| const send = async (event: string, data: string) => { | |
| await stream.writeSSE({ id: String(eventId++), event, data }); | |
| }; | |
| try { | |
| setLogSink((msg) => send("log", msg)); | |
| setStepSink((event) => send("step", JSON.stringify(event))); | |
| // === CODER === | |
| logger.info("[Coder] Gerando smart contract a partir dos requisitos..."); | |
| const coderResult = await coderAgent.invoke({ requirements: [requirements] }); | |
| if (coderResult.compilationErrors.length > 0) { | |
| logger.info(`[Coder] Erros de compilação restantes: ${coderResult.compilationErrors.length}`); | |
| } else { | |
| logger.info("[Coder] Contrato compilado sem erros."); | |
| } | |
| await send( | |
| "coder", | |
| JSON.stringify({ | |
| contract: coderResult.contract, | |
| compilationErrors: coderResult.compilationErrors, | |
| reviewSummary: coderResult.reviewSummary, | |
| }), | |
| ); | |
| // === AUDITOR === | |
| const outputDir = resolve(tmpdir(), `talp1-${Date.now()}`); | |
| mkdirSync(outputDir, { recursive: true }); | |
| writeFileSync(resolve(outputDir, "Contract.sol"), coderResult.contract, "utf-8"); | |
| writeFileSync(resolve(outputDir, "README.md"), requirements, "utf-8"); | |
| logger.info("[Auditor] Iniciando auditoria de segurança..."); | |
| const auditorResult = await auditorAgent.invoke({ repoPath: outputDir }); | |
| logger.info(`[Auditor] ${auditorResult.findings.length} vulnerabilidade(s) encontrada(s).`); | |
| for (const f of auditorResult.findings) { | |
| logger.info(`[Auditor] [${f.severity.toUpperCase()}] ${f.title} — ${f.location}`); | |
| } | |
| await send( | |
| "auditor", | |
| JSON.stringify({ | |
| findings: auditorResult.findings, | |
| }), | |
| ); | |
| // === TESTER === | |
| logger.info("[Tester] Gerando testes de prova de conceito..."); | |
| if (auditorResult.findings.length > 0) { | |
| const report = mapFindingToReport( | |
| auditorResult.findings[0], | |
| coderResult.contract, | |
| auditorResult.repoContext // ← now forwarded to tester | |
| ); | |
| const sandboxDir = resolve(tmpdir(), `talp1-tester-${Date.now()}`); | |
| await createEmptyFoundryProject(sandboxDir, coderResult.contract, "Contract"); | |
| report.customSandboxDir = sandboxDir; // ← tester runs in real project sandbox | |
| report.affectedContract.sourceFilePath = "src/Contract.sol"; // Fix bug with relative path | |
| const testerResult = await testerAgent.invoke( | |
| { report }, | |
| { recursionLimit: 100, configurable: { sandboxDir } } | |
| ); | |
| logger.info(`[Tester] Execução concluída com status: ${testerResult.status}`); | |
| let finalPocCode = testerResult.pocCode || ""; | |
| try { | |
| finalPocCode = readFileSync(resolve(sandboxDir, "test/Exploit.t.sol"), "utf-8"); | |
| } catch (e) { | |
| // Ignorar se não criou | |
| } | |
| let finalExecutionLogs: string[] = testerResult.executionLogs || []; | |
| if (finalExecutionLogs.length === 0 && testerResult.messages) { | |
| const testMsgs = testerResult.messages.filter((m: any) => m._getType() === "tool" && m.name === "smart_contract_test"); | |
| if (testMsgs.length > 0) { | |
| const content = testMsgs[testMsgs.length - 1].content; | |
| finalExecutionLogs = [typeof content === "string" ? content : JSON.stringify(content)]; | |
| } | |
| } | |
| // Garante que o objeto enviado tem exatamente o que o front espera | |
| await send( | |
| "tester", | |
| JSON.stringify({ | |
| status: testerResult.status, | |
| pocCode: finalPocCode, | |
| executionLogs: finalExecutionLogs, | |
| iterations: testerResult.iterations, | |
| }), | |
| ); | |
| } else { | |
| logger.info("[Tester] Nenhuma vulnerabilidade para testar."); | |
| await send("tester", JSON.stringify({ status: "skipped", iterations: 0 })); | |
| } | |
| logger.info("Pipeline concluído."); | |
| await send("done", "ok"); | |
| } catch (err) { | |
| const message = err instanceof Error ? err.message : String(err); | |
| await send("error", message); | |
| } finally { | |
| clearLogSink(); | |
| clearStepSink(); | |
| } | |
| }); | |
| }); | |
| // Serve static frontend files (built React app) | |
| app.use("/*", serveStatic({ root: "./frontend/dist" })); | |
| const port = Number(process.env.PORT) || 7860; | |
| console.log(`Servidor rodando em http://localhost:${port}`); | |
| serve({ fetch: app.fetch, port }); | |