solidity-argus 0.2.0 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (169) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +93 -37
  3. package/package.json +34 -7
  4. package/skills/INVENTORY.md +88 -57
  5. package/skills/README.md +26 -23
  6. package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
  7. package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
  8. package/skills/case-studies/cream-finance/SKILL.md +52 -0
  9. package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
  10. package/skills/case-studies/dao-hack/SKILL.md +51 -0
  11. package/skills/case-studies/euler-finance/SKILL.md +52 -0
  12. package/skills/case-studies/harvest-finance/SKILL.md +52 -0
  13. package/skills/case-studies/level-finance/SKILL.md +51 -0
  14. package/skills/case-studies/mango-markets/SKILL.md +53 -0
  15. package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
  16. package/skills/case-studies/parity-multisig/SKILL.md +55 -0
  17. package/skills/case-studies/poly-network/SKILL.md +51 -0
  18. package/skills/case-studies/rari-fuse/SKILL.md +51 -0
  19. package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
  20. package/skills/case-studies/wormhole-bridge/SKILL.md +51 -0
  21. package/skills/manifests/smartbugs.json +1 -3
  22. package/skills/manifests/sunweb3sec.json +1 -3
  23. package/skills/vulnerability-patterns/access-control/SKILL.md +14 -0
  24. package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
  25. package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
  26. package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
  27. package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +2 -1
  28. package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
  29. package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
  30. package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +2 -1
  31. package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
  32. package/skills/vulnerability-patterns/dos-revert/SKILL.md +1 -0
  33. package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
  34. package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
  35. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +1 -0
  36. package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
  37. package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
  38. package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
  39. package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
  40. package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
  41. package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
  42. package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
  43. package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
  44. package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
  45. package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
  46. package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
  47. package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
  48. package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
  49. package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
  50. package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
  51. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +9 -0
  52. package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
  53. package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +1 -0
  54. package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
  55. package/skills/vulnerability-patterns/reentrancy/SKILL.md +9 -0
  56. package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
  57. package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
  58. package/skills/vulnerability-patterns/signature-malleability/SKILL.md +2 -1
  59. package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
  60. package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
  61. package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +2 -1
  62. package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
  63. package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
  64. package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
  65. package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
  66. package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
  67. package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
  68. package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
  69. package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
  70. package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
  71. package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
  72. package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
  73. package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
  74. package/src/agents/argus-prompt.ts +34 -7
  75. package/src/agents/pythia-prompt.ts +13 -4
  76. package/src/agents/scribe-prompt.ts +20 -2
  77. package/src/agents/sentinel-prompt.ts +45 -5
  78. package/src/cli/cli-program.ts +29 -26
  79. package/src/cli/commands/check-skills.ts +135 -0
  80. package/src/cli/commands/doctor.ts +48 -26
  81. package/src/cli/commands/init.ts +5 -3
  82. package/src/cli/commands/install.ts +7 -5
  83. package/src/cli/commands/lint-skills.ts +16 -12
  84. package/src/cli/index.ts +5 -5
  85. package/src/cli/types.ts +3 -3
  86. package/src/config/index.ts +1 -1
  87. package/src/config/loader.ts +4 -6
  88. package/src/config/schema.ts +6 -5
  89. package/src/config/types.ts +2 -2
  90. package/src/constants/defaults.ts +2 -0
  91. package/src/create-hooks.ts +145 -34
  92. package/src/create-managers.ts +10 -8
  93. package/src/create-tools.ts +13 -9
  94. package/src/features/background-agent/background-manager.ts +93 -87
  95. package/src/features/background-agent/index.ts +1 -1
  96. package/src/features/context-monitor/context-monitor.ts +3 -3
  97. package/src/features/context-monitor/index.ts +2 -2
  98. package/src/features/error-recovery/session-recovery.ts +2 -4
  99. package/src/features/error-recovery/tool-error-recovery.ts +12 -7
  100. package/src/features/index.ts +5 -5
  101. package/src/features/persistent-state/audit-state-manager.ts +143 -60
  102. package/src/features/persistent-state/global-run-index.ts +38 -0
  103. package/src/features/persistent-state/index.ts +1 -1
  104. package/src/features/persistent-state/run-journal.ts +86 -0
  105. package/src/hooks/config-handler.ts +28 -11
  106. package/src/hooks/context-budget.ts +2 -5
  107. package/src/hooks/event-hook.ts +47 -23
  108. package/src/hooks/hook-system.ts +4 -4
  109. package/src/hooks/index.ts +5 -5
  110. package/src/hooks/knowledge-sync-hook.ts +18 -21
  111. package/src/hooks/recon-context-builder.ts +2 -2
  112. package/src/hooks/safe-create-hook.ts +6 -7
  113. package/src/hooks/system-prompt-hook.ts +18 -1
  114. package/src/hooks/tool-tracking-hook.ts +110 -51
  115. package/src/hooks/types.ts +2 -1
  116. package/src/index.ts +24 -37
  117. package/src/knowledge/retry.ts +22 -22
  118. package/src/knowledge/scvd-client.ts +88 -95
  119. package/src/knowledge/scvd-errors.ts +35 -35
  120. package/src/knowledge/scvd-index.ts +78 -80
  121. package/src/knowledge/scvd-sync.ts +106 -101
  122. package/src/managers/index.ts +1 -1
  123. package/src/managers/types.ts +19 -14
  124. package/src/plugin-interface.ts +7 -9
  125. package/src/shared/binary-utils.ts +44 -35
  126. package/src/shared/deep-merge.ts +55 -36
  127. package/src/shared/file-utils.ts +21 -19
  128. package/src/shared/index.ts +11 -5
  129. package/src/shared/jsonc-parser.ts +123 -28
  130. package/src/shared/logger.ts +16 -3
  131. package/src/shared/project-utils.ts +30 -0
  132. package/src/skills/analysis/cluster.ts +414 -0
  133. package/src/skills/analysis/gates.ts +227 -0
  134. package/src/skills/analysis/index.ts +33 -0
  135. package/src/skills/analysis/normalize.ts +217 -0
  136. package/src/skills/analysis/similarity.ts +224 -0
  137. package/src/skills/argus-skill-resolver.ts +17 -6
  138. package/src/skills/skill-schema.ts +11 -10
  139. package/src/solodit-lifecycle.ts +203 -0
  140. package/src/state/audit-state.ts +8 -8
  141. package/src/state/finding-store.ts +68 -55
  142. package/src/state/types.ts +88 -67
  143. package/src/tools/argus-skill-load-tool.ts +12 -7
  144. package/src/tools/contract-analyzer-tool.ts +142 -77
  145. package/src/tools/forge-coverage-tool.ts +226 -0
  146. package/src/tools/forge-fuzz-tool.ts +127 -127
  147. package/src/tools/forge-test-tool.ts +201 -158
  148. package/src/tools/gas-analysis-tool.ts +264 -0
  149. package/src/tools/pattern-checker-tool.ts +203 -191
  150. package/src/tools/pattern-loader.ts +5 -111
  151. package/src/tools/pattern-schema.ts +3 -0
  152. package/src/tools/proxy-detection-tool.ts +224 -0
  153. package/src/tools/report-generator-tool.ts +305 -206
  154. package/src/tools/slither-tool.ts +266 -218
  155. package/src/tools/solodit-search-tool.ts +235 -119
  156. package/src/tools/sync-knowledge-tool.ts +7 -11
  157. package/src/utils/audit-artifact-detector.ts +28 -29
  158. package/src/utils/dependency-scanner.ts +37 -37
  159. package/src/utils/project-detector.ts +111 -124
  160. package/src/utils/solidity-parser.ts +175 -75
  161. package/skills/patterns/access-control.yaml +0 -31
  162. package/skills/patterns/erc4626.yaml +0 -29
  163. package/skills/patterns/flash-loan.yaml +0 -20
  164. package/skills/patterns/oracle.yaml +0 -30
  165. package/skills/patterns/proxy.yaml +0 -30
  166. package/skills/patterns/reentrancy.yaml +0 -30
  167. package/skills/patterns/signature.yaml +0 -31
  168. package/src/hooks/event-hook-v2.ts +0 -99
  169. package/src/state/plugin-state.ts +0 -14
