Spaces:
Runtime error
Runtime error
Uanderson Silva commited on
Commit ·
01d948e
1
Parent(s): 7fb9975
lint and use logger
Browse files
src/agents/tester/nodes/context.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
| 1 |
import fs from "fs/promises";
|
| 2 |
import path from "path";
|
|
|
|
| 3 |
import { PoCState } from "../state.js";
|
| 4 |
import { logger } from "../../../logger.js";
|
| 5 |
|
| 6 |
export async function contextNode(state: PoCState): Promise<Partial<PoCState>> {
|
| 7 |
-
|
| 8 |
|
| 9 |
if (state.report.customSandboxDir) {
|
| 10 |
try {
|
|
@@ -18,16 +19,16 @@ export async function contextNode(state: PoCState): Promise<Partial<PoCState>> {
|
|
| 18 |
}
|
| 19 |
}
|
| 20 |
if (removed > 0) {
|
| 21 |
-
|
| 22 |
}
|
| 23 |
} catch (e) {
|
| 24 |
-
|
| 25 |
}
|
| 26 |
}
|
| 27 |
|
| 28 |
-
return {
|
| 29 |
-
templateCode: "",
|
| 30 |
-
pocCode: "",
|
| 31 |
-
infrastructurePhase: false
|
| 32 |
};
|
| 33 |
}
|
|
|
|
| 1 |
import fs from "fs/promises";
|
| 2 |
import path from "path";
|
| 3 |
+
|
| 4 |
import { PoCState } from "../state.js";
|
| 5 |
import { logger } from "../../../logger.js";
|
| 6 |
|
| 7 |
export async function contextNode(state: PoCState): Promise<Partial<PoCState>> {
|
| 8 |
+
logger.info(`[Tester] contextNode: Preparando ambiente de testes para: ${state.report.title}`);
|
| 9 |
|
| 10 |
if (state.report.customSandboxDir) {
|
| 11 |
try {
|
|
|
|
| 19 |
}
|
| 20 |
}
|
| 21 |
if (removed > 0) {
|
| 22 |
+
logger.info(`[Tester] contextNode: Limpos ${removed} arquivos de teste antigos.`);
|
| 23 |
}
|
| 24 |
} catch (e) {
|
| 25 |
+
logger.warn(`[Tester] contextNode: falha na limpeza do diretório de testes: ${(e as Error).message}`);
|
| 26 |
}
|
| 27 |
}
|
| 28 |
|
| 29 |
+
return {
|
| 30 |
+
templateCode: "",
|
| 31 |
+
pocCode: "",
|
| 32 |
+
infrastructurePhase: false,
|
| 33 |
};
|
| 34 |
}
|
src/agents/tester/nodes/pocoAgent.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { HumanMessage, SystemMessage, AIMessage, trimMessages } from "@langchain
|
|
| 2 |
import { PoCState } from "../state.js";
|
| 3 |
import { pocoTools } from "../tools.js";
|
| 4 |
import { createLLM } from "../../../config/llm.js";
|
| 5 |
-
import { emitStep } from "../../../logger.js";
|
| 6 |
|
| 7 |
const MAX_STEPS = 30; // Max tool calls threshold
|
| 8 |
const MAX_COST_USD = 3.0; // Max cost threshold
|
|
@@ -67,63 +67,66 @@ export async function pocoAgentNode(state: PoCState): Promise<Partial<PoCState>>
|
|
| 67 |
const sandboxDir = state.report.customSandboxDir || process.cwd();
|
| 68 |
const targetFile = state.report.affectedContract.sourceFilePath || state.report.affectedContract.name;
|
| 69 |
const desc = state.report.description || state.report.title;
|
| 70 |
-
|
| 71 |
// Original PoCo prompt
|
| 72 |
const taskPrompt = `Create a vulnerability exposing PoC forge test for the vulnerable contract at ${targetFile} using the vulnerability description: ${desc}. Use the write_file tool to save your PoC code to test/Exploit.t.sol. Write ONLY the test file, test ONLY the described vulnerability, and do NOT modify the original contract. Iterate on compilation, test, and logical errors using the smart_contract_compile and smart_contract_test tools. You are done when the test compiles and successfully demonstrates the vulnerability through passing assertions. Note: your execution sandbox is ${sandboxDir}. Ensure all commands target this directory.`;
|
| 73 |
|
| 74 |
-
initialMessages = [
|
| 75 |
-
new SystemMessage(POCO_SYSTEM_PROMPT),
|
| 76 |
-
new HumanMessage(taskPrompt)
|
| 77 |
-
];
|
| 78 |
messages = initialMessages;
|
| 79 |
}
|
| 80 |
|
| 81 |
// Invoke model
|
| 82 |
-
|
| 83 |
-
|
|
|
|
|
|
|
| 84 |
let runCost = 0;
|
| 85 |
-
|
| 86 |
let attempts = 0;
|
| 87 |
while (attempts < 3) {
|
| 88 |
try {
|
| 89 |
const trimmedMessages = await trimMessages(messages, {
|
| 90 |
maxTokens: 100000,
|
| 91 |
strategy: "last",
|
| 92 |
-
tokenCounter: (msgs) =>
|
|
|
|
| 93 |
includeSystem: true,
|
| 94 |
allowPartial: false,
|
| 95 |
});
|
| 96 |
|
| 97 |
response = await model.invoke(trimmedMessages, {
|
| 98 |
-
configurable: { sandboxDir: state.report.customSandboxDir || process.cwd() }
|
| 99 |
});
|
| 100 |
-
|
| 101 |
if (process.env.DEBUG_CONTEXT === "true") {
|
| 102 |
-
|
| 103 |
-
|
| 104 |
if (response.tool_calls) {
|
| 105 |
-
|
| 106 |
}
|
| 107 |
}
|
| 108 |
-
|
| 109 |
// Calculate costs
|
| 110 |
if (response.response_metadata?.tokenUsage) {
|
| 111 |
const usage: any = response.response_metadata.tokenUsage;
|
| 112 |
-
runCost = calculateCost(
|
|
|
|
|
|
|
|
|
|
| 113 |
}
|
| 114 |
break; // Success, exit retry loop
|
| 115 |
} catch (err: any) {
|
| 116 |
attempts++;
|
| 117 |
-
|
| 118 |
if (attempts >= 3) {
|
| 119 |
return {
|
| 120 |
messages: [new HumanMessage(`Model API Error after 3 attempts: ${err.message}.`)],
|
| 121 |
status: "failed",
|
| 122 |
-
lastError: err.message
|
| 123 |
};
|
| 124 |
}
|
| 125 |
// Wait 10 seconds before retrying (in case of strict rate limits)
|
| 126 |
-
await new Promise(r => setTimeout(r, 10000));
|
| 127 |
}
|
| 128 |
}
|
| 129 |
|
|
|
|
| 2 |
import { PoCState } from "../state.js";
|
| 3 |
import { pocoTools } from "../tools.js";
|
| 4 |
import { createLLM } from "../../../config/llm.js";
|
| 5 |
+
import { logger, emitStep } from "../../../logger.js";
|
| 6 |
|
| 7 |
const MAX_STEPS = 30; // Max tool calls threshold
|
| 8 |
const MAX_COST_USD = 3.0; // Max cost threshold
|
|
|
|
| 67 |
const sandboxDir = state.report.customSandboxDir || process.cwd();
|
| 68 |
const targetFile = state.report.affectedContract.sourceFilePath || state.report.affectedContract.name;
|
| 69 |
const desc = state.report.description || state.report.title;
|
| 70 |
+
|
| 71 |
// Original PoCo prompt
|
| 72 |
const taskPrompt = `Create a vulnerability exposing PoC forge test for the vulnerable contract at ${targetFile} using the vulnerability description: ${desc}. Use the write_file tool to save your PoC code to test/Exploit.t.sol. Write ONLY the test file, test ONLY the described vulnerability, and do NOT modify the original contract. Iterate on compilation, test, and logical errors using the smart_contract_compile and smart_contract_test tools. You are done when the test compiles and successfully demonstrates the vulnerability through passing assertions. Note: your execution sandbox is ${sandboxDir}. Ensure all commands target this directory.`;
|
| 73 |
|
| 74 |
+
initialMessages = [new SystemMessage(POCO_SYSTEM_PROMPT), new HumanMessage(taskPrompt)];
|
|
|
|
|
|
|
|
|
|
| 75 |
messages = initialMessages;
|
| 76 |
}
|
| 77 |
|
| 78 |
// Invoke model
|
| 79 |
+
logger.info(
|
| 80 |
+
`[Tester] pocoAgent: Invoking model (Steps: ${state.toolCallCount}/${MAX_STEPS}, Cost: $${state.totalCost.toFixed(2)})...`,
|
| 81 |
+
);
|
| 82 |
+
let response: any;
|
| 83 |
let runCost = 0;
|
| 84 |
+
|
| 85 |
let attempts = 0;
|
| 86 |
while (attempts < 3) {
|
| 87 |
try {
|
| 88 |
const trimmedMessages = await trimMessages(messages, {
|
| 89 |
maxTokens: 100000,
|
| 90 |
strategy: "last",
|
| 91 |
+
tokenCounter: (msgs) =>
|
| 92 |
+
msgs.map((m) => (m.content ? m.content.toString().length / 4 : 0)).reduce((a, b) => a + b, 0),
|
| 93 |
includeSystem: true,
|
| 94 |
allowPartial: false,
|
| 95 |
});
|
| 96 |
|
| 97 |
response = await model.invoke(trimmedMessages, {
|
| 98 |
+
configurable: { sandboxDir: state.report.customSandboxDir || process.cwd() },
|
| 99 |
});
|
| 100 |
+
|
| 101 |
if (process.env.DEBUG_CONTEXT === "true") {
|
| 102 |
+
logger.debug(`[Tester]\n--- Agent Response [Step ${state.toolCallCount}] ---`);
|
| 103 |
+
logger.debug(response.content);
|
| 104 |
if (response.tool_calls) {
|
| 105 |
+
logger.debug(`[Tester] Tool Calls: ${JSON.stringify(response.tool_calls, null, 2)}`);
|
| 106 |
}
|
| 107 |
}
|
| 108 |
+
|
| 109 |
// Calculate costs
|
| 110 |
if (response.response_metadata?.tokenUsage) {
|
| 111 |
const usage: any = response.response_metadata.tokenUsage;
|
| 112 |
+
runCost = calculateCost(
|
| 113 |
+
usage.promptTokens || usage.input_tokens || usage.prompt_tokens || 0,
|
| 114 |
+
usage.completionTokens || usage.output_tokens || usage.completion_tokens || 0,
|
| 115 |
+
);
|
| 116 |
}
|
| 117 |
break; // Success, exit retry loop
|
| 118 |
} catch (err: any) {
|
| 119 |
attempts++;
|
| 120 |
+
logger.warn(`[Tester] pocoAgent: API Error (attempt ${attempts}): ${err.message}`);
|
| 121 |
if (attempts >= 3) {
|
| 122 |
return {
|
| 123 |
messages: [new HumanMessage(`Model API Error after 3 attempts: ${err.message}.`)],
|
| 124 |
status: "failed",
|
| 125 |
+
lastError: err.message,
|
| 126 |
};
|
| 127 |
}
|
| 128 |
// Wait 10 seconds before retrying (in case of strict rate limits)
|
| 129 |
+
await new Promise((r) => setTimeout(r, 10000));
|
| 130 |
}
|
| 131 |
}
|
| 132 |
|
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 |
-
|
|
|
|
|
|
|
| 7 |
const DEFAULT_SANDBOX = process.env.SANDBOX_DIR || "/tmp/poc-sandbox";
|
| 8 |
const TIMEOUT_MS = 60_000;
|
| 9 |
|
|
@@ -22,7 +24,7 @@ async function ensureSandbox(sandboxDir: string) {
|
|
| 22 |
try {
|
| 23 |
await access(join(sandboxDir, "foundry.toml"));
|
| 24 |
} catch {
|
| 25 |
-
|
| 26 |
// Caminho absoluto para o script de setup (assume execução da raiz do projeto)
|
| 27 |
await execAsync("./scripts/setup-sandbox.sh", { env: { ...process.env, SANDBOX_DIR: sandboxDir } });
|
| 28 |
}
|
|
@@ -30,7 +32,7 @@ async function ensureSandbox(sandboxDir: string) {
|
|
| 30 |
|
| 31 |
export async function runFoundry(solidityCode: string, sandboxDir: string = DEFAULT_SANDBOX): Promise<FoundryResult> {
|
| 32 |
await ensureSandbox(sandboxDir);
|
| 33 |
-
|
| 34 |
// Ensure test directory exists
|
| 35 |
const testDir = join(sandboxDir, "test");
|
| 36 |
try {
|
|
@@ -38,20 +40,17 @@ export async function runFoundry(solidityCode: string, sandboxDir: string = DEFA
|
|
| 38 |
} catch {
|
| 39 |
await execAsync(`mkdir -p "${testDir}"`);
|
| 40 |
}
|
| 41 |
-
|
| 42 |
// Escrever o arquivo no sandbox
|
| 43 |
const testPath = join(testDir, "Exploit.t.sol");
|
| 44 |
await writeFile(testPath, solidityCode, "utf-8");
|
| 45 |
|
| 46 |
try {
|
| 47 |
-
const { stdout, stderr } = await execAsync(
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` }
|
| 53 |
-
}
|
| 54 |
-
);
|
| 55 |
return {
|
| 56 |
exitCode: 0,
|
| 57 |
stdout,
|
|
|
|
| 3 |
import { writeFile, access } from "fs/promises";
|
| 4 |
import { join } from "path";
|
| 5 |
|
| 6 |
+
import { logger } from "../../../logger.ts";
|
| 7 |
+
|
| 8 |
+
const execAsync = promisify(exec);
|
| 9 |
const DEFAULT_SANDBOX = process.env.SANDBOX_DIR || "/tmp/poc-sandbox";
|
| 10 |
const TIMEOUT_MS = 60_000;
|
| 11 |
|
|
|
|
| 24 |
try {
|
| 25 |
await access(join(sandboxDir, "foundry.toml"));
|
| 26 |
} catch {
|
| 27 |
+
logger.info(`[Tester] foundryRunner: Sandbox em ${sandboxDir} 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", { env: { ...process.env, SANDBOX_DIR: sandboxDir } });
|
| 30 |
}
|
|
|
|
| 32 |
|
| 33 |
export async function runFoundry(solidityCode: string, sandboxDir: string = DEFAULT_SANDBOX): Promise<FoundryResult> {
|
| 34 |
await ensureSandbox(sandboxDir);
|
| 35 |
+
|
| 36 |
// Ensure test directory exists
|
| 37 |
const testDir = join(sandboxDir, "test");
|
| 38 |
try {
|
|
|
|
| 40 |
} catch {
|
| 41 |
await execAsync(`mkdir -p "${testDir}"`);
|
| 42 |
}
|
| 43 |
+
|
| 44 |
// Escrever o arquivo no sandbox
|
| 45 |
const testPath = join(testDir, "Exploit.t.sol");
|
| 46 |
await writeFile(testPath, solidityCode, "utf-8");
|
| 47 |
|
| 48 |
try {
|
| 49 |
+
const { stdout, stderr } = await execAsync("forge test --match-contract ExploitTest -vvvv", {
|
| 50 |
+
cwd: sandboxDir,
|
| 51 |
+
timeout: TIMEOUT_MS,
|
| 52 |
+
env: { ...process.env, PATH: `${process.env.HOME}/.foundry/bin:${process.env.PATH}` },
|
| 53 |
+
});
|
|
|
|
|
|
|
|
|
|
| 54 |
return {
|
| 55 |
exitCode: 0,
|
| 56 |
stdout,
|