Uanderson Silva commited on
Commit
4c8dfec
Β·
1 Parent(s): 0f0b948

add agent phases

Browse files
src/agents/auditor/agent.ts CHANGED
@@ -1,20 +1,109 @@
 
1
  import { END, type GraphNode, START, StateGraph } from "@langchain/langgraph";
 
2
 
3
- import { AuditorState } from "./state.ts";
4
- import { slitherTool } from "./tools/slither-tool.ts";
 
 
 
 
 
5
 
6
- const PLACEHOLDER_VULNERABILITIES = [
7
- { type: "reentrancy", severity: "high", description: "Unchecked external call allows reentrancy attack." },
8
- { type: "integer-overflow", severity: "medium", description: "Arithmetic operation may overflow." },
9
- ];
10
 
11
- const auditContract: GraphNode<typeof AuditorState> = async (state) => {
12
- await slitherTool.invoke({ solidityFile: state.solidityFile });
13
- return { vulnerabilities: PLACEHOLDER_VULNERABILITIES };
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  };
15
 
16
  export const auditorAgent = new StateGraph(AuditorState)
17
- .addNode("auditContract", auditContract)
18
- .addEdge(START, "auditContract")
19
- .addEdge("auditContract", END)
 
 
 
 
 
 
 
 
 
 
20
  .compile();
 
1
+ import { HumanMessage, SystemMessage } from "@langchain/core/messages";
2
  import { END, type GraphNode, START, StateGraph } from "@langchain/langgraph";
3
+ import { z } from "zod";
4
 
5
+ import { auditorModel } from "./model.ts";
6
+ import {
7
+ CRITIC_FINDINGS_PROMPT,
8
+ FIND_VULNERABILITIES_PROMPT,
9
+ GATHER_CONTEXT_PROMPT,
10
+ } from "./prompts.ts";
11
+ import { AuditorState, CriticSchema, FindingSchema } from "./state.ts";
12
 
13
+ const MAX_REFLECTIONS = 3;
 
 
 
14
 
15
+ const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
16
+ const model = auditorModel.withStructuredOutput(z.object({ context: z.string() }));
17
+
18
+ const scopeNote =
19
+ state.scope.length > 0 ? `\nAudit scope (focus on these): ${state.scope.join(", ")}` : "";
20
+
21
+ const result = await model.invoke([
22
+ new SystemMessage(GATHER_CONTEXT_PROMPT),
23
+ new HumanMessage(`Analyze this smart contract:${scopeNote}\n\n${state.solidityFile}`),
24
+ ]);
25
+
26
+ return { repoContext: result.context };
27
+ };
28
+
29
+ const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
30
+ const model = auditorModel.withStructuredOutput(
31
+ z.object({ findings: z.array(FindingSchema) }),
32
+ );
33
+
34
+ let userMessage = `Contract:\n\n${state.solidityFile}\n\nProtocol Context:\n${state.repoContext}`;
35
+
36
+ if (state.criticReviews.length > 0) {
37
+ const feedback = state.criticReviews
38
+ .map(
39
+ (r) =>
40
+ `- "${r.findingTitle}": ${r.isFalsePositive ? "FALSE POSITIVE" : "TRUE POSITIVE"}\n Critic: ${r.review}`,
41
+ )
42
+ .join("\n");
43
+ userMessage += `\n\nCritic feedback from previous iteration (iteration ${state.reflectionCount}):\n${feedback}\n\nRevise your findings accordingly.`;
44
+ }
45
+
46
+ const result = await model.invoke([
47
+ new SystemMessage(FIND_VULNERABILITIES_PROMPT),
48
+ new HumanMessage(userMessage),
49
+ ]);
50
+
51
+ return { candidateFindings: result.findings };
52
+ };
53
+
54
+ const criticFindings: GraphNode<typeof AuditorState> = async (state) => {
55
+ if (state.candidateFindings.length === 0) {
56
+ return {
57
+ criticReviews: [],
58
+ findings: [],
59
+ reflectionCount: state.reflectionCount + 1,
60
+ };
61
+ }
62
+
63
+ const model = auditorModel.withStructuredOutput(
64
+ z.object({ reviews: z.array(CriticSchema) }),
65
+ );
66
+
67
+ const findingsText = state.candidateFindings
68
+ .map(
69
+ (f, i) =>
70
+ `[Finding ${i + 1}] ${f.title}\nSeverity: ${f.severity} | Auditor confidence: ${f.confidence}/100\nDescription: ${f.description}\nLocation: ${f.path} lines ${f.location}\nCode:\n\`\`\`solidity\n${f.codeSnippet}\n\`\`\`\nExploit paths:\n${f.exploitablePaths.map((p) => ` - ${p}`).join("\n")}`,
71
+ )
72
+ .join("\n\n---\n\n");
73
+
74
+ const result = await model.invoke([
75
+ new SystemMessage(CRITIC_FINDINGS_PROMPT),
76
+ new HumanMessage(
77
+ `Contract:\n\n${state.solidityFile}\n\nProtocol Context:\n${state.repoContext}\n\nCandidate Findings to Review:\n\n${findingsText}`,
78
+ ),
79
+ ]);
80
+
81
+ const reviewsByTitle = new Map(result.reviews.map((r) => [r.findingTitle.toLowerCase(), r]));
82
+
83
+ const confirmedFindings = state.candidateFindings.filter((f, i) => {
84
+ const review = reviewsByTitle.get(f.title.toLowerCase()) ?? result.reviews[i];
85
+ return review ? !review.isFalsePositive : true;
86
+ });
87
+
88
+ return {
89
+ criticReviews: result.reviews,
90
+ findings: confirmedFindings,
91
+ reflectionCount: state.reflectionCount + 1,
92
+ };
93
  };