@@ -1,22 +1,25 @@
1
- import type { ContractProfile } from "../state/types";
1
+ import * as parser from "@solidity-parser/parser"
2
+ import type { ContractProfile } from "../state/types"
3
+
4
+ const EXTERNAL_CALL_METHODS = new Set(["call", "transfer", "send", "delegatecall", "staticcall"])
2
5
 
3
6
  interface ABIFunction {
4
- type: string;
5
- name: string;
6
- inputs?: Array<{ name: string; type: string }>;
7
- outputs?: Array<{ name: string; type: string }>;
8
- stateMutability?: string;
7
+ type: string
8
+ name: string
9
+ inputs?: Array<{ name: string; type: string }>
10
+ outputs?: Array<{ name: string; type: string }>
11
+ stateMutability?: string
9
12
  }
10
13
 
11
14
  interface StorageLayoutItem {
12
- label: string;
13
- type: string;
14
- slot: string;
15
+ label: string
16
+ type: string
17
+ slot: string
15
18
  }
16
19
 
17
20
  interface StorageLayout {
18
- storage: StorageLayoutItem[];
19
- types: Record<string, { label: string }>;
21
+ storage: StorageLayoutItem[]
22
+ types: Record<string, { label: string }>
20
23
  }
21
24
 
22
25
  /**
@@ -24,13 +27,117 @@ interface StorageLayout {
24
27
  * prefix (e.g. forge table-format output, compilation progress).
25
28
  * Falls back to the original string if no JSON delimiter is found.
26
29
  */
