Spaces:
Runtime error
Runtime error
Tales-Cunha commited on
Commit ·
3f143e9
1
Parent(s): 15ab6b4
feat: integrate the agents
Browse files- Dockerfile +12 -0
- frontend/src/App.tsx +36 -7
- scripts/setup-sandbox.sh +6 -2
- src/agents/auditor/agent.ts +32 -10
- src/agents/auditor/prompts.ts +6 -2
- src/agents/tester/agent.ts +9 -5
- src/config/llm.ts +10 -5
- src/index.ts +1 -27
- src/server.ts +19 -11
- src/utils/mapFinding.ts +32 -0
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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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.
|
| 224 |
-
<
|
| 225 |
-
{
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 125 |
-
const result = await
|
|
|
|
|
|
|
| 126 |
|
| 127 |
-
logger.info(`gatherContext: context built (${
|
| 128 |
-
logger.debug(`gatherContext: full context:\n${
|
| 129 |
|
| 130 |
-
return { repoContext
|
| 131 |
};
|
| 132 |
|
| 133 |
const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
|
| 134 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
| 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(`[
|
| 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("[
|
| 69 |
return { pocCode: solidityCode, iterations: 1 };
|
| 70 |
} catch (err) {
|
| 71 |
-
console.error("[
|
| 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(`[
|
|
|
|
|
|
|
|
|
|
| 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 || "
|
| 15 |
temperature: 0.2,
|
| 16 |
-
|
| 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 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 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 |
+
}
|