Uanderson Silva commited on
Commit
881b356
·
1 Parent(s): 52c3560

adjust audit schemas

Browse files
src/agents/auditor/agent.ts CHANGED
@@ -8,7 +8,7 @@ import { z } from "zod";
8
  import { logger } from "../../logger.ts";
9
  import { judgeFindingsModel, findVulnerabilitiesModel, gatherContextModel } from "./model.ts";
10
  import { JUDGE_FINDINGS_PROMPT, FIND_VULNERABILITIES_PROMPT, GATHER_CONTEXT_PROMPT } from "./prompts.ts";
11
- import { AuditorState, ReviewSchema, PartialFindingSchema } from "./state.ts";
12
  import { analyzeSolidityFile } from "./tools/solidity-analyzer-tool.ts";
13
  import { buildRepoTree } from "./tools/repo-tree-tool.ts";
14
  import {
@@ -91,11 +91,6 @@ const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
91
  solidityEntries.push({ filePath, source, analysis });
92
  }
93
 
94
- // Concatenate all sources for downstream vulnerability phases
95
- const solidityFile = solidityEntries
96
- .map(({ filePath, source }) => `// === FILE: ${filePath} ===\n${source}`)
97
- .join("\n\n");
98
-
99
  // Read documentation files
100
  const docEntries: { filePath: string; content: string }[] = [];
101
  for (const filePath of state.docs) {
@@ -123,38 +118,61 @@ const gatherContext: GraphNode<typeof AuditorState> = async (state) => {
123
  parts.push(`### ${filePath}\n\`\`\`solidity\n${source}\n\`\`\``);
124
  }
125
 
126
- // const model = gatherContextModel.withStructuredOutput(z.object({ context: z.string() }));
127
- // const result = await model.invoke([new SystemMessage(GATHER_CONTEXT_PROMPT), new HumanMessage(parts.join("\n\n"))]);
128
 
129
  logger.info(`gatherContext: context built (${parts.join("\n\n").length} chars)`);
130
  logger.debug(`gatherContext: full context:\n${parts.join("\n\n")}`);
131
 
132
- return { solidityFile, repoContext: parts.join("\n\n") };
133
  };
134
 
135
  const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
136
- const model = findVulnerabilitiesModel.withStructuredOutput(z.object({ findings: z.array(PartialFindingSchema) }));
137
-
138
- let userMessage = `Contract:\n\n${state.solidityFile}\n\nProtocol Context:\n${state.repoContext}`;
139
-
140
- if (state.judgeReviews.length > 0) {
141
- const feedback = state.judgeReviews
142
- .map(
143
- (r) => `- "${r /*.title*/}": ${r.isFalsePositive ? "FALSE POSITIVE" : "TRUE POSITIVE"}\n Judge: ${r.review}`,
144
- )
145
- .join("\n");
146
- userMessage += `\n\nJudge feedback from previous iteration (iteration ${state.reflectionCount}):\n${feedback}\n\nRevise your findings accordingly.`;
147
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
148
 
149
- logger.info(`findVulnerabilities: invoking LLM (iteration ${state.reflectionCount + 1})`);
150
- logger.debug(`findVulnerabilities: user message:\n${userMessage}`);
 
 
151
 
152
- const result = await model.invoke([new SystemMessage(FIND_VULNERABILITIES_PROMPT), new HumanMessage(userMessage)]);
 
 
 
 
 
 
 
153
 
154
- logger.info(`findVulnerabilities: LLM returned ${result.findings.length} candidate finding(s)`);
155
- logger.debug(`findVulnerabilities: findings:\n${JSON.stringify(result.findings, null, 2)}`);
 
156
 
157
- return { candidateFindings: result.findings.map((finding: any) => ({ ...finding, path: "/", location: "1-14" })) };
158
  };
159
 
160
  const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
@@ -167,39 +185,52 @@ const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
167
  };
168
  }
169
 
170
- const model = findVulnerabilitiesModel.withStructuredOutput(z.object({ reviews: z.array(ReviewSchema) }));
 
 
 
 
 
 
 
 
 
 
 
171
 
172
- const findingsText = state.candidateFindings
173
- .map(
174
- (f, i) =>
175
- `[Finding ${i + 1}] ${f.title}\nSeverity: ${f.severity}\nDescription: ${f.description}\nLocation: ${f.path} lines ${f.location}\nCode:\n\`\`\`solidity\n${f.codeSnippet}\n\`\`\``,
176
- )
177
- .join("\n\n---\n\n");
178
 
179
- logger.info(`judgeFindings: reviewing ${state.candidateFindings.length} candidate finding(s)`);
180
- logger.debug(`judgeFindings: findings text:\n${findingsText}`);
 
 
 
 
 
 
 