27
- function extractJson(raw: string, opener: "[" | "{"): string {
28
- const closer = opener === "[" ? "]" : "}";
29
- const start = raw.indexOf(opener);
30
- if (start === -1) return raw;
31
- const end = raw.lastIndexOf(closer);
32
- if (end === -1) return raw;
33
- return raw.slice(start, end + 1);
30
+ export function extractJson(raw: string, opener: "[" | "{"): string {
31
+ const start = raw.indexOf(opener)
32
+ if (start === -1) return raw
33
+
34
+ let depth = 0
35
+ let inString = false
36
+ let escaped = false
37
+
38
+ for (let i = start; i < raw.length; i++) {
39
+ const ch = raw.charAt(i)
40
+
41
+ if (inString) {
42
+ if (escaped) {
43
+ escaped = false
44
+ continue
45
+ }
46
+ if (ch === "\\") {
47
+ escaped = true
48
+ continue
49
+ }
50
+ if (ch === '"') {
51
+ inString = false
52
+ }
53
+ continue
54
+ }
55
+
56
+ if (ch === '"') {
57
+ inString = true
58
+ continue
59
+ }
60
+
61
+ if (ch === "{" || ch === "[") {
62
+ depth++
63
+ } else if (ch === "}" || ch === "]") {
64
+ depth--
65
+ if (depth === 0) {
66
+ return raw.slice(start, i + 1)
67
+ }
68
+ }
69
+ }
70
+
71
+ return raw
72
+ }
73
+
74
+ function toRecord(value: unknown): Record<string, unknown> | undefined {
75
+ if (typeof value === "object" && value !== null) {
76
+ return value as Record<string, unknown>
77
+ }
78
+
79
+ return undefined
80
+ }
81
+
82
+ function extractNodeExpressionName(node: unknown): string | undefined {
83
+ const record = toRecord(node)
84
+ if (!record) return undefined
85
+
86
+ const type = typeof record.type === "string" ? record.type : undefined
87
+ if (!type) return undefined
88
+
89
+ if (type === "Identifier") {
90
+ return typeof record.name === "string" ? record.name : undefined
91
+ }
92
+
93
+ if (type === "ThisExpression") {
94
+ return "this"
95
+ }
96
+
97
+ if (type === "MemberAccess") {
98
+ const expressionName = extractNodeExpressionName(record.expression)
99
+ const memberName = typeof record.memberName === "string" ? record.memberName : undefined
100
+
101
+ if (expressionName && memberName) {
102
+ return `${expressionName}.${memberName}`
103
+ }
104
+
105
+ return expressionName ?? memberName
106
+ }
107
+
108
+ if (type === "IndexAccess") {
109
+ return extractNodeExpressionName(record.base)
110
+ }
111
+
112
+ if (type === "FunctionCall") {
113
+ return extractNodeExpressionName(record.expression)
114
+ }
115
+
116
+ return undefined
117
+ }
118
+
119
+ export function parseExternalCalls(sourceText: string): string[] {
120
+ try {
121
+ const ast = parser.parse(sourceText, { tolerant: true, loc: false, range: false })
122
+ const externalCalls = new Set<string>()
123
+
124
+ parser.visit(ast, {
125
+ MemberAccess(node: unknown) {
126
+ const record = toRecord(node)
127
+ if (!record) return
128
+
129
+ const memberName = typeof record.memberName === "string" ? record.memberName : undefined
130
+ if (!memberName || !EXTERNAL_CALL_METHODS.has(memberName)) return
131
+
132
+ const expressionName = extractNodeExpressionName(record.expression)
133
+ externalCalls.add(expressionName ? `${expressionName}.${memberName}` : memberName)
134
+ },
135
+ })
136
+
137
+ return [...externalCalls]
138
+ } catch {
139
+ return []
140
+ }
34
141
  }
35
142
 
36
143
  /**
@@ -41,7 +148,7 @@ function extractJson(raw: string, opener: "[" | "{"): string {
41
148
  */
42
149
  export async function extractContractInfo(
43
150
  contractName: string,
44
- projectDir: string
151
+ projectDir: string,
45
152
  ): Promise<ContractProfile> {
46
153
  const result: ContractProfile = {
47
154
  name: contractName,
@@ -52,23 +159,21 @@ export async function extractContractInfo(
52
159
  accessControlPattern: "none",
53
160
  externalCalls: [],
54
161
  riskIndicators: [],
55
- };
162
+ }
56
163
 
57
164
  try {
58
165
  // Run forge inspect abi
59
- const abiResult = Bun.spawnSync(
60
- ["forge", "inspect", contractName, "abi", "--json"],
61
- {
62
- cwd: projectDir,
63
- stdout: "pipe",
64
- stderr: "pipe",
65
- }
66
- );
166
+ const abiResult = Bun.spawnSync(["forge", "inspect", contractName, "abi", "--json"], {
167
+ cwd: projectDir,
168
+ stdout: "pipe",
169
+ stderr: "pipe",
170
+ timeout: 15_000,
171
+ })
67
172
 
68
173
  if (!abiResult.success) {
69
- const errorMsg = abiResult.stderr?.toString() || "Unknown error";
70
- result.error = `Failed to inspect ABI: ${errorMsg}`;
71
- return result;
174
+ const errorMsg = abiResult.stderr?.toString() || "Unknown error"
175
+ result.error = `Failed to inspect ABI: ${errorMsg}`
176
+ return result
72
177
  }
73
178
 
74
179
  // Run forge inspect storage-layout
@@ -78,65 +183,66 @@ export async function extractContractInfo(
78
183
  cwd: projectDir,
79
184
  stdout: "pipe",
80
185
  stderr: "pipe",
81
- }
82
- );
186
+ timeout: 15_000,
187
+ },
188
+ )
83
189
 
