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,109 +1,78 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import { dirname, extname, join, resolve } from "node:path"
|
|
4
|
-
import {
|
|
1
|
+
import { readdirSync, readFileSync, statSync } from "node:fs"
|
|
2
|
+
import os from "node:os"
|
|
3
|
+
import { dirname, extname, join, resolve } from "node:path"
|
|
4
|
+
import { type ToolContext, tool } from "@opencode-ai/plugin"
|
|
5
5
|
import {
|
|
6
6
|
loadIndex,
|
|
7
|
-
searchIndex,
|
|
8
7
|
type ScvdIndex,
|
|
9
8
|
type ScvdIndexEntry,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
} from "./pattern-
|
|
15
|
-
|
|
9
|
+
searchIndex,
|
|
10
|
+
} from "../knowledge/scvd-index"
|
|
11
|
+
import { createLogger } from "../shared/logger"
|
|
12
|
+
import { extractDetectionRulesFromSkills } from "./pattern-loader"
|
|
13
|
+
import type { PatternDefinition } from "./pattern-schema"
|
|
14
|
+
|
|
15
|
+
const logger = createLogger()
|
|
16
16
|
|
|
17
|
-
export type PatternSource = "
|
|
17
|
+
export type PatternSource = "skill"
|
|
18
18
|
|
|
19
19
|
export interface Match {
|
|
20
|
-
pattern: string
|
|
21
|
-
severity: "Critical" | "High" | "Medium" | "Low" | "Informational"
|
|
22
|
-
file: string
|
|
23
|
-
lines: [number, number]
|
|
24
|
-
description: string
|
|
25
|
-
exploitReference?: string
|
|
26
|
-
patternSource?: PatternSource
|
|
27
|
-
category?: string
|
|
20
|
+
pattern: string
|
|
21
|
+
severity: "Critical" | "High" | "Medium" | "Low" | "Informational"
|
|
22
|
+
file: string
|
|
23
|
+
lines: [number, number]
|
|
24
|
+
description: string
|
|
25
|
+
exploitReference?: string
|
|
26
|
+
patternSource?: PatternSource
|
|
27
|
+
category?: string
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
export interface MatchSource {
|
|
31
|
-
source: string
|
|
32
|
-
matches: Match[]
|
|
31
|
+
source: string
|
|
32
|
+
matches: Match[]
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
export interface PatternCheckResult {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
36
|
+
success: boolean
|
|
37
|
+
error?: string
|
|
38
|
+
matches: Match[]
|
|
39
|
+
summary: {
|
|
40
|
+
total: number
|
|
41
|
+
bySeverity: Record<string, number>
|
|
42
|
+
byCategory: Record<string, number>
|
|
43
|
+
}
|
|
44
|
+
sources: MatchSource[]
|
|
45
|
+
patternsChecked: number
|
|
46
|
+
executionTime: number
|
|
47
|
+
target: string
|
|
48
|
+
patternVersion?: string
|
|
41
49
|
}
|
|
42
50
|
|
|
43
51
|
type PatternCheckArgs = {
|
|
44
|
-
target: string
|
|
45
|
-
patterns?: string[]
|
|
46
|
-
include_scvd?: boolean
|
|
47
|
-
}
|
|
52
|
+
target: string
|
|
53
|
+
patterns?: string[]
|
|
54
|
+
include_scvd?: boolean
|
|
55
|
+
}
|
|
48
56
|
|
|
49
57
|
type PatternCheckDependencies = {
|
|
50
|
-
loadIndexFn?: (filePath: string) => Promise<ScvdIndex | null
|
|
58
|
+
loadIndexFn?: (filePath: string) => Promise<ScvdIndex | null>
|
|
51
59
|
searchIndexFn?: (
|
|
52
60
|
index: ScvdIndex,
|
|
53
|
-
query: { swc?: string; severity?: string; keyword?: string; limit?: number }
|
|
54
|
-
) => ScvdIndexEntry[]
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
type
|
|
58
|
-
name: string
|
|
59
|
-
category: string
|
|
60
|
-
severity: Match["severity"]
|
|
61
|
-
regex: RegExp
|
|
62
|
-
description: string
|
|
63
|
-
exploitReference?: string
|
|
64
|
-
source?: PatternSource
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export const PATTERN_PACK_VERSION = "1.0.0"
|
|
68
|
-
|
|
69
|
-
const BUILTIN_PATTERNS: BuiltinPattern[] = [
|
|
70
|
-
{
|
|
71
|
-
name: "reentrancy",
|
|
72
|
-
category: "reentrancy",
|
|
73
|
-
severity: "High",
|
|
74
|
-
regex: /\.call\{value:/,
|
|
75
|
-
description: "Potential reentrancy: ETH transfer via low-level call",
|
|
76
|
-
exploitReference: "DAO hack ($60M), 2016",
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: "tx-origin-auth",
|
|
80
|
-
category: "access-control",
|
|
81
|
-
severity: "High",
|
|
82
|
-
regex: /tx\.origin/,
|
|
83
|
-
description: "Use of tx.origin for authorization - vulnerable to phishing",
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: "selfdestruct",
|
|
87
|
-
category: "access-control",
|
|
88
|
-
severity: "High",
|
|
89
|
-
regex: /selfdestruct\(|suicide\(/,
|
|
90
|
-
description: "Contract uses selfdestruct - can destroy contract",
|
|
91
|
-
},
|
|
92
|
-
{
|
|
93
|
-
name: "delegatecall",
|
|
94
|
-
category: "delegatecall",
|
|
95
|
-
severity: "High",
|
|
96
|
-
regex: /\.delegatecall\(/,
|
|
97
|
-
description: "Use of delegatecall - can overwrite storage",
|
|
98
|
-
},
|
|
99
|
-
{
|
|
100
|
-
name: "missing-zero-check",
|
|
101
|
-
category: "access-control",
|
|
102
|
-
severity: "Medium",
|
|
103
|
-
regex: /address\(0\)/,
|
|
104
|
-
description: "Potential missing zero-address validation",
|
|
105
|
-
},
|
|
106
|
-
];
|
|
61
|
+
query: { swc?: string; severity?: string; keyword?: string; limit?: number },
|
|
62
|
+
) => ScvdIndexEntry[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
type LoadedPattern = {
|
|
66
|
+
name: string
|
|
67
|
+
category: string
|
|
68
|
+
severity: Match["severity"]
|
|
69
|
+
regex: RegExp
|
|
70
|
+
description: string
|
|
71
|
+
exploitReference?: string
|
|
72
|
+
source?: PatternSource
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const PATTERN_PACK_VERSION = "1.0.0"
|
|
107
76
|
|
|
108
77
|
const CATEGORY_TO_SWC: Record<string, string[]> = {
|
|
109
78
|
reentrancy: ["SWC-107"],
|
|
@@ -112,24 +81,25 @@ const CATEGORY_TO_SWC: Record<string, string[]> = {
|
|
|
112
81
|
delegatecall: ["SWC-112"],
|
|
113
82
|
"signature-replay": ["SWC-121"],
|
|
114
83
|
"integer-overflow": ["SWC-101"],
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
84
|
+
governance: ["SWC-105", "SWC-106"],
|
|
85
|
+
"front-running": ["SWC-114"],
|
|
86
|
+
"logic-error": ["SWC-101", "SWC-116"],
|
|
87
|
+
"gas-optimization": ["SWC-128"],
|
|
88
|
+
dos: ["SWC-128"],
|
|
89
|
+
}
|
|
120
90
|
|
|
121
91
|
function normalizeSeverity(value: string): Match["severity"] {
|
|
122
|
-
if (value === "Critical") return "Critical"
|
|
123
|
-
if (value === "High") return "High"
|
|
124
|
-
if (value === "Medium") return "Medium"
|
|
125
|
-
if (value === "Low") return "Low"
|
|
126
|
-
return "Informational"
|
|
92
|
+
if (value === "Critical") return "Critical"
|
|
93
|
+
if (value === "High") return "High"
|
|
94
|
+
if (value === "Medium") return "Medium"
|
|
95
|
+
if (value === "Low") return "Low"
|
|
96
|
+
return "Informational"
|
|
127
97
|
}
|
|
128
98
|
|
|
129
99
|
function normalizePatternDefinitions(
|
|
130
100
|
patterns: PatternDefinition[],
|
|
131
|
-
source: PatternSource
|
|
132
|
-
):
|
|
101
|
+
source: PatternSource,
|
|
102
|
+
): LoadedPattern[] {
|
|
133
103
|
return patterns.map((patternDef) => ({
|
|
134
104
|
name: patternDef.name,
|
|
135
105
|
category: patternDef.category,
|
|
@@ -138,55 +108,55 @@ function normalizePatternDefinitions(
|
|
|
138
108
|
description: patternDef.description,
|
|
139
109
|
...(patternDef.exploit_ref ? { exploitReference: patternDef.exploit_ref } : {}),
|
|
140
110
|
source,
|
|
141
|
-
}))
|
|
111
|
+
}))
|
|
142
112
|
}
|
|
143
113
|
|
|
144
114
|
function uniqueScvdEntries(entries: ScvdIndexEntry[]): ScvdIndexEntry[] {
|
|
145
|
-
const deduped = new Map<string, ScvdIndexEntry>()
|
|
115
|
+
const deduped = new Map<string, ScvdIndexEntry>()
|
|
146
116
|
for (const entry of entries) {
|
|
147
|
-
deduped.set(entry.id, entry)
|
|
117
|
+
deduped.set(entry.id, entry)
|
|
148
118
|
}
|
|
149
|
-
return Array.from(deduped.values())
|
|
119
|
+
return Array.from(deduped.values())
|
|
150
120
|
}
|
|
151
121
|
|
|
152
122
|
async function collectScvdMatches(
|
|
153
123
|
matches: Match[],
|
|
154
|
-
dependencies: Required<PatternCheckDependencies
|
|
124
|
+
dependencies: Required<PatternCheckDependencies>,
|
|
155
125
|
): Promise<Match[]> {
|
|
156
|
-
const detectedCategories = new Set<string>()
|
|
126
|
+
const detectedCategories = new Set<string>()
|
|
157
127
|
for (const match of matches) {
|
|
158
|
-
const category = match.category
|
|
128
|
+
const category = match.category
|
|
159
129
|
if (category) {
|
|
160
|
-
detectedCategories.add(category)
|
|
130
|
+
detectedCategories.add(category)
|
|
161
131
|
}
|
|
162
132
|
}
|
|
163
133
|
|
|
164
134
|
if (detectedCategories.size === 0) {
|
|
165
|
-
return []
|
|
135
|
+
return []
|
|
166
136
|
}
|
|
167
137
|
|
|
168
|
-
const swcCodes = new Set<string>()
|
|
138
|
+
const swcCodes = new Set<string>()
|
|
169
139
|
for (const category of detectedCategories) {
|
|
170
|
-
const mappedSwcs = CATEGORY_TO_SWC[category] ?? []
|
|
140
|
+
const mappedSwcs = CATEGORY_TO_SWC[category] ?? []
|
|
171
141
|
for (const swcCode of mappedSwcs) {
|
|
172
|
-
swcCodes.add(swcCode)
|
|
142
|
+
swcCodes.add(swcCode)
|
|
173
143
|
}
|
|
174
144
|
}
|
|
175
145
|
|
|
176
146
|
if (swcCodes.size === 0) {
|
|
177
|
-
return []
|
|
147
|
+
return []
|
|
178
148
|
}
|
|
179
149
|
|
|
180
|
-
const indexPath = join(os.homedir(), ".cache", "solidity-argus", "scvd-index.json")
|
|
181
|
-
const index = await dependencies.loadIndexFn(indexPath)
|
|
150
|
+
const indexPath = join(os.homedir(), ".cache", "solidity-argus", "scvd-index.json")
|
|
151
|
+
const index = await dependencies.loadIndexFn(indexPath)
|
|
182
152
|
|
|
183
153
|
if (!index) {
|
|
184
|
-
return []
|
|
154
|
+
return []
|
|
185
155
|
}
|
|
186
156
|
|
|
187
|
-
const entries: ScvdIndexEntry[] = []
|
|
157
|
+
const entries: ScvdIndexEntry[] = []
|
|
188
158
|
for (const swcCode of swcCodes) {
|
|
189
|
-
entries.push(...dependencies.searchIndexFn(index, { swc: swcCode }))
|
|
159
|
+
entries.push(...dependencies.searchIndexFn(index, { swc: swcCode }))
|
|
190
160
|
}
|
|
191
161
|
|
|
192
162
|
return uniqueScvdEntries(entries).map((entry) => ({
|
|
@@ -196,83 +166,86 @@ async function collectScvdMatches(
|
|
|
196
166
|
lines: [1, 1],
|
|
197
167
|
description: entry.title,
|
|
198
168
|
exploitReference: entry.repoUrl,
|
|
199
|
-
}))
|
|
169
|
+
}))
|
|
200
170
|
}
|
|
201
171
|
|
|
202
172
|
function collectSolidityFiles(target: string, maxDepth = 8): string[] {
|
|
203
|
-
const absoluteTarget = resolve(target)
|
|
204
|
-
let stats: ReturnType<typeof statSync
|
|
173
|
+
const absoluteTarget = resolve(target)
|
|
174
|
+
let stats: ReturnType<typeof statSync>
|
|
205
175
|
|
|
206
176
|
try {
|
|
207
|
-
stats = statSync(absoluteTarget)
|
|
177
|
+
stats = statSync(absoluteTarget)
|
|
208
178
|
} catch {
|
|
209
|
-
|
|
179
|
+
return []
|
|
210
180
|
}
|
|
211
181
|
|
|
212
182
|
if (stats.isFile()) {
|
|
213
|
-
return extname(absoluteTarget) === ".sol" ? [absoluteTarget] : []
|
|
183
|
+
return extname(absoluteTarget) === ".sol" ? [absoluteTarget] : []
|
|
214
184
|
}
|
|
215
185
|
|
|
216
186
|
if (!stats.isDirectory()) {
|
|
217
|
-
return []
|
|
187
|
+
return []
|
|
218
188
|
}
|
|
219
189
|
|
|
220
|
-
const discovered: string[] = []
|
|
221
|
-
const stack: Array<{ path: string; depth: number }> = [{ path: absoluteTarget, depth: 0 }]
|
|
190
|
+
const discovered: string[] = []
|
|
191
|
+
const stack: Array<{ path: string; depth: number }> = [{ path: absoluteTarget, depth: 0 }]
|
|
222
192
|
|
|
223
193
|
while (stack.length > 0) {
|
|
224
|
-
const current = stack.pop()
|
|
194
|
+
const current = stack.pop()
|
|
225
195
|
if (!current || current.depth > maxDepth) {
|
|
226
|
-
continue
|
|
196
|
+
continue
|
|
227
197
|
}
|
|
228
198
|
|
|
229
|
-
const entries = readdirSync(current.path, { withFileTypes: true })
|
|
199
|
+
const entries = readdirSync(current.path, { withFileTypes: true })
|
|
230
200
|
for (const entry of entries) {
|
|
231
|
-
const fullPath = resolve(current.path, entry.name)
|
|
201
|
+
const fullPath = resolve(current.path, entry.name)
|
|
232
202
|
if (entry.isDirectory()) {
|
|
233
|
-
stack.push({ path: fullPath, depth: current.depth + 1 })
|
|
234
|
-
continue
|
|
203
|
+
stack.push({ path: fullPath, depth: current.depth + 1 })
|
|
204
|
+
continue
|
|
235
205
|
}
|
|
236
206
|
|
|
237
207
|
if (entry.isFile() && extname(entry.name) === ".sol") {
|
|
238
|
-
discovered.push(fullPath)
|
|
208
|
+
discovered.push(fullPath)
|
|
239
209
|
}
|
|
240
210
|
}
|
|
241
211
|
}
|
|
242
212
|
|
|
243
|
-
return discovered
|
|
213
|
+
return discovered
|
|
244
214
|
}
|
|
245
215
|
|
|
246
216
|
function lineNumberAt(content: string, index: number): number {
|
|
247
217
|
if (index <= 0) {
|
|
248
|
-
return 1
|
|
218
|
+
return 1
|
|
249
219
|
}
|
|
250
220
|
|
|
251
|
-
let line = 1
|
|
221
|
+
let line = 1
|
|
252
222
|
for (let i = 0; i < index && i < content.length; i += 1) {
|
|
253
223
|
if (content[i] === "\n") {
|
|
254
|
-
line += 1
|
|
224
|
+
line += 1
|
|
255
225
|
}
|
|
256
226
|
}
|
|
257
|
-
return line
|
|
227
|
+
return line
|
|
258
228
|
}
|
|
259
229
|
|
|
260
230
|
function lineWindow(content: string, index: number): [number, number] {
|
|
261
|
-
const linesCount = content.split("\n").length
|
|
262
|
-
const line = lineNumberAt(content, index)
|
|
263
|
-
const start = Math.max(1, line - 5)
|
|
264
|
-
const end = Math.min(linesCount, line + 5)
|
|
265
|
-
return [start, end]
|
|
231
|
+
const linesCount = content.split("\n").length
|
|
232
|
+
const line = lineNumberAt(content, index)
|
|
233
|
+
const start = Math.max(1, line - 5)
|
|
234
|
+
const end = Math.min(linesCount, line + 5)
|
|
235
|
+
return [start, end]
|
|
266
236
|
}
|
|
267
237
|
|
|
268
|
-
function findMatches(file: string, patterns:
|
|
269
|
-
const content = readFileSync(file, "utf8")
|
|
270
|
-
const matches: Match[] = []
|
|
238
|
+
function findMatches(file: string, patterns: LoadedPattern[]): Match[] {
|
|
239
|
+
const content = readFileSync(file, "utf8")
|
|
240
|
+
const matches: Match[] = []
|
|
271
241
|
|
|
272
242
|
for (const pattern of patterns) {
|
|
273
|
-
const regex = new RegExp(
|
|
243
|
+
const regex = new RegExp(
|
|
244
|
+
pattern.regex.source,
|
|
245
|
+
pattern.regex.flags.includes("g") ? pattern.regex.flags : `${pattern.regex.flags}g`,
|
|
246
|
+
)
|
|
274
247
|
for (const found of content.matchAll(regex)) {
|
|
275
|
-
const index = found.index ?? 0
|
|
248
|
+
const index = found.index ?? 0
|
|
276
249
|
matches.push({
|
|
277
250
|
pattern: pattern.name,
|
|
278
251
|
severity: pattern.severity,
|
|
@@ -280,63 +253,70 @@ function findMatches(file: string, patterns: BuiltinPattern[]): Match[] {
|
|
|
280
253
|
lines: lineWindow(content, index),
|
|
281
254
|
description: pattern.description,
|
|
282
255
|
exploitReference: pattern.exploitReference,
|
|
283
|
-
patternSource: pattern.source ?? "
|
|
256
|
+
patternSource: pattern.source ?? "skill",
|
|
284
257
|
category: pattern.category,
|
|
285
|
-
})
|
|
258
|
+
})
|
|
286
259
|
}
|
|
287
260
|
}
|
|
288
261
|
|
|
289
|
-
return matches
|
|
262
|
+
return matches
|
|
290
263
|
}
|
|
291
264
|
|
|
292
265
|
function selectPatterns(
|
|
293
|
-
availablePatterns:
|
|
294
|
-
categories?: string[]
|
|
295
|
-
):
|
|
266
|
+
availablePatterns: LoadedPattern[],
|
|
267
|
+
categories?: string[],
|
|
268
|
+
): LoadedPattern[] {
|
|
296
269
|
if (!categories || categories.length === 0) {
|
|
297
|
-
return availablePatterns
|
|
270
|
+
return availablePatterns
|
|
298
271
|
}
|
|
299
272
|
|
|
300
|
-
const set = new Set(categories)
|
|
301
|
-
return availablePatterns.filter((pattern) => set.has(pattern.category))
|
|
273
|
+
const set = new Set(categories)
|
|
274
|
+
return availablePatterns.filter((pattern) => set.has(pattern.category))
|
|
302
275
|
}
|
|
303
276
|
|
|
304
277
|
export async function executePatternCheck(
|
|
305
278
|
args: PatternCheckArgs,
|
|
306
279
|
context: ToolContext,
|
|
307
|
-
deps: PatternCheckDependencies = {}
|
|
280
|
+
deps: PatternCheckDependencies = {},
|
|
308
281
|
): Promise<PatternCheckResult> {
|
|
309
282
|
const dependencies: Required<PatternCheckDependencies> = {
|
|
310
283
|
loadIndexFn: loadIndex,
|
|
311
284
|
searchIndexFn: searchIndex,
|
|
312
285
|
...deps,
|
|
313
|
-
}
|
|
286
|
+
}
|
|
314
287
|
|
|
315
|
-
const startedAt = Date.now()
|
|
316
|
-
context.metadata({ title: `Pattern check: ${args.target}` })
|
|
288
|
+
const startedAt = Date.now()
|
|
289
|
+
context.metadata({ title: `Pattern check: ${args.target}` })
|
|
317
290
|
|
|
318
|
-
const skillsDir = join(dirname(dirname(__dirname)), "skills")
|
|
319
|
-
const
|
|
320
|
-
const skillDetectionRules = extractDetectionRulesFromSkills(skillsDir);
|
|
291
|
+
const skillsDir = join(dirname(dirname(__dirname)), "skills")
|
|
292
|
+
const skillDetectionRules = extractDetectionRulesFromSkills(skillsDir)
|
|
321
293
|
|
|
322
|
-
const allPatterns:
|
|
323
|
-
...BUILTIN_PATTERNS.map((pattern) => ({ ...pattern, source: "builtin" as const })),
|
|
324
|
-
...normalizePatternDefinitions(yamlPatterns, "yaml"),
|
|
294
|
+
const allPatterns: LoadedPattern[] = [
|
|
325
295
|
...normalizePatternDefinitions(skillDetectionRules, "skill"),
|
|
326
|
-
]
|
|
296
|
+
]
|
|
327
297
|
|
|
328
|
-
const selectedPatterns = selectPatterns(allPatterns, args.patterns)
|
|
329
|
-
const solidityFiles = collectSolidityFiles(args.target)
|
|
298
|
+
const selectedPatterns = selectPatterns(allPatterns, args.patterns)
|
|
299
|
+
const solidityFiles = collectSolidityFiles(args.target)
|
|
330
300
|
if (solidityFiles.length === 0) {
|
|
331
|
-
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
error: `No Solidity files found for target: ${args.target}`,
|
|
304
|
+
matches: [],
|
|
305
|
+
summary: { total: 0, bySeverity: {}, byCategory: {} },
|
|
306
|
+
sources: [],
|
|
307
|
+
patternsChecked: selectedPatterns.length,
|
|
308
|
+
executionTime: Date.now() - startedAt,
|
|
309
|
+
target: args.target,
|
|
310
|
+
patternVersion: PATTERN_PACK_VERSION,
|
|
311
|
+
}
|
|
332
312
|
}
|
|
333
313
|
|
|
334
|
-
const sourceMatches: Match[] = []
|
|
314
|
+
const sourceMatches: Match[] = []
|
|
335
315
|
for (const solidityFile of solidityFiles) {
|
|
336
316
|
if (context.abort.aborted) {
|
|
337
|
-
throw new Error("pattern check aborted")
|
|
317
|
+
throw new Error("pattern check aborted")
|
|
338
318
|
}
|
|
339
|
-
sourceMatches.push(...findMatches(solidityFile, selectedPatterns))
|
|
319
|
+
sourceMatches.push(...findMatches(solidityFile, selectedPatterns))
|
|
340
320
|
}
|
|
341
321
|
|
|
342
322
|
const sources: MatchSource[] = [
|
|
@@ -344,27 +324,42 @@ export async function executePatternCheck(
|
|
|
344
324
|
source: "pattern-db",
|
|
345
325
|
matches: sourceMatches,
|
|
346
326
|
},
|
|
347
|
-
]
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
327
|
+
]
|
|
328
|
+
|
|
329
|
+
if (args.include_scvd === true) {
|
|
330
|
+
try {
|
|
331
|
+
const scvdMatches = await collectScvdMatches(sourceMatches, dependencies)
|
|
332
|
+
if (scvdMatches.length > 0) {
|
|
333
|
+
sources.push({
|
|
334
|
+
source: "scvd",
|
|
335
|
+
matches: scvdMatches,
|
|
336
|
+
})
|
|
337
|
+
}
|
|
338
|
+
} catch (_e) {
|
|
339
|
+
logger.debug("SCVD enrichment failed, continuing without SCVD matches")
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const allMatches = sources.flatMap((s) => s.matches)
|
|
344
|
+
const bySeverity: Record<string, number> = {}
|
|
345
|
+
const byCategory: Record<string, number> = {}
|
|
346
|
+
for (const m of allMatches) {
|
|
347
|
+
bySeverity[m.severity] = (bySeverity[m.severity] ?? 0) + 1
|
|
348
|
+
if (m.category) {
|
|
349
|
+
byCategory[m.category] = (byCategory[m.category] ?? 0) + 1
|
|
350
|
+
}
|
|
351
|
+
}
|
|
360
352
|
|
|
361
353
|
return {
|
|
354
|
+
success: true,
|
|
355
|
+
matches: allMatches,
|
|
356
|
+
summary: { total: allMatches.length, bySeverity, byCategory },
|
|
362
357
|
sources,
|
|
363
358
|
patternsChecked: selectedPatterns.length,
|
|
364
359
|
executionTime: Date.now() - startedAt,
|
|
365
360
|
target: args.target,
|
|
366
361
|
patternVersion: PATTERN_PACK_VERSION,
|
|
367
|
-
}
|
|
362
|
+
}
|
|
368
363
|
}
|
|
369
364
|
|
|
370
365
|
export const patternCheckerTool = tool({
|
|
@@ -375,7 +370,7 @@ export const patternCheckerTool = tool({
|
|
|
375
370
|
include_scvd: tool.schema.boolean().default(true),
|
|
376
371
|
},
|
|
377
372
|
async execute(args, context) {
|
|
378
|
-
const result = await executePatternCheck(args, context)
|
|
379
|
-
return JSON.stringify(result)
|
|
373
|
+
const result = await executePatternCheck(args, context)
|
|
374
|
+
return JSON.stringify(result)
|
|
380
375
|
},
|
|
381
|
-
})
|
|
376
|
+
})
|