181
 
182
- const result = await model.invoke([
183
- new SystemMessage(JUDGE_FINDINGS_PROMPT),
184
- new HumanMessage(
185
- `Contract:\n\n${state.solidityFile}\n\nProtocol Context:\n${state.repoContext}\n\nCandidate Findings to Review:\n\n${findingsText}`,
186
- ),
187
- ]);
188
 
189
- const reviewsByTitle = new Map(result.reviews.map((r: any) => [r.findingTitle.toLowerCase(), r]));
 
 
 
 
 
 
 
190
 
191
- const confirmedFindings = state.candidateFindings.filter((f, i) => {
192
- const review = reviewsByTitle.get(f.title.toLowerCase()) ?? result.reviews[i];
193
- return review ? !review.isFalsePositive : true;
194
- });
195
 
196
- const falsePositiveCount = state.candidateFindings.length - confirmedFindings.length;
197
- logger.info(`judgeFindings: ${confirmedFindings.length} confirmed, ${falsePositiveCount} false positive(s)`);
198
- logger.debug(`judgeFindings: reviews:\n${JSON.stringify(result.reviews, null, 2)}`);
199
 
200
  return {
201
- judgeReviews: result.reviews,
202
- findings: confirmedFindings,
203
  reflectionCount: state.reflectionCount + 1,
204
  };
205
  };
 
8
  import { logger } from "../../logger.ts";
9
  import { judgeFindingsModel, findVulnerabilitiesModel, gatherContextModel } from "./model.ts";
10
  import { JUDGE_FINDINGS_PROMPT, FIND_VULNERABILITIES_PROMPT, GATHER_CONTEXT_PROMPT } from "./prompts.ts";
11
+ import { AuditorState, JudgeReviewSchema, CandidateFindingSchema } from "./state.ts";
12
  import { analyzeSolidityFile } from "./tools/solidity-analyzer-tool.ts";
13
  import { buildRepoTree } from "./tools/repo-tree-tool.ts";
14
  import {
 
91
  solidityEntries.push({ filePath, source, analysis });
92
  }
93
 
 
 
 
 
 
94
  // Read documentation files
95
  const docEntries: { filePath: string; content: string }[] = [];
96
  for (const filePath of state.docs) {
 
118
  parts.push(`### ${filePath}\n\`\`\`solidity\n${source}\n\`\`\``);
119
  }
120
 
121
+ const model = gatherContextModel.withStructuredOutput(z.object({ context: z.string() }));
122
+ const result = await model.invoke([new SystemMessage(GATHER_CONTEXT_PROMPT), new HumanMessage(parts.join("\n\n"))]);
123
 
124
  logger.info(`gatherContext: context built (${parts.join("\n\n").length} chars)`);
125
  logger.debug(`gatherContext: full context:\n${parts.join("\n\n")}`);
126
 
127
+ return { repoContext: result.context };
128
  };
129
 
130
  const findVulnerabilities: GraphNode<typeof AuditorState> = async (state) => {
131
+ const model = findVulnerabilitiesModel.withStructuredOutput(z.object({ findings: z.array(CandidateFindingSchema) }));
132
+
133
+ const previousFeedback =
134
+ state.judgeReviews.length > 0
135
+ ? state.judgeReviews
136
+ .map((r, i) => {
137
+ const title = state.candidateFindings[i]?.title ?? `Finding ${i + 1}`;
138
+ return `- "${title}": ${r.isFalsePositive ? "FALSE POSITIVE" : "TRUE POSITIVE"}\n Judge: ${r.review}`;
139
+ })
140
+ .join("\n")
141
+ : null;
142
+
143
+ logger.info(
144
+ `findVulnerabilities: invoking LLM for ${state.scope.length} file(s) in parallel (iteration ${state.reflectionCount + 1})`,
145
+ );
146
+
147
+ const allFindings = await Promise.all(
148
+ state.scope.map(async (filePath) => {
149
+ let source: string;
150
+ try {
151
+ source = fs.readFileSync(filePath, "utf-8").slice(0, MAX_SOL_CHARS);
152
+ } catch {
153
+ return [];
154
+ }
155
+ if (!source) return [];
156
 
157
+ let userMessage = `Contract (${filePath}):\n\n${source}\n\nProtocol Context:\n${state.repoContext}`;
158
+ if (previousFeedback) {
159
+ userMessage += `\n\nJudge feedback from previous iteration (iteration ${state.reflectionCount}):\n${previousFeedback}\n\nRevise your findings accordingly.`;
160
+ }
161
 
162
+ logger.debug(`findVulnerabilities: processing ${filePath}`);
163
+ const result = await model.invoke([
164
+ new SystemMessage(FIND_VULNERABILITIES_PROMPT),
165
+ new HumanMessage(userMessage),
166
+ ]);
167
+ return result.findings.map((finding: any) => ({ ...finding, path: filePath, location: "1-14" }));
168
+ }),
169
+ );
170
 
171
+ const candidateFindings = allFindings.flat();
172
+ logger.info(`findVulnerabilities: LLM returned ${candidateFindings.length} total candidate finding(s)`);
173
+ logger.debug(`findVulnerabilities: findings:\n${JSON.stringify(candidateFindings, null, 2)}`);
174
 
175
+ return { candidateFindings };
176
  };