94
 
95
  export const auditorAgent = new StateGraph(AuditorState)
96
+ .addNode("gatherContext", gatherContext)
97
+ .addNode("findVulnerabilities", findVulnerabilities)
98
+ .addNode("criticFindings", criticFindings)
99
+ .addEdge(START, "gatherContext")
100
+ .addEdge("gatherContext", "findVulnerabilities")
101
+ .addEdge("findVulnerabilities", "criticFindings")
102
+ .addConditionalEdges("criticFindings", (state) => {
103
+ const hasFalsePositives = state.criticReviews.some((r) => r.isFalsePositive);
104
+ if (hasFalsePositives && state.reflectionCount < MAX_REFLECTIONS) {
105
+ return "findVulnerabilities";
106
+ }
107
+ return END;
108
+ })
109
  .compile();
src/agents/auditor/prompts.ts ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export const GATHER_CONTEXT_PROMPT = `You are a smart contract security expert. Analyze the provided Solidity source code and produce a thorough protocol context that will guide vulnerability discovery.
2
+
3
+ Extract and clearly structure the following:
4
+
5
+ 1. **Invariants**: Conditions that must always hold (e.g., "total supply must equal sum of all balances", "contract ETH balance >= sum of all user deposits").
6
+
7
+ 2. **Design Assumptions**: What the protocol assumes about callers, external contracts, oracles, admin keys, and token behavior (e.g., "tokens are ERC-20 compliant", "admin is trusted", "no fee-on-transfer tokens").
8
+
9
+ 3. **Key Flows**: The main execution paths and state transitions (e.g., deposit β†’ mint shares β†’ updateRewards; withdraw β†’ burn shares β†’ transfer ETH).
10
+
11
+ 4. **Business Rules**: Access controls, fee structures, timelocks, caps, pausing mechanisms, and any other domain constraints.
12
+
13
+ Be precise and exhaustive β€” the richer the context, the more accurately vulnerabilities can be identified and validated.`;
14
+
15
+ export const FIND_VULNERABILITIES_PROMPT = `You are an expert smart contract security auditor specializing in Solidity. Systematically analyze the contract source code and protocol context to identify security vulnerabilities.
16
+
17
+ For each vulnerability provide ALL of the following fields:
18
+
19
+ - **title**: Short, precise name (e.g., "Reentrancy in withdraw", "Missing access control on setFee").
20
+ - **description**: Explain the EXPECTED behavior vs the OBSERVED (vulnerable) behavior in 2–4 sentences.
21
+ - **recommendation**: Specific, actionable remediation (e.g., "Apply checks-effects-interactions pattern", "Add onlyOwner modifier").
22
+ - **severity**: One of "high" (direct fund loss or contract takeover), "medium" (indirect or conditional risk), "low" (best-practice issue, no immediate financial risk).
23
+ - **confidence**: Integer 0–100 reflecting your confidence this is a real, exploitable vulnerability.
24
+ - **codeSnippet**: The exact vulnerable code block as it appears in the source.
25
+ - **location**: Line range as "start-end" (e.g., "42-58"). Use "unknown" if lines are not determinable.
26
+ - **path**: File path of the vulnerable contract. Use the contract name if a single inline source is provided.
27
+ - **exploitablePaths**: Array of one or more concrete exploit traces. Each trace must describe the attacker steps with realistic inputs/values (e.g., "1. Attacker calls deposit(100 ETH) 2. Attacker contract fallback re-enters withdraw() before balance update 3. Attacker drains 100 ETH twice").
28
+
29
+ Vulnerability categories to systematically check: reentrancy (single- and cross-function), access control, integer overflow/underflow, oracle manipulation, flash loan attacks, front-running/MEV, signature replay, storage collisions, uninitialized proxies, unsafe delegatecall, gas griefing, denial of service, precision loss, and logic/business rule violations.
30
+
31
+ If critic feedback is provided from a previous iteration, remove confirmed false positives from your list and refine or expand remaining findings based on the critique.`;
32
+
33
+ export const CRITIC_FINDINGS_PROMPT = `You are a rigorous smart contract security reviewer. Evaluate each candidate vulnerability submitted by the auditor and determine whether it is a true positive or a false positive.
34
+
35
+ For each finding provide ALL of the following fields:
36
+
37
+ - **findingTitle**: Must match exactly the title of the finding you are reviewing.
38
+ - **review**: Detailed analysis (3–6 sentences) explaining why the vulnerability is or isn't real. Reference specific code, protocol invariants, preconditions, and mitigating controls.
39
+ - **isFalsePositive**: true if the finding is NOT exploitable in practice; false if it IS a real vulnerability.
40
+ - **confidence**: Integer 0–100 reflecting your confidence in this verdict.
41
+ - **exploitablePaths**: If a true positive, provide concrete paths confirming exploitability with real values. If a false positive, provide the reasoning that blocks the exploit.
42
+
43
+ A finding is a false positive if and only if: the exploit path is unreachable given access controls or preconditions, it is already fully mitigated by the code, it requires impossible or economically infeasible conditions, or it is explicitly documented as by-design behavior in the protocol assumptions.
44
+
45
+ You must provide exactly one review object per finding, in the same order as the findings were presented.`;
src/agents/auditor/state.ts CHANGED
@@ -1,7 +1,32 @@
1
  import { StateSchema } from "@langchain/langgraph";
