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.
- package/AGENTS.md +3 -3
- package/README.md +93 -37
- package/package.json +34 -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 +34 -7
- package/src/agents/pythia-prompt.ts +13 -4
- package/src/agents/scribe-prompt.ts +20 -2
- package/src/agents/sentinel-prompt.ts +45 -5
- 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 +6 -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/system-prompt-hook.ts +18 -1
- package/src/hooks/tool-tracking-hook.ts +110 -51
- package/src/hooks/types.ts +2 -1
- package/src/index.ts +24 -37
- 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 +203 -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 +142 -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 +201 -158
- package/src/tools/gas-analysis-tool.ts +264 -0
- package/src/tools/pattern-checker-tool.ts +203 -191
- package/src/tools/pattern-loader.ts +5 -111
- package/src/tools/pattern-schema.ts +3 -0
- package/src/tools/proxy-detection-tool.ts +224 -0
- package/src/tools/report-generator-tool.ts +305 -206
- package/src/tools/slither-tool.ts +266 -218
- package/src/tools/solodit-search-tool.ts +235 -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 +175 -75
- 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,81 @@
|
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
+
export 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
|
+
confidence?: "High" | "Medium" | "Low"
|
|
74
|
+
applies_to?: string[]
|
|
75
|
+
exclude_if?: string[]
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export const PATTERN_PACK_VERSION = "1.0.0"
|
|
107
79
|
|
|
108
80
|
const CATEGORY_TO_SWC: Record<string, string[]> = {
|
|
109
81
|
reentrancy: ["SWC-107"],
|
|
@@ -112,24 +84,25 @@ const CATEGORY_TO_SWC: Record<string, string[]> = {
|
|
|
112
84
|
delegatecall: ["SWC-112"],
|
|
113
85
|
"signature-replay": ["SWC-121"],
|
|
114
86
|
"integer-overflow": ["SWC-101"],
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
87
|
+
governance: ["SWC-105", "SWC-106"],
|
|
88
|
+
"front-running": ["SWC-114"],
|
|
89
|
+
"logic-error": ["SWC-101", "SWC-116"],
|
|
90
|
+
"gas-optimization": ["SWC-128"],
|
|
91
|
+
dos: ["SWC-128"],
|
|
92
|
+
}
|
|
120
93
|
|
|
121
94
|
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"
|
|
95
|
+
if (value === "Critical") return "Critical"
|
|
96
|
+
if (value === "High") return "High"
|
|
97
|
+
if (value === "Medium") return "Medium"
|
|
98
|
+
if (value === "Low") return "Low"
|
|
99
|
+
return "Informational"
|
|
127
100
|
}
|
|
128
101
|
|
|
129
102
|
function normalizePatternDefinitions(
|
|
130
103
|
patterns: PatternDefinition[],
|
|
131
|
-
source: PatternSource
|
|
132
|
-
):
|
|
104
|
+
source: PatternSource,
|
|
105
|
+
): LoadedPattern[] {
|
|
133
106
|
return patterns.map((patternDef) => ({
|
|
134
107
|
name: patternDef.name,
|
|
135
108
|
category: patternDef.category,
|
|
@@ -137,56 +110,59 @@ function normalizePatternDefinitions(
|
|
|
137
110
|
regex: new RegExp(patternDef.regex),
|
|
138
111
|
description: patternDef.description,
|
|
139
112
|
...(patternDef.exploit_ref ? { exploitReference: patternDef.exploit_ref } : {}),
|
|
113
|
+
...(patternDef.confidence ? { confidence: patternDef.confidence } : {}),
|
|
114
|
+
...(patternDef.applies_to ? { applies_to: patternDef.applies_to } : {}),
|
|
115
|
+
...(patternDef.exclude_if ? { exclude_if: patternDef.exclude_if } : {}),
|
|
140
116
|
source,
|
|
141
|
-
}))
|
|
117
|
+
}))
|
|
142
118
|
}
|
|
143
119
|
|
|
144
120
|
function uniqueScvdEntries(entries: ScvdIndexEntry[]): ScvdIndexEntry[] {
|
|
145
|
-
const deduped = new Map<string, ScvdIndexEntry>()
|
|
121
|
+
const deduped = new Map<string, ScvdIndexEntry>()
|
|
146
122
|
for (const entry of entries) {
|
|
147
|
-
deduped.set(entry.id, entry)
|
|
123
|
+
deduped.set(entry.id, entry)
|
|
148
124
|
}
|
|
149
|
-
return Array.from(deduped.values())
|
|
125
|
+
return Array.from(deduped.values())
|
|
150
126
|
}
|
|
151
127
|
|
|
152
128
|
async function collectScvdMatches(
|
|
153
129
|
matches: Match[],
|
|
154
|
-
dependencies: Required<PatternCheckDependencies
|
|
130
|
+
dependencies: Required<PatternCheckDependencies>,
|
|
155
131
|
): Promise<Match[]> {
|
|
156
|
-
const detectedCategories = new Set<string>()
|
|
132
|
+
const detectedCategories = new Set<string>()
|
|
157
133
|
for (const match of matches) {
|
|
158
|
-
const category = match.category
|
|
134
|
+
const category = match.category
|
|
159
135
|
if (category) {
|
|
160
|
-
detectedCategories.add(category)
|
|
136
|
+
detectedCategories.add(category)
|
|
161
137
|
}
|
|
162
138
|
}
|
|
163
139
|
|
|
164
140
|
if (detectedCategories.size === 0) {
|
|
165
|
-
return []
|
|
141
|
+
return []
|
|
166
142
|
}
|
|
167
143
|
|
|
168
|
-
const swcCodes = new Set<string>()
|
|
144
|
+
const swcCodes = new Set<string>()
|
|
169
145
|
for (const category of detectedCategories) {
|
|
170
|
-
const mappedSwcs = CATEGORY_TO_SWC[category] ?? []
|
|
146
|
+
const mappedSwcs = CATEGORY_TO_SWC[category] ?? []
|
|
171
147
|
for (const swcCode of mappedSwcs) {
|
|
172
|
-
swcCodes.add(swcCode)
|
|
148
|
+
swcCodes.add(swcCode)
|
|
173
149
|
}
|
|
174
150
|
}
|
|
175
151
|
|
|
176
152
|
if (swcCodes.size === 0) {
|
|
177
|
-
return []
|
|
153
|
+
return []
|
|
178
154
|
}
|
|
179
155
|
|
|
180
|
-
const indexPath = join(os.homedir(), ".cache", "solidity-argus", "scvd-index.json")
|
|
181
|
-
const index = await dependencies.loadIndexFn(indexPath)
|
|
156
|
+
const indexPath = join(os.homedir(), ".cache", "solidity-argus", "scvd-index.json")
|
|
157
|
+
const index = await dependencies.loadIndexFn(indexPath)
|
|
182
158
|
|
|
183
159
|
if (!index) {
|
|
184
|
-
return []
|
|
160
|
+
return []
|
|
185
161
|
}
|
|
186
162
|
|
|
187
|
-
const entries: ScvdIndexEntry[] = []
|
|
163
|
+
const entries: ScvdIndexEntry[] = []
|
|
188
164
|
for (const swcCode of swcCodes) {
|
|
189
|
-
entries.push(...dependencies.searchIndexFn(index, { swc: swcCode }))
|
|
165
|
+
entries.push(...dependencies.searchIndexFn(index, { swc: swcCode }))
|
|
190
166
|
}
|
|
191
167
|
|
|
192
168
|
return uniqueScvdEntries(entries).map((entry) => ({
|
|
@@ -196,83 +172,97 @@ async function collectScvdMatches(
|
|
|
196
172
|
lines: [1, 1],
|
|
197
173
|
description: entry.title,
|
|
198
174
|
exploitReference: entry.repoUrl,
|
|
199
|
-
}))
|
|
175
|
+
}))
|
|
200
176
|
}
|
|
201
177
|
|
|
202
178
|
function collectSolidityFiles(target: string, maxDepth = 8): string[] {
|
|
203
|
-
const absoluteTarget = resolve(target)
|
|
204
|
-
let stats: ReturnType<typeof statSync
|
|
179
|
+
const absoluteTarget = resolve(target)
|
|
180
|
+
let stats: ReturnType<typeof statSync>
|
|
205
181
|
|
|
206
182
|
try {
|
|
207
|
-
stats = statSync(absoluteTarget)
|
|
183
|
+
stats = statSync(absoluteTarget)
|
|
208
184
|
} catch {
|
|
209
|
-
|
|
185
|
+
return []
|
|
210
186
|
}
|
|
211
187
|
|
|
212
188
|
if (stats.isFile()) {
|
|
213
|
-
return extname(absoluteTarget) === ".sol" ? [absoluteTarget] : []
|
|
189
|
+
return extname(absoluteTarget) === ".sol" ? [absoluteTarget] : []
|
|
214
190
|
}
|
|
215
191
|
|
|
216
192
|
if (!stats.isDirectory()) {
|
|
217
|
-
return []
|
|
193
|
+
return []
|
|
218
194
|
}
|
|
219
195
|
|
|
220
|
-
const discovered: string[] = []
|
|
221
|
-
const stack: Array<{ path: string; depth: number }> = [{ path: absoluteTarget, depth: 0 }]
|
|
196
|
+
const discovered: string[] = []
|
|
197
|
+
const stack: Array<{ path: string; depth: number }> = [{ path: absoluteTarget, depth: 0 }]
|
|
222
198
|
|
|
223
199
|
while (stack.length > 0) {
|
|
224
|
-
const current = stack.pop()
|
|
200
|
+
const current = stack.pop()
|
|
225
201
|
if (!current || current.depth > maxDepth) {
|
|
226
|
-
continue
|
|
202
|
+
continue
|
|
227
203
|
}
|
|
228
204
|
|
|
229
|
-
const entries = readdirSync(current.path, { withFileTypes: true })
|
|
205
|
+
const entries = readdirSync(current.path, { withFileTypes: true })
|
|
230
206
|
for (const entry of entries) {
|
|
231
|
-
const fullPath = resolve(current.path, entry.name)
|
|
207
|
+
const fullPath = resolve(current.path, entry.name)
|
|
232
208
|
if (entry.isDirectory()) {
|
|
233
|
-
stack.push({ path: fullPath, depth: current.depth + 1 })
|
|
234
|
-
continue
|
|
209
|
+
stack.push({ path: fullPath, depth: current.depth + 1 })
|
|
210
|
+
continue
|
|
235
211
|
}
|
|
236
212
|
|
|
237
213
|
if (entry.isFile() && extname(entry.name) === ".sol") {
|
|
238
|
-
discovered.push(fullPath)
|
|
214
|
+
discovered.push(fullPath)
|
|
239
215
|
}
|
|
240
216
|
}
|
|
241
217
|
}
|
|
242
218
|
|
|
243
|
-
return discovered
|
|
219
|
+
return discovered
|
|
244
220
|
}
|
|
245
221
|
|
|
246
222
|
function lineNumberAt(content: string, index: number): number {
|
|
247
223
|
if (index <= 0) {
|
|
248
|
-
return 1
|
|
224
|
+
return 1
|
|
249
225
|
}
|
|
250
226
|
|
|
251
|
-
let line = 1
|
|
227
|
+
let line = 1
|
|
252
228
|
for (let i = 0; i < index && i < content.length; i += 1) {
|
|
253
229
|
if (content[i] === "\n") {
|
|
254
|
-
line += 1
|
|
230
|
+
line += 1
|
|
255
231
|
}
|
|
256
232
|
}
|
|
257
|
-
return line
|
|
233
|
+
return line
|
|
258
234
|
}
|
|
259
235
|
|
|
260
236
|
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]
|
|
237
|
+
const linesCount = content.split("\n").length
|
|
238
|
+
const line = lineNumberAt(content, index)
|
|
239
|
+
const start = Math.max(1, line - 5)
|
|
240
|
+
const end = Math.min(linesCount, line + 5)
|
|
241
|
+
return [start, end]
|
|
266
242
|
}
|
|
267
243
|
|
|
268
|
-
function findMatches(file: string, patterns:
|
|
269
|
-
const content = readFileSync(file, "utf8")
|
|
270
|
-
const matches: Match[] = []
|
|
244
|
+
export function findMatches(file: string, patterns: LoadedPattern[]): Match[] {
|
|
245
|
+
const content = readFileSync(file, "utf8")
|
|
246
|
+
const matches: Match[] = []
|
|
247
|
+
|
|
248
|
+
// Strip comments and string literals to reduce false positives.
|
|
249
|
+
// Use a space-preserving approach so line numbers remain valid.
|
|
250
|
+
// Order: multi-line comments first (can contain //), then single-line, then strings.
|
|
251
|
+
const stripped = content
|
|
252
|
+
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "))
|
|
253
|
+
.replace(/\/\/[^\n]*/g, (m) => " ".repeat(m.length))
|
|
254
|
+
.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g, (m) => {
|
|
255
|
+
const quote = m[0]
|
|
256
|
+
return `${quote}${" ".repeat(Math.max(0, m.length - 2))}${quote}`
|
|
257
|
+
})
|
|
271
258
|
|
|
272
259
|
for (const pattern of patterns) {
|
|
273
|
-
const regex = new RegExp(
|
|
274
|
-
|
|
275
|
-
|
|
260
|
+
const regex = new RegExp(
|
|
261
|
+
pattern.regex.source,
|
|
262
|
+
pattern.regex.flags.includes("g") ? pattern.regex.flags : `${pattern.regex.flags}g`,
|
|
263
|
+
)
|
|
264
|
+
for (const found of stripped.matchAll(regex)) {
|
|
265
|
+
const index = found.index ?? 0
|
|
276
266
|
matches.push({
|
|
277
267
|
pattern: pattern.name,
|
|
278
268
|
severity: pattern.severity,
|
|
@@ -280,63 +270,70 @@ function findMatches(file: string, patterns: BuiltinPattern[]): Match[] {
|
|
|
280
270
|
lines: lineWindow(content, index),
|
|
281
271
|
description: pattern.description,
|
|
282
272
|
exploitReference: pattern.exploitReference,
|
|
283
|
-
patternSource: pattern.source ?? "
|
|
273
|
+
patternSource: pattern.source ?? "skill",
|
|
284
274
|
category: pattern.category,
|
|
285
|
-
})
|
|
275
|
+
})
|
|
286
276
|
}
|
|
287
277
|
}
|
|
288
278
|
|
|
289
|
-
return matches
|
|
279
|
+
return matches
|
|
290
280
|
}
|
|
291
281
|
|
|
292
282
|
function selectPatterns(
|
|
293
|
-
availablePatterns:
|
|
294
|
-
categories?: string[]
|
|
295
|
-
):
|
|
283
|
+
availablePatterns: LoadedPattern[],
|
|
284
|
+
categories?: string[],
|
|
285
|
+
): LoadedPattern[] {
|
|
296
286
|
if (!categories || categories.length === 0) {
|
|
297
|
-
return availablePatterns
|
|
287
|
+
return availablePatterns
|
|
298
288
|
}
|
|
299
289
|
|
|
300
|
-
const set = new Set(categories)
|
|
301
|
-
return availablePatterns.filter((pattern) => set.has(pattern.category))
|
|
290
|
+
const set = new Set(categories)
|
|
291
|
+
return availablePatterns.filter((pattern) => set.has(pattern.category))
|
|
302
292
|
}
|
|
303
293
|
|
|
304
294
|
export async function executePatternCheck(
|
|
305
295
|
args: PatternCheckArgs,
|
|
306
296
|
context: ToolContext,
|
|
307
|
-
deps: PatternCheckDependencies = {}
|
|
297
|
+
deps: PatternCheckDependencies = {},
|
|
308
298
|
): Promise<PatternCheckResult> {
|
|
309
299
|
const dependencies: Required<PatternCheckDependencies> = {
|
|
310
300
|
loadIndexFn: loadIndex,
|
|
311
301
|
searchIndexFn: searchIndex,
|
|
312
302
|
...deps,
|
|
313
|
-
}
|
|
303
|
+
}
|
|
314
304
|
|
|
315
|
-
const startedAt = Date.now()
|
|
316
|
-
context.metadata({ title: `Pattern check: ${args.target}` })
|
|
305
|
+
const startedAt = Date.now()
|
|
306
|
+
context.metadata({ title: `Pattern check: ${args.target}` })
|
|
317
307
|
|
|
318
|
-
const skillsDir = join(dirname(dirname(__dirname)), "skills")
|
|
319
|
-
const
|
|
320
|
-
const skillDetectionRules = extractDetectionRulesFromSkills(skillsDir);
|
|
308
|
+
const skillsDir = join(dirname(dirname(__dirname)), "skills")
|
|
309
|
+
const skillDetectionRules = extractDetectionRulesFromSkills(skillsDir)
|
|
321
310
|
|
|
322
|
-
const allPatterns:
|
|
323
|
-
...BUILTIN_PATTERNS.map((pattern) => ({ ...pattern, source: "builtin" as const })),
|
|
324
|
-
...normalizePatternDefinitions(yamlPatterns, "yaml"),
|
|
311
|
+
const allPatterns: LoadedPattern[] = [
|
|
325
312
|
...normalizePatternDefinitions(skillDetectionRules, "skill"),
|
|
326
|
-
]
|
|
313
|
+
]
|
|
327
314
|
|
|
328
|
-
const selectedPatterns = selectPatterns(allPatterns, args.patterns)
|
|
329
|
-
const solidityFiles = collectSolidityFiles(args.target)
|
|
315
|
+
const selectedPatterns = selectPatterns(allPatterns, args.patterns)
|
|
316
|
+
const solidityFiles = collectSolidityFiles(args.target)
|
|
330
317
|
if (solidityFiles.length === 0) {
|
|
331
|
-
|
|
318
|
+
return {
|
|
319
|
+
success: false,
|
|
320
|
+
error: `No Solidity files found for target: ${args.target}`,
|
|
321
|
+
matches: [],
|
|
322
|
+
summary: { total: 0, bySeverity: {}, byCategory: {} },
|
|
323
|
+
sources: [],
|
|
324
|
+
patternsChecked: selectedPatterns.length,
|
|
325
|
+
executionTime: Date.now() - startedAt,
|
|
326
|
+
target: args.target,
|
|
327
|
+
patternVersion: PATTERN_PACK_VERSION,
|
|
328
|
+
}
|
|
332
329
|
}
|
|
333
330
|
|
|
334
|
-
const sourceMatches: Match[] = []
|
|
331
|
+
const sourceMatches: Match[] = []
|
|
335
332
|
for (const solidityFile of solidityFiles) {
|
|
336
333
|
if (context.abort.aborted) {
|
|
337
|
-
throw new Error("pattern check aborted")
|
|
334
|
+
throw new Error("pattern check aborted")
|
|
338
335
|
}
|
|
339
|
-
sourceMatches.push(...findMatches(solidityFile, selectedPatterns))
|
|
336
|
+
sourceMatches.push(...findMatches(solidityFile, selectedPatterns))
|
|
340
337
|
}
|
|
341
338
|
|
|
342
339
|
const sources: MatchSource[] = [
|
|
@@ -344,27 +341,42 @@ export async function executePatternCheck(
|
|
|
344
341
|
source: "pattern-db",
|
|
345
342
|
matches: sourceMatches,
|
|
346
343
|
},
|
|
347
|
-
]
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
344
|
+
]
|
|
345
|
+
|
|
346
|
+
if (args.include_scvd === true) {
|
|
347
|
+
try {
|
|
348
|
+
const scvdMatches = await collectScvdMatches(sourceMatches, dependencies)
|
|
349
|
+
if (scvdMatches.length > 0) {
|
|
350
|
+
sources.push({
|
|
351
|
+
source: "scvd",
|
|
352
|
+
matches: scvdMatches,
|
|
353
|
+
})
|
|
354
|
+
}
|
|
355
|
+
} catch (_e) {
|
|
356
|
+
logger.debug("SCVD enrichment failed, continuing without SCVD matches")
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
const allMatches = sources.flatMap((s) => s.matches)
|
|
361
|
+
const bySeverity: Record<string, number> = {}
|
|
362
|
+
const byCategory: Record<string, number> = {}
|
|
363
|
+
for (const m of allMatches) {
|
|
364
|
+
bySeverity[m.severity] = (bySeverity[m.severity] ?? 0) + 1
|
|
365
|
+
if (m.category) {
|
|
366
|
+
byCategory[m.category] = (byCategory[m.category] ?? 0) + 1
|
|
367
|
+
}
|
|
368
|
+
}
|
|
360
369
|
|
|
361
370
|
return {
|
|
371
|
+
success: true,
|
|
372
|
+
matches: allMatches,
|
|
373
|
+
summary: { total: allMatches.length, bySeverity, byCategory },
|
|
362
374
|
sources,
|
|
363
375
|
patternsChecked: selectedPatterns.length,
|
|
364
376
|
executionTime: Date.now() - startedAt,
|
|
365
377
|
target: args.target,
|
|
366
378
|
patternVersion: PATTERN_PACK_VERSION,
|
|
367
|
-
}
|
|
379
|
+
}
|
|
368
380
|
}
|
|
369
381
|
|
|
370
382
|
export const patternCheckerTool = tool({
|
|
@@ -375,7 +387,7 @@ export const patternCheckerTool = tool({
|
|
|
375
387
|
include_scvd: tool.schema.boolean().default(true),
|
|
376
388
|
},
|
|
377
389
|
async execute(args, context) {
|
|
378
|
-
const result = await executePatternCheck(args, context)
|
|
379
|
-
return JSON.stringify(result)
|
|
390
|
+
const result = await executePatternCheck(args, context)
|
|
391
|
+
return JSON.stringify(result)
|
|
380
392
|
},
|
|
381
|
-
})
|
|
393
|
+
})
|