84
190
  if (!storageResult.success) {
85
- const errorMsg = storageResult.stderr?.toString() || "Unknown error";
86
- result.error = `Failed to inspect storage layout: ${errorMsg}`;
87
- return result;
191
+ const errorMsg = storageResult.stderr?.toString() || "Unknown error"
192
+ result.error = `Failed to inspect storage layout: ${errorMsg}`
193
+ return result
88
194
  }
89
195
 
90
196
  // Parse ABI
91
- const abiRaw = abiResult.stdout?.toString() || "[]";
92
- const abiOutput = extractJson(abiRaw, "[");
93
- let abi: ABIFunction[] = [];
197
+ const abiRaw = abiResult.stdout?.toString() || "[]"
198
+ const abiOutput = extractJson(abiRaw, "[")
199
+ let abi: ABIFunction[] = []
94
200
  try {
95
- abi = JSON.parse(abiOutput);
201
+ abi = JSON.parse(abiOutput)
96
202
  } catch (e) {
97
- result.error = `Failed to parse ABI JSON: ${e instanceof Error ? e.message : "Unknown error"}`;
98
- return result;
203
+ result.error = `Failed to parse ABI JSON: ${e instanceof Error ? e.message : "Unknown error"}`
204
+ return result
99
205
  }
100
206
 
101
207
  // Parse storage layout
102
- const storageRaw = storageResult.stdout?.toString() || "{}";
103
- const storageOutput = extractJson(storageRaw, "{");
104
- let storageLayout: StorageLayout = { storage: [], types: {} };
208
+ const storageRaw = storageResult.stdout?.toString() || "{}"
209
+ const storageOutput = extractJson(storageRaw, "{")
210
+ let storageLayout: StorageLayout = { storage: [], types: {} }
105
211
  try {
106
- storageLayout = JSON.parse(storageOutput);
212
+ storageLayout = JSON.parse(storageOutput)
107
213
  } catch (e) {
108
- result.error = `Failed to parse storage layout JSON: ${e instanceof Error ? e.message : "Unknown error"}`;
109
- return result;
214
+ result.error = `Failed to parse storage layout JSON: ${e instanceof Error ? e.message : "Unknown error"}`
215
+ return result
110
216
  }