177
 
178
  const judgeFindings: GraphNode<typeof AuditorState> = async (state) => {
 
185
  };
186
  }
187
 
188
+ const model = judgeFindingsModel.withStructuredOutput(JudgeReviewSchema);
189
+
190
+ logger.info(`judgeFindings: reviewing ${state.candidateFindings.length} candidate finding(s) in parallel`);
191
+
192
+ const reviews = await Promise.all(
193
+ state.candidateFindings.map(async (finding, i) => {
194
+ let source: string;
195
+ try {
196
+ source = fs.readFileSync(finding.path, "utf-8").slice(0, MAX_SOL_CHARS);
197
+ } catch {
198
+ source = "";
199
+ }
200
 
201
+ 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\`\`\``;
 
 
 
 
 
202
 
203
+ logger.debug(`judgeFindings: reviewing finding ${i + 1}: ${finding.title}`);
204
+ return model.invoke([
205
+ new SystemMessage(JUDGE_FINDINGS_PROMPT),
206
+ new HumanMessage(
207
+ `Contract (${finding.path}):\n\n${source}\n\nProtocol Context:\n${state.repoContext}\n\nFinding to Review:\n\n${findingText}`,
208
+ ),
209
+ ]);
210
+ }),
211
+ );
212
 
213
+ const confirmedEntries = state.candidateFindings
214
+ .map((finding, i) => ({ finding, review: reviews[i] }))
215
+ .filter(({ review }) => !review.isFalsePositive);
 
 
 
216
 
217
+ const findings = confirmedEntries.map(({ finding, review }) => ({
218
+ ...finding,
219
+ judgeReview: {
220
+ review: review.review,
221
+ confidence: review.confidence,
222
+ exploitablePaths: review.exploitablePaths,
223
+ },
224
+ }));
225
 
226
+ const falsePositiveCount = state.candidateFindings.length - findings.length;
 
 
 
227
 
228
+ logger.info(`judgeFindings: ${findings.length} confirmed, ${falsePositiveCount} false positive(s)`);
229
+ logger.debug(`judgeFindings: reviews:\n${JSON.stringify(reviews, null, 2)}`);
 
230
 
231
  return {
232
+ judgeReviews: reviews,
233
+ findings,
234
  reflectionCount: state.reflectionCount + 1,
235
  };
236
  };
src/agents/auditor/config.ts CHANGED
@@ -20,4 +20,4 @@ export const SKIP_DIRS = new Set([
20
  export const MAX_DEPTH = 6;
21
  export const MAX_DOC_CHARS = 12_000;
22
  export const MAX_SOL_CHARS = 40_000;
23
- export const MAX_REFLECTIONS = 3;
 
20
  export const MAX_DEPTH = 6;
21
  export const MAX_DOC_CHARS = 12_000;
22
  export const MAX_SOL_CHARS = 40_000;
23
+ export const MAX_REFLECTIONS = 1;
src/agents/auditor/model.ts CHANGED
@@ -5,9 +5,9 @@ export const gatherContextModel = new ChatAnthropic({
5
  });
6
 
7
  export const findVulnerabilitiesModel = new ChatAnthropic({
8
- model: "claude-sonnet-4-6",
9
  });
10
 
11
  export const judgeFindingsModel = new ChatAnthropic({
12
- model: "claude-sonnet-4-6",
13
  });
 
5
  });
6
 
7
  export const findVulnerabilitiesModel = new ChatAnthropic({
8
+ model: "claude-haiku-4-5",
9
  });
10
 
11
  export const judgeFindingsModel = new ChatAnthropic({
12
+ model: "claude-haiku-4-5",
13
  });
src/agents/auditor/state.ts CHANGED
@@ -1,7 +1,7 @@
1
  import { StateSchema } from "@langchain/langgraph";
2
  import { z } from "zod";
3
 
