solidity-argus 0.2.0 → 0.3.0
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.
- package/AGENTS.md +3 -3
- package/README.md +93 -37
- package/package.json +33 -7
- package/skills/INVENTORY.md +88 -57
- package/skills/README.md +26 -23
- package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
- package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
- package/skills/case-studies/cream-finance/SKILL.md +52 -0
- package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
- package/skills/case-studies/dao-hack/SKILL.md +51 -0
- package/skills/case-studies/euler-finance/SKILL.md +52 -0
- package/skills/case-studies/harvest-finance/SKILL.md +52 -0
- package/skills/case-studies/level-finance/SKILL.md +51 -0
- package/skills/case-studies/mango-markets/SKILL.md +53 -0
- package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
- package/skills/case-studies/parity-multisig/SKILL.md +55 -0
- package/skills/case-studies/poly-network/SKILL.md +51 -0
- package/skills/case-studies/rari-fuse/SKILL.md +51 -0
- package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
- package/skills/case-studies/wormhole-bridge/SKILL.md +51 -0
- package/skills/manifests/smartbugs.json +1 -3
- package/skills/manifests/sunweb3sec.json +1 -3
- package/skills/vulnerability-patterns/access-control/SKILL.md +14 -0
- package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
- package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
- package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
- package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +2 -1
- package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
- package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
- package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +2 -1
- package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +1 -0
- package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
- package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
- package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +1 -0
- package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
- package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
- package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
- package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
- package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
- package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
- package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
- package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
- package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
- package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
- package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
- package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
- package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
- package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
- package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
- package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +9 -0
- package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +1 -0
- package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +9 -0
- package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
- package/skills/vulnerability-patterns/signature-malleability/SKILL.md +2 -1
- package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
- package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +2 -1
- package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
- package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
- package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
- package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
- package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
- package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
- package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
- package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
- package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
- package/src/agents/argus-prompt.ts +24 -7
- package/src/agents/pythia-prompt.ts +3 -4
- package/src/agents/scribe-prompt.ts +7 -2
- package/src/agents/sentinel-prompt.ts +32 -3
- package/src/cli/cli-program.ts +29 -26
- package/src/cli/commands/check-skills.ts +135 -0
- package/src/cli/commands/doctor.ts +48 -26
- package/src/cli/commands/init.ts +5 -3
- package/src/cli/commands/install.ts +7 -5
- package/src/cli/commands/lint-skills.ts +16 -12
- package/src/cli/index.ts +5 -5
- package/src/cli/types.ts +3 -3
- package/src/config/index.ts +1 -1
- package/src/config/loader.ts +4 -6
- package/src/config/schema.ts +4 -5
- package/src/config/types.ts +2 -2
- package/src/constants/defaults.ts +2 -0
- package/src/create-hooks.ts +145 -34
- package/src/create-managers.ts +10 -8
- package/src/create-tools.ts +13 -9
- package/src/features/background-agent/background-manager.ts +93 -87
- package/src/features/background-agent/index.ts +1 -1
- package/src/features/context-monitor/context-monitor.ts +3 -3
- package/src/features/context-monitor/index.ts +2 -2
- package/src/features/error-recovery/session-recovery.ts +2 -4
- package/src/features/error-recovery/tool-error-recovery.ts +12 -7
- package/src/features/index.ts +5 -5
- package/src/features/persistent-state/audit-state-manager.ts +143 -60
- package/src/features/persistent-state/global-run-index.ts +38 -0
- package/src/features/persistent-state/index.ts +1 -1
- package/src/features/persistent-state/run-journal.ts +86 -0
- package/src/hooks/config-handler.ts +28 -11
- package/src/hooks/context-budget.ts +2 -5
- package/src/hooks/event-hook.ts +47 -23
- package/src/hooks/hook-system.ts +4 -4
- package/src/hooks/index.ts +5 -5
- package/src/hooks/knowledge-sync-hook.ts +18 -21
- package/src/hooks/recon-context-builder.ts +2 -2
- package/src/hooks/safe-create-hook.ts +6 -7
- package/src/hooks/tool-tracking-hook.ts +104 -50
- package/src/hooks/types.ts +2 -1
- package/src/index.ts +23 -36
- package/src/knowledge/retry.ts +22 -22
- package/src/knowledge/scvd-client.ts +88 -95
- package/src/knowledge/scvd-errors.ts +35 -35
- package/src/knowledge/scvd-index.ts +78 -80
- package/src/knowledge/scvd-sync.ts +106 -101
- package/src/managers/index.ts +1 -1
- package/src/managers/types.ts +19 -14
- package/src/plugin-interface.ts +7 -9
- package/src/shared/binary-utils.ts +44 -35
- package/src/shared/deep-merge.ts +55 -36
- package/src/shared/file-utils.ts +21 -19
- package/src/shared/index.ts +11 -5
- package/src/shared/jsonc-parser.ts +123 -28
- package/src/shared/logger.ts +16 -3
- package/src/shared/project-utils.ts +30 -0
- package/src/skills/analysis/cluster.ts +414 -0
- package/src/skills/analysis/gates.ts +227 -0
- package/src/skills/analysis/index.ts +33 -0
- package/src/skills/analysis/normalize.ts +217 -0
- package/src/skills/analysis/similarity.ts +224 -0
- package/src/skills/argus-skill-resolver.ts +17 -6
- package/src/skills/skill-schema.ts +11 -10
- package/src/solodit-lifecycle.ts +202 -0
- package/src/state/audit-state.ts +8 -8
- package/src/state/finding-store.ts +68 -55
- package/src/state/types.ts +88 -67
- package/src/tools/argus-skill-load-tool.ts +12 -7
- package/src/tools/contract-analyzer-tool.ts +60 -77
- package/src/tools/forge-coverage-tool.ts +226 -0
- package/src/tools/forge-fuzz-tool.ts +127 -127
- package/src/tools/forge-test-tool.ts +153 -157
- package/src/tools/gas-analysis-tool.ts +264 -0
- package/src/tools/pattern-checker-tool.ts +185 -190
- package/src/tools/pattern-loader.ts +5 -111
- package/src/tools/proxy-detection-tool.ts +224 -0
- package/src/tools/report-generator-tool.ts +268 -200
- package/src/tools/slither-tool.ts +266 -218
- package/src/tools/solodit-search-tool.ts +216 -119
- package/src/tools/sync-knowledge-tool.ts +7 -11
- package/src/utils/audit-artifact-detector.ts +28 -29
- package/src/utils/dependency-scanner.ts +37 -37
- package/src/utils/project-detector.ts +111 -124
- package/src/utils/solidity-parser.ts +103 -74
- package/skills/patterns/access-control.yaml +0 -31
- package/skills/patterns/erc4626.yaml +0 -29
- package/skills/patterns/flash-loan.yaml +0 -20
- package/skills/patterns/oracle.yaml +0 -30
- package/skills/patterns/proxy.yaml +0 -30
- package/skills/patterns/reentrancy.yaml +0 -30
- package/skills/patterns/signature.yaml +0 -31
- package/src/hooks/event-hook-v2.ts +0 -99
- package/src/state/plugin-state.ts +0 -14
|
@@ -1,22 +1,22 @@
|
|
|
1
|
-
import type { ContractProfile } from "../state/types"
|
|
1
|
+
import type { ContractProfile } from "../state/types"
|
|
2
2
|
|
|
3
3
|
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
|
|
4
|
+
type: string
|
|
5
|
+
name: string
|
|
6
|
+
inputs?: Array<{ name: string; type: string }>
|
|
7
|
+
outputs?: Array<{ name: string; type: string }>
|
|
8
|
+
stateMutability?: string
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
interface StorageLayoutItem {
|
|
12
|
-
label: string
|
|
13
|
-
type: string
|
|
14
|
-
slot: string
|
|
12
|
+
label: string
|
|
13
|
+
type: string
|
|
14
|
+
slot: string
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
interface StorageLayout {
|
|
18
|
-
storage: StorageLayoutItem[]
|
|
19
|
-
types: Record<string, { label: string }
|
|
18
|
+
storage: StorageLayoutItem[]
|
|
19
|
+
types: Record<string, { label: string }>
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
@@ -25,12 +25,48 @@ interface StorageLayout {
|
|
|
25
25
|
* Falls back to the original string if no JSON delimiter is found.
|
|
26
26
|
*/
|
|
27
27
|
function extractJson(raw: string, opener: "[" | "{"): string {
|
|
28
|
-
const
|
|
29
|
-
const start = raw.indexOf(opener)
|
|
30
|
-
if (start === -1) return raw
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
28
|
+
const _closer = opener === "[" ? "]" : "}"
|
|
29
|
+
const start = raw.indexOf(opener)
|
|
30
|
+
if (start === -1) return raw
|
|
31
|
+
|
|
32
|
+
let depth = 0
|
|
33
|
+
let inString = false
|
|
34
|
+
let escaped = false
|
|
35
|
+
|
|
36
|
+
for (let i = start; i < raw.length; i++) {
|
|
37
|
+
const ch = raw.charAt(i)
|
|
38
|
+
|
|
39
|
+
if (inString) {
|
|
40
|
+
if (escaped) {
|
|
41
|
+
escaped = false
|
|
42
|
+
continue
|
|
43
|
+
}
|
|
44
|
+
if (ch === "\\") {
|
|
45
|
+
escaped = true
|
|
46
|
+
continue
|
|
47
|
+
}
|
|
48
|
+
if (ch === '"') {
|
|
49
|
+
inString = false
|
|
50
|
+
}
|
|
51
|
+
continue
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (ch === '"') {
|
|
55
|
+
inString = true
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (ch === "{" || ch === "[") {
|
|
60
|
+
depth++
|
|
61
|
+
} else if (ch === "}" || ch === "]") {
|
|
62
|
+
depth--
|
|
63
|
+
if (depth === 0) {
|
|
64
|
+
return raw.slice(start, i + 1)
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return raw
|
|
34
70
|
}
|
|
35
71
|
|
|
36
72
|
/**
|
|
@@ -41,7 +77,7 @@ function extractJson(raw: string, opener: "[" | "{"): string {
|
|
|
41
77
|
*/
|
|
42
78
|
export async function extractContractInfo(
|
|
43
79
|
contractName: string,
|
|
44
|
-
projectDir: string
|
|
80
|
+
projectDir: string,
|
|
45
81
|
): Promise<ContractProfile> {
|
|
46
82
|
const result: ContractProfile = {
|
|
47
83
|
name: contractName,
|
|
@@ -52,23 +88,21 @@ export async function extractContractInfo(
|
|
|
52
88
|
accessControlPattern: "none",
|
|
53
89
|
externalCalls: [],
|
|
54
90
|
riskIndicators: [],
|
|
55
|
-
}
|
|
91
|
+
}
|
|
56
92
|
|
|
57
93
|
try {
|
|
58
94
|
// Run forge inspect abi
|
|
59
|
-
const abiResult = Bun.spawnSync(
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
);
|
|
95
|
+
const abiResult = Bun.spawnSync(["forge", "inspect", contractName, "abi", "--json"], {
|
|
96
|
+
cwd: projectDir,
|
|
97
|
+
stdout: "pipe",
|
|
98
|
+
stderr: "pipe",
|
|
99
|
+
timeout: 15_000,
|
|
100
|
+
})
|
|
67
101
|
|
|
68
102
|
if (!abiResult.success) {
|
|
69
|
-
const errorMsg = abiResult.stderr?.toString() || "Unknown error"
|
|
70
|
-
result.error = `Failed to inspect ABI: ${errorMsg}
|
|
71
|
-
return result
|
|
103
|
+
const errorMsg = abiResult.stderr?.toString() || "Unknown error"
|
|
104
|
+
result.error = `Failed to inspect ABI: ${errorMsg}`
|
|
105
|
+
return result
|
|
72
106
|
}
|
|
73
107
|
|
|
74
108
|
// Run forge inspect storage-layout
|
|
@@ -78,65 +112,66 @@ export async function extractContractInfo(
|
|
|
78
112
|
cwd: projectDir,
|
|
79
113
|
stdout: "pipe",
|
|
80
114
|
stderr: "pipe",
|
|
81
|
-
|
|
82
|
-
|
|
115
|
+
timeout: 15_000,
|
|
116
|
+
},
|
|
117
|
+
)
|
|
83
118
|
|
|
84
119
|
if (!storageResult.success) {
|
|
85
|
-
const errorMsg = storageResult.stderr?.toString() || "Unknown error"
|
|
86
|
-
result.error = `Failed to inspect storage layout: ${errorMsg}
|
|
87
|
-
return result
|
|
120
|
+
const errorMsg = storageResult.stderr?.toString() || "Unknown error"
|
|
121
|
+
result.error = `Failed to inspect storage layout: ${errorMsg}`
|
|
122
|
+
return result
|
|
88
123
|
}
|
|
89
124
|
|
|
90
125
|
// Parse ABI
|
|
91
|
-
const abiRaw = abiResult.stdout?.toString() || "[]"
|
|
92
|
-
const abiOutput = extractJson(abiRaw, "[")
|
|
93
|
-
let abi: ABIFunction[] = []
|
|
126
|
+
const abiRaw = abiResult.stdout?.toString() || "[]"
|
|
127
|
+
const abiOutput = extractJson(abiRaw, "[")
|
|
128
|
+
let abi: ABIFunction[] = []
|
|
94
129
|
try {
|
|
95
|
-
abi = JSON.parse(abiOutput)
|
|
130
|
+
abi = JSON.parse(abiOutput)
|
|
96
131
|
} catch (e) {
|
|
97
|
-
result.error = `Failed to parse ABI JSON: ${e instanceof Error ? e.message : "Unknown error"}
|
|
98
|
-
return result
|
|
132
|
+
result.error = `Failed to parse ABI JSON: ${e instanceof Error ? e.message : "Unknown error"}`
|
|
133
|
+
return result
|
|
99
134
|
}
|
|
100
135
|
|
|
101
136
|
// Parse storage layout
|
|
102
|
-
const storageRaw = storageResult.stdout?.toString() || "{}"
|
|
103
|
-
const storageOutput = extractJson(storageRaw, "{")
|
|
104
|
-
let storageLayout: StorageLayout = { storage: [], types: {} }
|
|
137
|
+
const storageRaw = storageResult.stdout?.toString() || "{}"
|
|
138
|
+
const storageOutput = extractJson(storageRaw, "{")
|
|
139
|
+
let storageLayout: StorageLayout = { storage: [], types: {} }
|
|
105
140
|
try {
|
|
106
|
-
storageLayout = JSON.parse(storageOutput)
|
|
141
|
+
storageLayout = JSON.parse(storageOutput)
|
|
107
142
|
} catch (e) {
|
|
108
|
-
result.error = `Failed to parse storage layout JSON: ${e instanceof Error ? e.message : "Unknown error"}
|
|
109
|
-
return result
|
|
143
|
+
result.error = `Failed to parse storage layout JSON: ${e instanceof Error ? e.message : "Unknown error"}`
|
|
144
|
+
return result
|
|
110
145
|
}
|
|
111
146
|
|
|
112
147
|
// Extract functions from ABI
|
|
113
|
-
const functions = abi.filter((item) => item.type === "function")
|
|
148
|
+
const functions = abi.filter((item) => item.type === "function")
|
|
114
149
|
result.functions = functions.map((func) => ({
|
|
115
150
|
name: func.name || "",
|
|
116
151
|
visibility: mapStateMutabilityToVisibility(func.stateMutability || "nonpayable"),
|
|
117
152
|
mutability: func.stateMutability || "nonpayable",
|
|
118
153
|
modifiers: [],
|
|
119
|
-
}))
|
|
154
|
+
}))
|
|
120
155
|
|
|
121
156
|
// Extract state variables from storage layout
|
|
122
157
|
result.stateVars = storageLayout.storage.map((item) => {
|
|
123
|
-
const typeInfo = storageLayout.types[item.type]
|
|
124
|
-
const typeLabel = typeInfo?.label || item.type
|
|
158
|
+
const typeInfo = storageLayout.types[item.type]
|
|
159
|
+
const typeLabel = typeInfo?.label || item.type
|
|
125
160
|
|
|
126
161
|
return {
|
|
127
162
|
name: item.label,
|
|
128
163
|
type: typeLabel,
|
|
129
164
|
visibility: "internal", // Default visibility for storage vars
|
|
130
|
-
}
|
|
131
|
-
})
|
|
165
|
+
}
|
|
166
|
+
})
|
|
132
167
|
|
|
133
168
|
// Detect access control pattern
|
|
134
|
-
result.accessControlPattern = detectAccessControlPattern(result.functions)
|
|
169
|
+
result.accessControlPattern = detectAccessControlPattern(result.functions)
|
|
135
170
|
|
|
136
|
-
return result
|
|
171
|
+
return result
|
|
137
172
|
} catch (e) {
|
|
138
|
-
result.error = `Unexpected error: ${e instanceof Error ? e.message : "Unknown error"}
|
|
139
|
-
return result
|
|
173
|
+
result.error = `Unexpected error: ${e instanceof Error ? e.message : "Unknown error"}`
|
|
174
|
+
return result
|
|
140
175
|
}
|
|
141
176
|
}
|
|
142
177
|
|
|
@@ -144,18 +179,16 @@ export async function extractContractInfo(
|
|
|
144
179
|
* Map Solidity stateMutability to visibility
|
|
145
180
|
* ABI doesn't directly specify visibility, so we infer from mutability
|
|
146
181
|
*/
|
|
147
|
-
function mapStateMutabilityToVisibility(
|
|
148
|
-
stateMutability: string
|
|
149
|
-
): string {
|
|
182
|
+
function mapStateMutabilityToVisibility(stateMutability: string): string {
|
|
150
183
|
switch (stateMutability) {
|
|
151
184
|
case "pure":
|
|
152
185
|
case "view":
|
|
153
|
-
return "view"
|
|
186
|
+
return "view"
|
|
154
187
|
case "payable":
|
|
155
188
|
case "nonpayable":
|
|
156
|
-
return "external"
|
|
189
|
+
return "external"
|
|
157
190
|
default:
|
|
158
|
-
return "external"
|
|
191
|
+
return "external"
|
|
159
192
|
}
|
|
160
193
|
}
|
|
161
194
|
|
|
@@ -163,28 +196,24 @@ function mapStateMutabilityToVisibility(
|
|
|
163
196
|
* Detect access control pattern from function names and signatures
|
|
164
197
|
*/
|
|
165
198
|
function detectAccessControlPattern(
|
|
166
|
-
functions: Array<{ name: string; visibility: string; mutability: string; modifiers: string[] }
|
|
199
|
+
functions: Array<{ name: string; visibility: string; mutability: string; modifiers: string[] }>,
|
|
167
200
|
): "ownable" | "access-control" | "custom" | "none" {
|
|
168
|
-
const functionNames = functions.map((f) => f.name.toLowerCase())
|
|
201
|
+
const functionNames = functions.map((f) => f.name.toLowerCase())
|
|
169
202
|
|
|
170
203
|
// Check for Ownable pattern
|
|
171
204
|
if (functionNames.includes("owner") || functionNames.includes("transferownership")) {
|
|
172
|
-
return "ownable"
|
|
205
|
+
return "ownable"
|
|
173
206
|
}
|
|
174
207
|
|
|
175
208
|
// Check for AccessControl pattern (OpenZeppelin)
|
|
176
209
|
if (functionNames.includes("hasrole") || functionNames.includes("grantrole")) {
|
|
177
|
-
return "access-control"
|
|
210
|
+
return "access-control"
|
|
178
211
|
}
|
|
179
212
|
|
|
180
213
|
// Check for custom access control patterns
|
|
181
|
-
if (
|
|
182
|
-
|
|
183
|
-
name.includes("onlyadmin") || name.includes("requireadmin")
|
|
184
|
-
)
|
|
185
|
-
) {
|
|
186
|
-
return "custom";
|
|
214
|
+
if (functionNames.some((name) => name.includes("onlyadmin") || name.includes("requireadmin"))) {
|
|
215
|
+
return "custom"
|
|
187
216
|
}
|
|
188
217
|
|
|
189
|
-
return "none"
|
|
218
|
+
return "none"
|
|
190
219
|
}
|
|
@@ -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
|
-
}
|