111
217
 
112
218
  // Extract functions from ABI
113
- const functions = abi.filter((item) => item.type === "function");
219
+ const functions = abi.filter((item) => item.type === "function")
114
220
  result.functions = functions.map((func) => ({
115
221
  name: func.name || "",
116
222
  visibility: mapStateMutabilityToVisibility(func.stateMutability || "nonpayable"),
117
223
  mutability: func.stateMutability || "nonpayable",
118
224
  modifiers: [],
119
- }));
225
+ }))
120
226
 
121
227
  // Extract state variables from storage layout
122
228
  result.stateVars = storageLayout.storage.map((item) => {
123
- const typeInfo = storageLayout.types[item.type];
124
- const typeLabel = typeInfo?.label || item.type;
229
+ const typeInfo = storageLayout.types[item.type]
230
+ const typeLabel = typeInfo?.label || item.type
125
231
 
126
232
  return {
127
233
  name: item.label,
128
234
  type: typeLabel,
129
235
  visibility: "internal", // Default visibility for storage vars
130
- };
131
- });
236
+ }
237
+ })
132
238
 
133
239
  // Detect access control pattern
134
- result.accessControlPattern = detectAccessControlPattern(result.functions);
240
+ result.accessControlPattern = detectAccessControlPattern(result.functions)
135
241
 
136
- return result;
242
+ return result
137
243
  } catch (e) {
138
- result.error = `Unexpected error: ${e instanceof Error ? e.message : "Unknown error"}`;
139
- return result;
244
+ result.error = `Unexpected error: ${e instanceof Error ? e.message : "Unknown error"}`
245
+ return result
140
246
  }
141
247
  }
142
248
 