4
- export const PartialFindingSchema = z.object({
5
  title: z.string(),
6
  description: z.string(),
7
  recommendation: z.string(),
@@ -9,32 +9,34 @@ export const PartialFindingSchema = z.object({
9
  codeSnippet: z.string(),
10
  });
11
 
12
- export const FindingSchema = z.object({
13
- title: z.string(),
14
- description: z.string(),
15
- recommendation: z.string(),
16
- severity: z.enum(["high", "medium", "low"]),
17
- codeSnippet: z.string(),
18
  location: z.string(),
19
  path: z.string(),
20
  });
21
 
22
- export const ReviewSchema = z.object({
23
  review: z.string(),
24
  isFalsePositive: z.boolean(),
25
  confidence: z.number(),
26
  exploitablePaths: z.array(z.string()),
27
  });
28
 
 
 
 
 
 
 
 
 
29
  export const AuditorState = new StateSchema({
30
  repoPath: z.string().default(""),
31
- solidityFile: z.string().default(""),
32
  scope: z.array(z.string()).default([]),
33
  docs: z.array(z.string()).default([]),
34
  fileTree: z.string().default(""),
35
  repoContext: z.string().default(""),
36
- candidateFindings: z.array(FindingSchema).default([]),
37
- judgeReviews: z.array(ReviewSchema).default([]),
38
  findings: z.array(FindingSchema).default([]),
39
  reflectionCount: z.number().default(0),
40
  });
 
1
  import { StateSchema } from "@langchain/langgraph";
2
  import { z } from "zod";
3
 
4
+ export const CandidateFindingSchema = z.object({
5
  title: z.string(),
6
  description: z.string(),
7
  recommendation: z.string(),
 
9
  codeSnippet: z.string(),
10
  });
11
 
12
+ export const LocatedFindingSchema = CandidateFindingSchema.extend({
 
 
 
 
 
13
  location: z.string(),
14
  path: z.string(),
15
  });
16
 
17
+ export const JudgeReviewSchema = z.object({
18
  review: z.string(),
19
  isFalsePositive: z.boolean(),
20
  confidence: z.number(),
21
  exploitablePaths: z.array(z.string()),
22
  });
23
 
24
+ export const FindingSchema = LocatedFindingSchema.extend({
25
+ judgeReview: z.object({
26
+ review: z.string(),
27
+ confidence: z.number(),
28
+ exploitablePaths: z.array(z.string()),
29
+ }),
30
+ });
31
+
32
  export const AuditorState = new StateSchema({
33
  repoPath: z.string().default(""),
 
34
  scope: z.array(z.string()).default([]),
35
  docs: z.array(z.string()).default([]),
36
  fileTree: z.string().default(""),
37
  repoContext: z.string().default(""),
38
+ candidateFindings: z.array(LocatedFindingSchema).default([]),
39
+ judgeReviews: z.array(JudgeReviewSchema).default([]),
40
  findings: z.array(FindingSchema).default([]),
41
  reflectionCount: z.number().default(0),
42
  });
src/index.ts CHANGED
@@ -1,22 +1,35 @@
1
- import "dotenv/config";
2
 
3
- import { auditorAgent } from "./agents/auditor/agent.ts";
4
- import { coderAgent } from "./agents/coder/agent.ts";
5
- import { testerAgent } from "./agents/tester/agent.ts";
 
 
 
 
 
 
6
 
7
- const requirements = ["ERC20 token", "pausable", "ownable"];
 
 
8
 
9
- const coderResult = await coderAgent.invoke({ requirements });
10
- console.log("======= Coder =======");
11
- console.log(coderResult.contract);
 
 
 
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);
 
 
1
+ // import "dotenv/config";
2
 
3
+ // import { auditorAgent } from "./agents/auditor/agent.ts";
4
+ // import { coderAgent } from "./agents/coder/agent.ts";
5
+ // import { testerAgent } from "./agents/tester/agent.ts";
6
+
7
+ // const requirements = ["ERC20 token", "pausable", "ownable"];
8
+
9
+ // const coderResult = await coderAgent.invoke({ requirements });
10
+ // console.log("======= Coder =======");
11
+ // console.log(coderResult.contract);
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);
23
 
24
+ import "dotenv/config";
25
+ import { auditorAgent } from "./agents/auditor/agent.ts";
26
+ import { logger } from "./logger.ts";
27
 
28
+ logger.info("Starting auditorAgent");
29
+
30
+ const result = await auditorAgent.invoke({
31
+ repoPath: "/Users/uanderson/personal/projeto-talp1/dist/repos/example",
32
  });
33
+
34
+ logger.info("Agent completed");
35
+ logger.debug(`Agent result:\n${JSON.stringify(result, null, 2)}`);