2
  import { z } from "zod";
3
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  export const AuditorState = new StateSchema({
5
  solidityFile: z.string().default(""),
6
- vulnerabilities: z.array(z.record(z.string(), z.any())).default([]),
 
 
 
 
 
7
  });
 
1
  import { StateSchema } from "@langchain/langgraph";
2
  import { z } from "zod";
3
 
4
+ export const FindingSchema = z.object({
5
+ title: z.string(),
6
+ description: z.string(),
7
+ recommendation: z.string(),
8
+ severity: z.enum(["high", "medium", "low"]),
9
+ confidence: z.number(),
10
+ codeSnippet: z.string(),
11
+ location: z.string(),
12
+ path: z.string(),
13
+ exploitablePaths: z.array(z.string()),
14
+ });
15
+
16
+ export const CriticSchema = z.object({
17
+ findingTitle: z.string(),
18
+ review: z.string(),
19
+ isFalsePositive: z.boolean(),
20
+ confidence: z.number(),
21
+ exploitablePaths: z.array(z.string()),
22
+ });
23
+
24
  export const AuditorState = new StateSchema({
25
  solidityFile: z.string().default(""),
26
+ scope: z.array(z.string()).default([]),
27
+ repoContext: z.string().default(""),
28
+ candidateFindings: z.array(FindingSchema).default([]),
29
+ criticReviews: z.array(CriticSchema).default([]),
30
+ findings: z.array(FindingSchema).default([]),
31
+ reflectionCount: z.number().default(0),
32
  });
src/index.ts CHANGED
@@ -12,11 +12,11 @@ console.log(coderResult.contract);
12
 
13
  const auditorResult = await auditorAgent.invoke({ solidityFile: coderResult.contract });
14
  console.log("\n======= Auditor =======");
15
- console.log(auditorResult.vulnerabilities);
16
 
17
  const testerResult = await testerAgent.invoke({
18
  solidityFiles: [coderResult.contract],
19
- vulnerability: auditorResult.vulnerabilities[0] ?? {},
20
  });
21
  console.log("\n======= Tester =======");
22
  console.log(testerResult.results);
 
12
 
13
  const auditorResult = await auditorAgent.invoke({ solidityFile: coderResult.contract });
14
  console.log("\n======= Auditor =======");
15
+ console.log(auditorResult.findings);
16
 
17
  const testerResult = await testerAgent.invoke({
18
  solidityFiles: [coderResult.contract],
19
+ vulnerability: auditorResult.findings[0] ?? {},
20
  });
21
  console.log("\n======= Tester =======");
22
  console.log(testerResult.results);