@@ -144,18 +250,16 @@ export async function extractContractInfo(
144
250
  * Map Solidity stateMutability to visibility
145
251
  * ABI doesn't directly specify visibility, so we infer from mutability
146
252
  */
147
- function mapStateMutabilityToVisibility(
148
- stateMutability: string
149
- ): string {
253
+ function mapStateMutabilityToVisibility(stateMutability: string): string {
150
254
  switch (stateMutability) {
151
255
  case "pure":
152
256
  case "view":
153
- return "view";
257
+ return "view"
154
258
  case "payable":
155
259
  case "nonpayable":
156
- return "external";
260
+ return "external"
157
261
  default:
158
- return "external";
262
+ return "external"
159
263
  }
160
264
  }
161
265
 
@@ -163,28 +267,24 @@ function mapStateMutabilityToVisibility(
163
267
  * Detect access control pattern from function names and signatures
164
268
  */
165
269
  function detectAccessControlPattern(
166
- functions: Array<{ name: string; visibility: string; mutability: string; modifiers: string[] }>
270
+ functions: Array<{ name: string; visibility: string; mutability: string; modifiers: string[] }>,
167
271
  ): "ownable" | "access-control" | "custom" | "none" {
168
- const functionNames = functions.map((f) => f.name.toLowerCase());
272
+ const functionNames = functions.map((f) => f.name.toLowerCase())
169
273
 
170
274
  // Check for Ownable pattern
171
275
  if (functionNames.includes("owner") || functionNames.includes("transferownership")) {
172
- return "ownable";
276
+ return "ownable"
173
277
  }
174
278
 
175
279
  // Check for AccessControl pattern (OpenZeppelin)
176
280
  if (functionNames.includes("hasrole") || functionNames.includes("grantrole")) {
177
- return "access-control";
281
+ return "access-control"
178
282
  }
179
283
 
180
284
  // Check for custom access control patterns
181
- if (
182
- functionNames.some((name) =>
183
- name.includes("onlyadmin") || name.includes("requireadmin")
184
- )
185
- ) {
186
- return "custom";
285
+ if (functionNames.some((name) => name.includes("onlyadmin") || name.includes("requireadmin"))) {
286
+ return "custom"
187
287
  }
188
288
 
189
- return "none";
289
+ return "none"
190
290
  }
@@ -1,31 +0,0 @@
1
- pack_name: access-control
2
- pack_version: "1.0"
3
- patterns:
4
- - name: missing-access-modifier
5
- category: access-control
6
- severity: High
7
- swc: SWC-105
8
- confidence: Low
9
- version: "1.0"
10
- regex: 'function\s+\w+\s*\([^)]*\)\s+(external|public)'
11
- description: External or public function — verify appropriate access control modifiers (onlyOwner, onlyRole, require(msg.sender)) are applied
12
- remediation: Add access control modifiers to sensitive functions; use OpenZeppelin AccessControl or Ownable patterns
13
-
14
- - name: unprotected-initialize
15
- category: access-control
16
- severity: Critical
17
- confidence: High
18
- version: "1.0"
19
- regex: 'function\s+initialize'
20
- description: Initializer function detected — if missing initializer modifier, anyone can call and take ownership of the contract
21
- remediation: Use OpenZeppelin Initializable with initializer modifier; call _disableInitializers() in constructor for implementation contracts
22
-
23
- - name: default-visibility
24
- category: access-control
25
- severity: Medium
26
- swc: SWC-100
27
- confidence: Low
28
- version: "1.0"
29
- regex: 'function\s+\w+\s*\([^)]*\)\s*\{'
30
- description: Function without explicit visibility specifier — defaults to public in older Solidity versions, potentially exposing internal logic
31
- remediation: Always specify visibility (external, public, internal, private) explicitly for every function
@@ -1,29 +0,0 @@
1
- pack_name: erc4626
2
- pack_version: "1.0"
3
- patterns:
4
- - name: inflation-attack
5
- category: erc4626
6
- severity: Critical
7
- confidence: High
8
- version: "1.0"
9
- regex: 'deposit.*totalSupply.*==.*0|convertToShares.*totalSupply'
10
- description: ERC-4626 vault first-depositor inflation attack — attacker can donate assets to vault before first deposit to manipulate share price and steal subsequent deposits
11
- remediation: Mint dead shares on first deposit (e.g., 10**3 to address(0)); use virtual offset in share calculation; set minimum deposit amount
12
-
13
- - name: donation-attack
14
- category: erc4626
15
- severity: High
16
- confidence: Medium
17
- version: "1.0"
18
- regex: 'balanceOf.*address.*this.*totalAssets|asset\.balanceOf'
19
- description: Vault totalAssets derived from balanceOf — vulnerable to donation attack where attacker sends assets directly to inflate share price
20
- remediation: Use internal accounting for totalAssets instead of balanceOf; track deposits and withdrawals explicitly
21
-
22
- - name: rounding-error
23
- category: erc4626
24
- severity: Medium
25
- confidence: Medium
26
- version: "1.0"
27
- regex: 'mulDiv|roundUp|roundDown|FullMath'
28
- description: Custom rounding math in vault share calculations — potential rounding errors that favor attacker (round down on deposit, round up on withdraw)
29
- remediation: Round against the user (down on deposit/mint, up on withdraw/redeem); use OpenZeppelin Math.mulDiv with explicit rounding direction
@@ -1,20 +0,0 @@
1
- pack_name: flash-loan
2
- pack_version: "1.0"
3
- patterns:
4
- - name: unchecked-flash-return
5
- category: flash-loan
6
- severity: High
7
- confidence: Medium
8
- version: "1.0"
9
- regex: 'flashLoan|flashBorrow'
10
- description: Flash loan invocation without verified return — borrowed funds may not be repaid if return value is not checked
11
- remediation: Verify flash loan callback returns expected success value; check token balance after repayment; use established flash loan receiver interfaces
12
-
13
- - name: balance-inflation
14
- category: flash-loan
15
- severity: Medium
16
- confidence: Medium
17
- version: "1.0"
18
- regex: 'balanceOf\(address\(this\)\)'
19
- description: Contract reads its own token balance — vulnerable to donation/inflation attacks where attacker sends tokens directly to manipulate balance-dependent logic
20
- remediation: Track balances via internal accounting instead of balanceOf(address(this)); use shares-based accounting for vaults
@@ -1,30 +0,0 @@
1
- pack_name: oracle-manipulation
2
- pack_version: "1.0"
3
- patterns:
4
- - name: stale-price-check
5
- category: oracle-manipulation
6
- severity: High
7
- swc: SWC-120
8
- confidence: High
9
- version: "1.0"
10
- regex: 'latestRoundData|getPrice'
11
- description: Oracle price feed usage — verify staleness checks (updatedAt, roundId) are enforced to prevent using outdated prices
12
- remediation: Add require(updatedAt > block.timestamp - MAX_STALENESS) after latestRoundData calls; check answeredInRound >= roundId
13
-
14
- - name: twap-manipulation
15
- category: oracle-manipulation
16
- severity: Medium
17
- confidence: Medium
18
- version: "1.0"
19
- regex: 'observe\(|consult\('
20
- description: TWAP oracle usage — time-weighted average prices can be manipulated via sustained trading pressure within the observation window
21
- remediation: Use sufficiently long TWAP windows (30+ minutes); combine with spot price deviation checks; add circuit breakers
22
-
23
- - name: price-feed-decimals
24
- category: oracle-manipulation
25
- severity: Medium
26
- confidence: Medium
27
- version: "1.0"
28
- regex: 'priceFeed|oracle.*decimals'
29
- description: Oracle price feed with decimal handling — potential decimal mismatch between oracle feed (8 decimals) and token (18 decimals)
30
- remediation: Normalize oracle response to consistent decimals; use oracle.decimals() dynamically rather than hardcoded values
@@ -1,30 +0,0 @@
1
- pack_name: proxy
2
- pack_version: "1.0"
3
- patterns:
4
- - name: storage-collision
5
- category: proxy
6
- severity: Critical
7
- swc: SWC-112
8
- confidence: Medium
9
- version: "1.0"
10
- regex: 'delegatecall|IMPLEMENTATION_SLOT'
11
- description: Delegatecall or implementation slot usage — potential storage collision between proxy and implementation contracts if storage layouts diverge
12
- remediation: Use EIP-1967 standard storage slots; use OpenZeppelin TransparentUpgradeableProxy or UUPS; verify storage layout compatibility on upgrades
13
-
14
- - name: uninitialized-proxy
15
- category: proxy
16
- severity: High
17
- confidence: Medium
18
- version: "1.0"
19
- regex: '_disableInitializers|initializer'
20
- description: Proxy initialization pattern detected — verify implementation contract calls _disableInitializers() in constructor and proxy calls initialize()
21
- remediation: Call _disableInitializers() in implementation constructor; ensure initialize() is called atomically during proxy deployment
22
-
23
- - name: selector-clash
24
- category: proxy
25
- severity: Medium
26
- confidence: Low
27
- version: "1.0"
28
- regex: 'fallback\(\)|receive\(\).*delegatecall'
29
- description: Fallback or receive function with delegatecall — risk of function selector clash between proxy admin functions and implementation functions
30
- remediation: Use TransparentUpgradeableProxy pattern to separate admin and user call paths; verify no selector collisions with implementation ABI
@@ -1,30 +0,0 @@
1
- pack_name: reentrancy
2
- pack_version: "1.0"
3
- patterns:
4
- - name: reentrancy-eth-transfer
5
- category: reentrancy
6
- severity: High
7
- swc: SWC-107
8
- confidence: High
9
- version: "1.0"
10
- regex: '\.call\{value:'
11
- description: ETH transfer via low-level call — classic reentrancy vector where external call is made before state updates
12
- remediation: Apply checks-effects-interactions pattern; use ReentrancyGuard; update state before external calls
13
-
14
- - name: reentrancy-erc20
15
- category: reentrancy
16
- severity: Medium
17
- confidence: Medium
18
- version: "1.0"
19
- regex: '\.(transfer|transferFrom)\('
20
- description: ERC-20 token transfer that may precede state changes — potential reentrancy via token callback hooks (ERC-777, ERC-1155)
21
- remediation: Update state variables before token transfers; use ReentrancyGuard for functions with external token interactions
22
-
23
- - name: cross-function-reentrancy
24
- category: reentrancy
25
- severity: High
26
- confidence: Low
27
- version: "1.0"
28
- regex: '(external|public)\s.*\{[^}]*\.call'
29
- description: Public or external function containing a low-level call — potential cross-function reentrancy if shared state is read by other functions
30
- remediation: Use ReentrancyGuard on all functions sharing mutable state; apply checks-effects-interactions across the contract
@@ -1,31 +0,0 @@
1
- pack_name: signature
2
- pack_version: "1.0"
3
- patterns:
4
- - name: replay-attack
5
- category: signature
6
- severity: High
7
- swc: SWC-117
8
- confidence: Medium
9
- version: "1.0"
10
- regex: 'ecrecover|ECDSA\.recover'
11
- description: Signature recovery without nonce tracking — signatures may be replayed across transactions or chains if nonce and chainId are not included in signed data
12
- remediation: Include nonce, chainId, and contract address in signed message hash; increment nonce after use; use EIP-712 typed structured data
13
-
14
- - name: sig-malleability
15
- category: signature
16
- severity: Medium
17
- swc: SWC-117
18
- confidence: Medium
19
- version: "1.0"
20
- regex: ecrecover
21
- description: Raw ecrecover usage — ECDSA signatures are malleable (s-value can be flipped) allowing signature reuse if not checked against canonical form
22
- remediation: Use OpenZeppelin ECDSA.recover which enforces s <= 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0; reject non-canonical signatures
23
-
24
- - name: missing-nonce
25
- category: signature
26
- severity: High
27
- confidence: Medium
28
- version: "1.0"
29
- regex: 'permit\(|signTypedData'
30
- description: Permit or typed data signing without nonce validation — missing nonce allows signature replay after the original transaction is executed
31
- remediation: Track per-address nonces mapping(address => uint256); include nonce in EIP-712 struct; increment nonce on each use
@@ -1,99 +0,0 @@
1
- import type { AuditState } from "../state/types"
2
- import { createAuditState } from "../state/audit-state"
3
- import { createLogger } from "../shared/logger"
4
-
5
- export type AuditEventType =
6
- | "session.created"
7
- | "session.idle"
8
- | "session.error"
9
- | "session.deleted"
10
- | "audit.phase-changed"
11
- | "audit.finding-added"
12
- | "audit.complete"
13
-
14
- export type EventHookV2Fn = (input: {
15
- event: { type: string; sessionId?: string; properties?: Record<string, unknown> }
16
- }) => Promise<void>
17
-
18
- export type EventSubHandler = (event: {
19
- type: string
20
- sessionId?: string
21
- auditState: AuditState | null
22
- setAuditState: (state: AuditState | null) => void
23
- }) => Promise<void>
24
-
25
- export function createEventHookV2(
26
- projectDir?: string,
27
- subHandlers: EventSubHandler[] = [],
28
- ): {
29
- hook: EventHookV2Fn
30
- getAuditState: () => AuditState | null
31
- setAuditState: (state: AuditState | null) => void
32
- } {
33
- const logger = createLogger()
34
- let currentAuditState: AuditState | null = null
35
-
36
- const getAuditState = (): AuditState | null => currentAuditState
37
- const setAuditState = (state: AuditState | null): void => {
38
- currentAuditState = state
39
- }
40
-
41
- const hook: EventHookV2Fn = async (input): Promise<void> => {
42
- const { type, sessionId } = input.event
43
-
44
- switch (type) {
45
- case "session.created": {
46
- const dir = projectDir ?? process.cwd()
47
- const { state } = createAuditState(dir)
48
- currentAuditState = state
49
- break
50
- }
51
-
52
- case "session.idle": {
53
- if (currentAuditState) {
54
- logger.debug(
55
- `Session idle — phase: ${currentAuditState.currentPhase}, findings: ${currentAuditState.findings.length}`,
56
- )
57
- }
58
- break
59
- }
60
-
61
- case "session.error": {
62
- if (currentAuditState) {
63
- logger.error(
64
- `Session error — state snapshot: ${JSON.stringify({
65
- sessionId: currentAuditState.sessionId,
66
- phase: currentAuditState.currentPhase,
67
- findingsCount: currentAuditState.findings.length,
68
- contractsReviewed: currentAuditState.contractsReviewed,
69
- })}`,
70
- )
71
- }
72
- break
73
- }
74
-
75
- case "session.deleted": {
76
- currentAuditState = null
77
- break
78
- }
79
-
80
- default:
81
- break
82
- }
83
-
84
- for (const handler of subHandlers) {
85
- try {
86
- await handler({
87
- type,
88
- sessionId,
89
- auditState: currentAuditState,
90
- setAuditState,
91
- })
92
- } catch (error) {
93
- logger.error(`Sub-handler failed for event ${type}:`, error)
94
- }
95
- }
96
- }
97
-
98
- return { hook, getAuditState, setAuditState }
99
- }
@@ -1,14 +0,0 @@
1
- import type { ArgusConfig } from "../config/types";
2
- import type { Managers } from "../managers/types";
3
-
4
- /**
5
- * PluginState interface
6
- * Represents the complete state of the Argus plugin instance
7
- * Includes configuration, project context, and manager instances
8
- */
9
- export interface PluginState {
10
- config: ArgusConfig;
11
- projectDir: string;
12
- managers: Managers;
13
- isHookEnabled: (name: string) => boolean;
14
- }