solidity-argus 0.1.8 → 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 +229 -13
- package/package.json +37 -8
- package/skills/INVENTORY.md +88 -57
- package/skills/README.md +72 -6
- 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/checklists/cyfrin-defi-core/SKILL.md +3 -0
- package/skills/manifests/cyfrin.json +16 -0
- package/skills/manifests/defifofum.json +25 -0
- package/skills/manifests/kadenzipfel.json +48 -0
- package/skills/manifests/scvd.json +9 -0
- package/skills/manifests/smartbugs.json +9 -0
- package/skills/manifests/solodit.json +9 -0
- package/skills/manifests/sunweb3sec.json +9 -0
- package/skills/manifests/trailofbits.json +9 -0
- package/skills/methodology/audit-workflow/SKILL.md +3 -0
- package/skills/protocol-patterns/amm-dex/SKILL.md +3 -0
- package/skills/references/exploit-reference/SKILL.md +3 -0
- package/skills/vulnerability-patterns/access-control/SKILL.md +27 -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 +8 -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 +8 -1
- package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
- package/skills/vulnerability-patterns/dos-revert/SKILL.md +14 -1
- 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 +13 -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 +22 -0
- package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
- package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +11 -1
- package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
- package/skills/vulnerability-patterns/reentrancy/SKILL.md +22 -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 +11 -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 +13 -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 +27 -10
- package/src/agents/pythia-prompt.ts +7 -8
- package/src/agents/scribe-prompt.ts +10 -5
- package/src/agents/sentinel-prompt.ts +36 -7
- package/src/cli/cli-output.ts +16 -0
- package/src/cli/cli-program.ts +29 -22
- package/src/cli/commands/check-skills.ts +135 -0
- package/src/cli/commands/doctor.ts +303 -23
- package/src/cli/commands/init.ts +8 -6
- package/src/cli/commands/install.ts +10 -8
- package/src/cli/commands/lint-skills.ts +118 -0
- package/src/cli/index.ts +5 -5
- package/src/cli/tui-prompts.ts +4 -2
- 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 +225 -29
- package/src/create-managers.ts +10 -8
- package/src/create-tools.ts +14 -8
- 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 +79 -19
- package/src/features/index.ts +5 -5
- package/src/features/persistent-state/audit-state-manager.ts +158 -52
- 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/agent-tracker.ts +53 -0
- package/src/hooks/compaction-hook.ts +46 -37
- package/src/hooks/config-handler.ts +31 -11
- package/src/hooks/context-budget.ts +42 -0
- package/src/hooks/event-hook.ts +48 -23
- package/src/hooks/hook-system.ts +4 -4
- package/src/hooks/index.ts +5 -5
- package/src/hooks/knowledge-sync-hook.ts +19 -21
- package/src/hooks/recon-context-builder.ts +66 -0
- package/src/hooks/safe-create-hook.ts +9 -11
- package/src/hooks/system-prompt-hook.ts +128 -0
- package/src/hooks/tool-tracking-hook.ts +162 -29
- package/src/hooks/types.ts +2 -1
- package/src/index.ts +23 -13
- package/src/knowledge/retry.ts +53 -0
- package/src/knowledge/scvd-client.ts +103 -83
- package/src/knowledge/scvd-errors.ts +89 -0
- package/src/knowledge/scvd-index.ts +110 -62
- package/src/knowledge/scvd-sync.ts +223 -47
- package/src/knowledge/source-manifest.ts +102 -0
- package/src/managers/index.ts +1 -1
- package/src/managers/types.ts +19 -14
- package/src/plugin-interface.ts +19 -8
- package/src/shared/binary-utils.ts +44 -34
- 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 +91 -17
- 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 +237 -0
- package/src/skills/skill-schema.ts +99 -0
- package/src/solodit-lifecycle.ts +202 -0
- package/src/state/audit-state.ts +10 -8
- package/src/state/finding-store.ts +68 -55
- package/src/state/types.ts +96 -44
- package/src/tools/argus-skill-load-tool.ts +78 -0
- 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 +206 -167
- package/src/tools/pattern-loader.ts +77 -0
- package/src/tools/pattern-schema.ts +51 -0
- package/src/tools/proxy-detection-tool.ts +224 -0
- package/src/tools/report-generator-tool.ts +333 -142
- package/src/tools/slither-tool.ts +300 -210
- package/src/tools/solodit-search-tool.ts +255 -80
- package/src/tools/sync-knowledge-tool.ts +7 -11
- package/src/utils/audit-artifact-detector.ts +118 -0
- package/src/utils/dependency-scanner.ts +93 -0
- package/src/utils/project-detector.ts +175 -86
- package/src/utils/solidity-parser.ts +112 -67
- package/src/utils/solodit-health.ts +29 -0
- package/src/hooks/event-hook-v2.ts +0 -99
- package/src/state/plugin-state.ts +0 -14
|
@@ -1,37 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type { AuditState, Finding, FindingSeverity } from "../state/types"
|
|
1
|
+
import { type ToolContext, tool } from "@opencode-ai/plugin"
|
|
2
|
+
import type { AuditState, Finding, FindingSeverity } from "../state/types"
|
|
3
3
|
|
|
4
|
-
type SeverityThreshold = "critical" | "high" | "medium" | "low" | "informational"
|
|
4
|
+
type SeverityThreshold = "critical" | "high" | "medium" | "low" | "informational"
|
|
5
5
|
|
|
6
6
|
type ReportGeneratorArgs = {
|
|
7
|
-
project_name: string
|
|
8
|
-
scope: string[]
|
|
9
|
-
include_executive_summary?: boolean
|
|
10
|
-
severity_threshold?: SeverityThreshold
|
|
11
|
-
audit_state: string
|
|
12
|
-
}
|
|
7
|
+
project_name: string
|
|
8
|
+
scope: string[]
|
|
9
|
+
include_executive_summary?: boolean
|
|
10
|
+
severity_threshold?: SeverityThreshold
|
|
11
|
+
audit_state: string
|
|
12
|
+
}
|
|
13
13
|
|
|
14
14
|
type FindingsCount = {
|
|
15
|
-
critical: number
|
|
16
|
-
high: number
|
|
17
|
-
medium: number
|
|
18
|
-
low: number
|
|
19
|
-
informational: number
|
|
20
|
-
}
|
|
15
|
+
critical: number
|
|
16
|
+
high: number
|
|
17
|
+
medium: number
|
|
18
|
+
low: number
|
|
19
|
+
informational: number
|
|
20
|
+
}
|
|
21
21
|
|
|
22
22
|
export type ReportGenerationResult = {
|
|
23
|
-
report: string
|
|
24
|
-
findingsCount: FindingsCount
|
|
25
|
-
filename: string
|
|
26
|
-
}
|
|
23
|
+
report: string
|
|
24
|
+
findingsCount: FindingsCount
|
|
25
|
+
filename: string
|
|
26
|
+
}
|
|
27
27
|
|
|
28
|
-
const SEVERITY_ORDER: FindingSeverity[] = [
|
|
29
|
-
"Critical",
|
|
30
|
-
"High",
|
|
31
|
-
"Medium",
|
|
32
|
-
"Low",
|
|
33
|
-
"Informational",
|
|
34
|
-
];
|
|
28
|
+
const SEVERITY_ORDER: FindingSeverity[] = ["Critical", "High", "Medium", "Low", "Informational"]
|
|
35
29
|
|
|
36
30
|
const SEVERITY_PREFIX: Record<FindingSeverity, string> = {
|
|
37
31
|
Critical: "CRIT",
|
|
@@ -39,7 +33,7 @@ const SEVERITY_PREFIX: Record<FindingSeverity, string> = {
|
|
|
39
33
|
Medium: "MED",
|
|
40
34
|
Low: "LOW",
|
|
41
35
|
Informational: "INFO",
|
|
42
|
-
}
|
|
36
|
+
}
|
|
43
37
|
|
|
44
38
|
const THRESHOLD_WEIGHT: Record<SeverityThreshold, number> = {
|
|
45
39
|
critical: 5,
|
|
@@ -47,7 +41,7 @@ const THRESHOLD_WEIGHT: Record<SeverityThreshold, number> = {
|
|
|
47
41
|
medium: 3,
|
|
48
42
|
low: 2,
|
|
49
43
|
informational: 1,
|
|
50
|
-
}
|
|
44
|
+
}
|
|
51
45
|
|
|
52
46
|
const FINDING_WEIGHT: Record<FindingSeverity, number> = {
|
|
53
47
|
Critical: 5,
|
|
@@ -55,7 +49,7 @@ const FINDING_WEIGHT: Record<FindingSeverity, number> = {
|
|
|
55
49
|
Medium: 3,
|
|
56
50
|
Low: 2,
|
|
57
51
|
Informational: 1,
|
|
58
|
-
}
|
|
52
|
+
}
|
|
59
53
|
|
|
60
54
|
function emptyCounts(): FindingsCount {
|
|
61
55
|
return {
|
|
@@ -64,229 +58,426 @@ function emptyCounts(): FindingsCount {
|
|
|
64
58
|
medium: 0,
|
|
65
59
|
low: 0,
|
|
66
60
|
informational: 0,
|
|
67
|
-
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function emptyAuditState(findings: Finding[] = []): AuditState {
|
|
65
|
+
return {
|
|
66
|
+
sessionId: "",
|
|
67
|
+
projectDir: "",
|
|
68
|
+
contractsReviewed: [],
|
|
69
|
+
findings,
|
|
70
|
+
toolsExecuted: [],
|
|
71
|
+
currentPhase: "complete",
|
|
72
|
+
scope: [],
|
|
73
|
+
startTime: 0,
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function hasMinimumFindingFields(
|
|
78
|
+
f: unknown,
|
|
79
|
+
): f is { check: string; file: string; lines: [number, number] } {
|
|
80
|
+
if (typeof f !== "object" || f === null) return false
|
|
81
|
+
const obj = f as Record<string, unknown>
|
|
82
|
+
return (
|
|
83
|
+
typeof obj.check === "string" &&
|
|
84
|
+
obj.check.length > 0 &&
|
|
85
|
+
typeof obj.file === "string" &&
|
|
86
|
+
Array.isArray(obj.lines) &&
|
|
87
|
+
obj.lines.length === 2
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const VALID_SEVERITIES: ReadonlySet<string> = new Set([
|
|
92
|
+
"Critical",
|
|
93
|
+
"High",
|
|
94
|
+
"Medium",
|
|
95
|
+
"Low",
|
|
96
|
+
"Informational",
|
|
97
|
+
])
|
|
98
|
+
const VALID_SOURCES: ReadonlySet<string> = new Set([
|
|
99
|
+
"slither",
|
|
100
|
+
"manual",
|
|
101
|
+
"pattern",
|
|
102
|
+
"scvd",
|
|
103
|
+
"solodit",
|
|
104
|
+
"fuzz",
|
|
105
|
+
])
|
|
106
|
+
|
|
107
|
+
function normalizeFinding(f: Record<string, unknown>): Finding {
|
|
108
|
+
const severity =
|
|
109
|
+
typeof f.severity === "string" && VALID_SEVERITIES.has(f.severity)
|
|
110
|
+
? (f.severity as Finding["severity"])
|
|
111
|
+
: "Informational"
|
|
112
|
+
const confidence =
|
|
113
|
+
typeof f.confidence === "string" && ["High", "Medium", "Low"].includes(f.confidence)
|
|
114
|
+
? (f.confidence as Finding["confidence"])
|
|
115
|
+
: "Low"
|
|
116
|
+
const source =
|
|
117
|
+
typeof f.source === "string" && VALID_SOURCES.has(f.source)
|
|
118
|
+
? (f.source as Finding["source"])
|
|
119
|
+
: "manual"
|
|
120
|
+
const description = typeof f.description === "string" ? f.description : (f.check as string)
|
|
121
|
+
const id = typeof f.id === "string" ? f.id : `${f.check}:${f.file}:${(f.lines as number[])[0]}`
|
|
122
|
+
return {
|
|
123
|
+
id,
|
|
124
|
+
check: f.check as string,
|
|
125
|
+
severity,
|
|
126
|
+
confidence,
|
|
127
|
+
description,
|
|
128
|
+
file: f.file as string,
|
|
129
|
+
lines: f.lines as [number, number],
|
|
130
|
+
source,
|
|
131
|
+
remediation: typeof f.remediation === "string" ? f.remediation : undefined,
|
|
132
|
+
exploitReference: typeof f.exploitReference === "string" ? f.exploitReference : undefined,
|
|
133
|
+
}
|
|
68
134
|
}
|
|
69
135
|
|
|
70
|
-
function parseAuditState(auditState: string):
|
|
71
|
-
let parsed: unknown
|
|
136
|
+
export function parseAuditState(auditState: string): AuditState {
|
|
137
|
+
let parsed: unknown
|
|
72
138
|
try {
|
|
73
|
-
parsed = JSON.parse(auditState)
|
|
139
|
+
parsed = JSON.parse(auditState)
|
|
74
140
|
} catch {
|
|
75
|
-
throw new Error(
|
|
141
|
+
throw new Error(
|
|
142
|
+
"audit_state is not valid JSON — expected an AuditState object or Finding[] array",
|
|
143
|
+
)
|
|
76
144
|
}
|
|
77
145
|
|
|
78
146
|
if (Array.isArray(parsed)) {
|
|
79
|
-
|
|
147
|
+
const validFindings = (parsed as unknown[])
|
|
148
|
+
.filter(hasMinimumFindingFields)
|
|
149
|
+
.map((f) => normalizeFinding(f as Record<string, unknown>))
|
|
150
|
+
return emptyAuditState(validFindings)
|
|
80
151
|
}
|
|
81
152
|
|
|
82
|
-
if (
|
|
83
|
-
|
|
153
|
+
if (
|
|
154
|
+
typeof parsed === "object" &&
|
|
155
|
+
parsed !== null &&
|
|
156
|
+
Array.isArray((parsed as AuditState).findings)
|
|
157
|
+
) {
|
|
158
|
+
const state = parsed as AuditState
|
|
159
|
+
const validFindings = state.findings
|
|
160
|
+
.filter(hasMinimumFindingFields)
|
|
161
|
+
.map((f) => normalizeFinding(f as unknown as Record<string, unknown>))
|
|
162
|
+
return {
|
|
163
|
+
...emptyAuditState(),
|
|
164
|
+
...state,
|
|
165
|
+
findings: validFindings,
|
|
166
|
+
}
|
|
84
167
|
}
|
|
85
168
|
|
|
86
|
-
return
|
|
169
|
+
return emptyAuditState()
|
|
87
170
|
}
|
|
88
171
|
|
|
89
172
|
function normalizeTitle(check: string): string {
|
|
173
|
+
if (!check || typeof check !== "string") return "Unknown Check"
|
|
90
174
|
return check
|
|
91
175
|
.split(/[-_\s]+/)
|
|
92
176
|
.filter((part) => part.length > 0)
|
|
93
177
|
.map((part) => `${part[0]?.toUpperCase() ?? ""}${part.slice(1)}`)
|
|
94
|
-
.join(" ")
|
|
178
|
+
.join(" ")
|
|
95
179
|
}
|
|
96
180
|
|
|
97
181
|
function formatLocation(finding: Finding): string {
|
|
98
|
-
|
|
182
|
+
if (!finding.file || !Array.isArray(finding.lines) || finding.lines.length < 2)
|
|
183
|
+
return "unknown location"
|
|
184
|
+
return `${finding.file}:${finding.lines[0]}-${finding.lines[1]}`
|
|
99
185
|
}
|
|
100
186
|
|
|
101
187
|
function shouldIncludeFinding(finding: Finding, threshold: SeverityThreshold): boolean {
|
|
102
|
-
return FINDING_WEIGHT[finding.severity] >= THRESHOLD_WEIGHT[threshold]
|
|
188
|
+
return FINDING_WEIGHT[finding.severity] >= THRESHOLD_WEIGHT[threshold]
|
|
103
189
|
}
|
|
104
190
|
|
|
105
191
|
function calculateCounts(findings: Finding[]): FindingsCount {
|
|
106
|
-
const counts = emptyCounts()
|
|
192
|
+
const counts = emptyCounts()
|
|
107
193
|
|
|
108
194
|
for (const finding of findings) {
|
|
109
|
-
if (finding.severity === "Critical") counts.critical += 1
|
|
110
|
-
if (finding.severity === "High") counts.high += 1
|
|
111
|
-
if (finding.severity === "Medium") counts.medium += 1
|
|
112
|
-
if (finding.severity === "Low") counts.low += 1
|
|
113
|
-
if (finding.severity === "Informational") counts.informational += 1
|
|
195
|
+
if (finding.severity === "Critical") counts.critical += 1
|
|
196
|
+
if (finding.severity === "High") counts.high += 1
|
|
197
|
+
if (finding.severity === "Medium") counts.medium += 1
|
|
198
|
+
if (finding.severity === "Low") counts.low += 1
|
|
199
|
+
if (finding.severity === "Informational") counts.informational += 1
|
|
114
200
|
}
|
|
115
201
|
|
|
116
|
-
return counts
|
|
202
|
+
return counts
|
|
117
203
|
}
|
|
118
204
|
|
|
119
205
|
function overallRiskAssessment(counts: FindingsCount): string {
|
|
120
|
-
if (counts.critical > 0) return "Critical risk"
|
|
121
|
-
if (counts.high > 0) return "High risk"
|
|
122
|
-
if (counts.medium > 0) return "Medium risk"
|
|
123
|
-
if (counts.low > 0) return "Low risk"
|
|
124
|
-
if (counts.informational > 0) return "Informational only"
|
|
125
|
-
return "No significant risk identified"
|
|
206
|
+
if (counts.critical > 0) return "Critical risk"
|
|
207
|
+
if (counts.high > 0) return "High risk"
|
|
208
|
+
if (counts.medium > 0) return "Medium risk"
|
|
209
|
+
if (counts.low > 0) return "Low risk"
|
|
210
|
+
if (counts.informational > 0) return "Informational only"
|
|
211
|
+
return "No significant risk identified"
|
|
126
212
|
}
|
|
127
213
|
|
|
128
214
|
function genericImpact(severity: FindingSeverity): string {
|
|
129
215
|
if (severity === "Critical") {
|
|
130
|
-
return "Could lead to immediate and severe compromise of funds or protocol control."
|
|
216
|
+
return "Could lead to immediate and severe compromise of funds or protocol control."
|
|
131
217
|
}
|
|
132
218
|
if (severity === "High") {
|
|
133
|
-
return "Could materially impact protocol security, user funds, or system integrity."
|
|
219
|
+
return "Could materially impact protocol security, user funds, or system integrity."
|
|
134
220
|
}
|
|
135
221
|
if (severity === "Medium") {
|
|
136
|
-
return "Could cause operational issues or increase exploitability under specific conditions."
|
|
222
|
+
return "Could cause operational issues or increase exploitability under specific conditions."
|
|
137
223
|
}
|
|
138
224
|
if (severity === "Low") {
|
|
139
|
-
return "Limited direct impact but should be addressed to improve security posture."
|
|
225
|
+
return "Limited direct impact but should be addressed to improve security posture."
|
|
140
226
|
}
|
|
141
|
-
return "No immediate exploit impact, but useful for hardening and maintainability."
|
|
227
|
+
return "No immediate exploit impact, but useful for hardening and maintainability."
|
|
142
228
|
}
|
|
143
229
|
|
|
144
230
|
function genericRecommendation(severity: FindingSeverity): string {
|
|
145
231
|
if (severity === "Critical" || severity === "High") {
|
|
146
|
-
return "Prioritize remediation before production deployment and validate with focused regression tests."
|
|
232
|
+
return "Prioritize remediation before production deployment and validate with focused regression tests."
|
|
147
233
|
}
|
|
148
234
|
if (severity === "Medium") {
|
|
149
|
-
return "Address in the near term and include unit/integration tests to prevent regressions."
|
|
235
|
+
return "Address in the near term and include unit/integration tests to prevent regressions."
|
|
150
236
|
}
|
|
151
237
|
if (severity === "Low") {
|
|
152
|
-
return "Schedule remediation in regular hardening cycles."
|
|
238
|
+
return "Schedule remediation in regular hardening cycles."
|
|
153
239
|
}
|
|
154
|
-
return "Track and resolve during routine code quality and documentation improvements."
|
|
240
|
+
return "Track and resolve during routine code quality and documentation improvements."
|
|
155
241
|
}
|
|
156
242
|
|
|
157
243
|
function buildRecommendations(counts: FindingsCount): string[] {
|
|
158
|
-
const items: string[] = []
|
|
244
|
+
const items: string[] = []
|
|
159
245
|
|
|
160
246
|
if (counts.critical > 0) {
|
|
161
|
-
items.push(
|
|
247
|
+
items.push(
|
|
248
|
+
"1. Immediately remediate all Critical findings and block release until fixes are verified.",
|
|
249
|
+
)
|
|
162
250
|
}
|
|
163
251
|
if (counts.high > 0) {
|
|
164
|
-
items.push(
|
|
252
|
+
items.push(
|
|
253
|
+
"2. Prioritize High findings in the next patch cycle with dedicated security test coverage.",
|
|
254
|
+
)
|
|
165
255
|
}
|
|
166
256
|
if (counts.medium > 0) {
|
|
167
|
-
items.push("3. Resolve Medium findings to reduce attack surface and improve resilience.")
|
|
257
|
+
items.push("3. Resolve Medium findings to reduce attack surface and improve resilience.")
|
|
168
258
|
}
|
|
169
259
|
if (counts.low > 0 || counts.informational > 0) {
|
|
170
|
-
items.push(
|
|
260
|
+
items.push(
|
|
261
|
+
"4. Address Low/Informational findings as part of ongoing hardening and code quality efforts.",
|
|
262
|
+
)
|
|
171
263
|
}
|
|
172
264
|
|
|
173
265
|
if (items.length === 0) {
|
|
174
|
-
items.push(
|
|
266
|
+
items.push(
|
|
267
|
+
"1. Maintain current controls, monitor code changes, and re-audit before major upgrades.",
|
|
268
|
+
)
|
|
175
269
|
}
|
|
176
270
|
|
|
177
|
-
return items
|
|
271
|
+
return items
|
|
178
272
|
}
|
|
179
273
|
|
|
180
274
|
function buildFindingsSection(findings: Finding[]): string {
|
|
181
275
|
if (findings.length === 0) {
|
|
182
|
-
return "## Findings\nNo findings meet the configured severity threshold."
|
|
276
|
+
return "## Findings\nNo findings meet the configured severity threshold."
|
|
183
277
|
}
|
|
184
278
|
|
|
185
|
-
const lines: string[] = ["## Findings"]
|
|
279
|
+
const lines: string[] = ["## Findings"]
|
|
186
280
|
|
|
187
281
|
for (const severity of SEVERITY_ORDER) {
|
|
188
|
-
const severityFindings = findings.filter((finding) => finding.severity === severity)
|
|
282
|
+
const severityFindings = findings.filter((finding) => finding.severity === severity)
|
|
189
283
|
if (severityFindings.length === 0) {
|
|
190
|
-
continue
|
|
284
|
+
continue
|
|
191
285
|
}
|
|
192
286
|
|
|
193
|
-
lines.push(`### ${severity}`)
|
|
287
|
+
lines.push(`### ${severity}`)
|
|
194
288
|
|
|
195
289
|
severityFindings.forEach((finding, index) => {
|
|
196
|
-
const prefix = SEVERITY_PREFIX[severity]
|
|
197
|
-
const findingId = `[${prefix}-${index + 1}]
|
|
198
|
-
const title = normalizeTitle(finding.check)
|
|
199
|
-
const recommendation = finding.remediation ?? genericRecommendation(severity)
|
|
200
|
-
|
|
201
|
-
lines.push(`### ${findingId} ${title}`)
|
|
202
|
-
lines.push(`**Severity**: ${finding.severity}`)
|
|
203
|
-
lines.push(`**Confidence**: ${finding.confidence}`)
|
|
204
|
-
lines.push(`**Location**: ${formatLocation(finding)}`)
|
|
205
|
-
lines.push("")
|
|
206
|
-
lines.push(`**Description**: ${finding.description}`)
|
|
207
|
-
lines.push("")
|
|
208
|
-
lines.push(`**Impact**: ${genericImpact(finding.severity)}`)
|
|
209
|
-
lines.push("")
|
|
210
|
-
lines.push(`**Recommendation**: ${recommendation}`)
|
|
211
|
-
lines.push("")
|
|
212
|
-
})
|
|
290
|
+
const prefix = SEVERITY_PREFIX[severity]
|
|
291
|
+
const findingId = `[${prefix}-${index + 1}]`
|
|
292
|
+
const title = normalizeTitle(finding.check)
|
|
293
|
+
const recommendation = finding.remediation ?? genericRecommendation(severity)
|
|
294
|
+
|
|
295
|
+
lines.push(`### ${findingId} ${title}`)
|
|
296
|
+
lines.push(`**Severity**: ${finding.severity}`)
|
|
297
|
+
lines.push(`**Confidence**: ${finding.confidence}`)
|
|
298
|
+
lines.push(`**Location**: ${formatLocation(finding)}`)
|
|
299
|
+
lines.push("")
|
|
300
|
+
lines.push(`**Description**: ${finding.description}`)
|
|
301
|
+
lines.push("")
|
|
302
|
+
lines.push(`**Impact**: ${genericImpact(finding.severity)}`)
|
|
303
|
+
lines.push("")
|
|
304
|
+
lines.push(`**Recommendation**: ${recommendation}`)
|
|
305
|
+
lines.push("")
|
|
306
|
+
})
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
return lines.join("\n")
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function formatDuration(ms: number): string {
|
|
313
|
+
if (ms < 1000) return `${ms}ms`
|
|
314
|
+
return `${(ms / 1000).toFixed(1)}s`
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
export function buildProvenanceAppendix(
|
|
318
|
+
state: AuditState,
|
|
319
|
+
threshold: SeverityThreshold,
|
|
320
|
+
includedCount: number,
|
|
321
|
+
): string {
|
|
322
|
+
const lines: string[] = ["## Appendix: Data Provenance"]
|
|
323
|
+
|
|
324
|
+
lines.push("- Data source: `audit_state` payload")
|
|
325
|
+
lines.push(`- Severity threshold applied: ${threshold}`)
|
|
326
|
+
lines.push(`- Findings included in report: ${includedCount}`)
|
|
327
|
+
|
|
328
|
+
if (state.findings.length > 0) {
|
|
329
|
+
const sourceCounts: Record<string, number> = {}
|
|
330
|
+
for (const f of state.findings) {
|
|
331
|
+
sourceCounts[f.source] = (sourceCounts[f.source] ?? 0) + 1
|
|
332
|
+
}
|
|
333
|
+
lines.push("")
|
|
334
|
+
lines.push("### Source Breakdown")
|
|
335
|
+
lines.push("")
|
|
336
|
+
lines.push("| Source | Count |")
|
|
337
|
+
lines.push("| --- | ---: |")
|
|
338
|
+
for (const [source, count] of Object.entries(sourceCounts).sort((a, b) => b[1] - a[1])) {
|
|
339
|
+
lines.push(`| ${source} | ${count} |`)
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (state.toolsExecuted.length > 0) {
|
|
344
|
+
lines.push("")
|
|
345
|
+
lines.push("### Tool Execution Summary")
|
|
346
|
+
lines.push("")
|
|
347
|
+
lines.push("| Tool | Duration | Status | Findings |")
|
|
348
|
+
lines.push("| --- | --- | --- | ---: |")
|
|
349
|
+
for (const exec of state.toolsExecuted) {
|
|
350
|
+
const duration = exec.endTime != null ? formatDuration(exec.endTime - exec.startTime) : "—"
|
|
351
|
+
const status = exec.success ? "✅ success" : "❌ failure"
|
|
352
|
+
lines.push(`| ${exec.tool} | ${duration} | ${status} | ${exec.findingsCount} |`)
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const syncExec = state.toolsExecuted.find((t) => t.tool === "argus_sync_knowledge")
|
|
357
|
+
if (state.patternVersion || syncExec) {
|
|
358
|
+
lines.push("")
|
|
359
|
+
lines.push("### Data Freshness")
|
|
360
|
+
lines.push("")
|
|
361
|
+
if (state.patternVersion) {
|
|
362
|
+
lines.push(`- Pattern pack version: \`${state.patternVersion}\``)
|
|
363
|
+
}
|
|
364
|
+
if (syncExec) {
|
|
365
|
+
lines.push(`- SCVD last synced: ${new Date(syncExec.startTime).toISOString()}`)
|
|
366
|
+
}
|
|
213
367
|
}
|
|
214
368
|
|
|
215
|
-
|
|
369
|
+
if (state.soloditResults && state.soloditResults.length > 0) {
|
|
370
|
+
lines.push("")
|
|
371
|
+
lines.push("### Solodit Cross-References")
|
|
372
|
+
lines.push("")
|
|
373
|
+
for (const result of state.soloditResults) {
|
|
374
|
+
lines.push(`**Query**: "${result.query}" — ${result.resultCount} results`)
|
|
375
|
+
if (result.topResults.length > 0) {
|
|
376
|
+
lines.push("")
|
|
377
|
+
lines.push("| Title | Severity | Protocol |")
|
|
378
|
+
lines.push("| --- | --- | --- |")
|
|
379
|
+
for (const top of result.topResults) {
|
|
380
|
+
lines.push(`| ${top.title} | ${top.severity} | ${top.protocol} |`)
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
lines.push("")
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (state.fuzzCounterexamples && state.fuzzCounterexamples.length > 0) {
|
|
388
|
+
lines.push("")
|
|
389
|
+
lines.push("### Fuzz Evidence")
|
|
390
|
+
lines.push("")
|
|
391
|
+
lines.push("| Test | Inputs | Runs | Revert Reason |")
|
|
392
|
+
lines.push("| --- | --- | ---: | --- |")
|
|
393
|
+
for (const cx of state.fuzzCounterexamples) {
|
|
394
|
+
const inputs = cx.inputs.join(", ")
|
|
395
|
+
const reason = cx.revertReason ?? "—"
|
|
396
|
+
lines.push(`| ${cx.testName} | ${inputs} | ${cx.runs} | ${reason} |`)
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (state.skillsLoaded && state.skillsLoaded.length > 0) {
|
|
401
|
+
lines.push("")
|
|
402
|
+
lines.push("### Knowledge Sources")
|
|
403
|
+
lines.push("")
|
|
404
|
+
lines.push("Skills loaded during this audit:")
|
|
405
|
+
lines.push("")
|
|
406
|
+
for (const skill of state.skillsLoaded) {
|
|
407
|
+
lines.push(`- ${skill}`)
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
return lines.join("\n")
|
|
216
412
|
}
|
|
217
413
|
|
|
218
414
|
export async function executeReportGeneration(
|
|
219
415
|
args: ReportGeneratorArgs,
|
|
220
|
-
context: ToolContext
|
|
416
|
+
context: ToolContext,
|
|
221
417
|
): Promise<ReportGenerationResult> {
|
|
222
|
-
const includeExecutiveSummary = args.include_executive_summary ?? true
|
|
223
|
-
const threshold = args.severity_threshold ?? "low"
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
)
|
|
227
|
-
const
|
|
228
|
-
const auditDate = new Date().toISOString().slice(0, 10);
|
|
418
|
+
const includeExecutiveSummary = args.include_executive_summary ?? true
|
|
419
|
+
const threshold = args.severity_threshold ?? "low"
|
|
420
|
+
const state = parseAuditState(args.audit_state)
|
|
421
|
+
const findings = state.findings.filter((finding) => shouldIncludeFinding(finding, threshold))
|
|
422
|
+
const counts = calculateCounts(findings)
|
|
423
|
+
const auditDate = new Date().toISOString().slice(0, 10)
|
|
229
424
|
|
|
230
|
-
context.metadata({ title: `Generate audit report: ${args.project_name}` })
|
|
425
|
+
context.metadata({ title: `Generate audit report: ${args.project_name}` })
|
|
231
426
|
|
|
232
|
-
const sections: string[] = [`# Security Audit Report — ${args.project_name}`]
|
|
427
|
+
const sections: string[] = [`# Security Audit Report — ${args.project_name}`]
|
|
233
428
|
|
|
234
429
|
if (includeExecutiveSummary) {
|
|
235
|
-
sections.push("## Executive Summary")
|
|
430
|
+
sections.push("## Executive Summary")
|
|
236
431
|
sections.push(
|
|
237
|
-
`This report summarizes security findings identified for ${args.project_name} based on static analysis, testing, and pattern-based review
|
|
238
|
-
)
|
|
239
|
-
sections.push("")
|
|
240
|
-
sections.push("| Severity | Count |")
|
|
241
|
-
sections.push("| --- | ---: |")
|
|
242
|
-
sections.push(`| Critical | ${counts.critical} |`)
|
|
243
|
-
sections.push(`| High | ${counts.high} |`)
|
|
244
|
-
sections.push(`| Medium | ${counts.medium} |`)
|
|
245
|
-
sections.push(`| Low | ${counts.low} |`)
|
|
246
|
-
sections.push(`| Informational | ${counts.informational} |`)
|
|
247
|
-
sections.push("")
|
|
248
|
-
sections.push(`Overall risk assessment: ${overallRiskAssessment(counts)}.`)
|
|
432
|
+
`This report summarizes security findings identified for ${args.project_name} based on static analysis, testing, and pattern-based review.`,
|
|
433
|
+
)
|
|
434
|
+
sections.push("")
|
|
435
|
+
sections.push("| Severity | Count |")
|
|
436
|
+
sections.push("| --- | ---: |")
|
|
437
|
+
sections.push(`| Critical | ${counts.critical} |`)
|
|
438
|
+
sections.push(`| High | ${counts.high} |`)
|
|
439
|
+
sections.push(`| Medium | ${counts.medium} |`)
|
|
440
|
+
sections.push(`| Low | ${counts.low} |`)
|
|
441
|
+
sections.push(`| Informational | ${counts.informational} |`)
|
|
442
|
+
sections.push("")
|
|
443
|
+
sections.push(`Overall risk assessment: ${overallRiskAssessment(counts)}.`)
|
|
249
444
|
}
|
|
250
445
|
|
|
251
|
-
sections.push("## Scope")
|
|
252
|
-
sections.push("Contracts in scope:")
|
|
446
|
+
sections.push("## Scope")
|
|
447
|
+
sections.push("Contracts in scope:")
|
|
253
448
|
if (args.scope.length === 0) {
|
|
254
|
-
sections.push("- None provided")
|
|
449
|
+
sections.push("- None provided")
|
|
255
450
|
} else {
|
|
256
451
|
for (const contract of args.scope) {
|
|
257
|
-
sections.push(`- ${contract}`)
|
|
452
|
+
sections.push(`- ${contract}`)
|
|
258
453
|
}
|
|
259
454
|
}
|
|
260
|
-
sections.push(`Audit date: ${auditDate}`)
|
|
261
|
-
|
|
262
|
-
sections.push("## Methodology")
|
|
263
|
-
sections.push("Tools and techniques used:")
|
|
264
|
-
sections.push("- Slither static analysis")
|
|
265
|
-
sections.push("- Foundry tests and fuzzing")
|
|
266
|
-
sections.push("- Pattern Analysis")
|
|
267
|
-
sections.push("- Solodit research cross-referencing")
|
|
455
|
+
sections.push(`Audit date: ${auditDate}`)
|
|
456
|
+
|
|
457
|
+
sections.push("## Methodology")
|
|
458
|
+
sections.push("Tools and techniques used:")
|
|
459
|
+
sections.push("- Slither static analysis")
|
|
460
|
+
sections.push("- Foundry tests and fuzzing")
|
|
461
|
+
sections.push("- Pattern Analysis")
|
|
462
|
+
sections.push("- Solodit research cross-referencing")
|
|
268
463
|
sections.push(
|
|
269
|
-
"Approach: Findings were normalized, deduplicated by detector signature and location, then prioritized by severity and confidence."
|
|
270
|
-
)
|
|
464
|
+
"Approach: Findings were normalized, deduplicated by detector signature and location, then prioritized by severity and confidence.",
|
|
465
|
+
)
|
|
271
466
|
|
|
272
|
-
sections.push(buildFindingsSection(findings))
|
|
467
|
+
sections.push(buildFindingsSection(findings))
|
|
273
468
|
|
|
274
|
-
sections.push("## Recommendations")
|
|
469
|
+
sections.push("## Recommendations")
|
|
275
470
|
for (const item of buildRecommendations(counts)) {
|
|
276
|
-
sections.push(`- ${item}`)
|
|
471
|
+
sections.push(`- ${item}`)
|
|
277
472
|
}
|
|
278
473
|
|
|
279
|
-
sections.push(
|
|
280
|
-
sections.push("Tool execution summary:");
|
|
281
|
-
sections.push("- Data source: `audit_state` payload");
|
|
282
|
-
sections.push(`- Severity threshold applied: ${threshold}`);
|
|
283
|
-
sections.push(`- Findings included in report: ${findings.length}`);
|
|
474
|
+
sections.push(buildProvenanceAppendix(state, threshold, findings.length))
|
|
284
475
|
|
|
285
476
|
return {
|
|
286
477
|
report: sections.join("\n\n"),
|
|
287
478
|
findingsCount: counts,
|
|
288
479
|
filename: `${args.project_name}-audit-report-${auditDate}.md`,
|
|
289
|
-
}
|
|
480
|
+
}
|
|
290
481
|
}
|
|
291
482
|
|
|
292
483
|
export const reportGeneratorTool = tool({
|
|
@@ -302,7 +493,7 @@ export const reportGeneratorTool = tool({
|
|
|
302
493
|
audit_state: tool.schema.string(),
|
|
303
494
|
},
|
|
304
495
|
async execute(args, context) {
|
|
305
|
-
const result = await executeReportGeneration(args, context)
|
|
306
|
-
return JSON.stringify(result)
|
|
496
|
+
const result = await executeReportGeneration(args, context)
|
|
497
|
+
return JSON.stringify(result)
|
|
307
498
|
},
|
|
308
|
-
})
|
|
499
|
+
})
|