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,85 +1,261 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
1
|
+
import { createLogger } from "../shared/logger"
|
|
2
|
+
import { withRetry } from "./retry"
|
|
3
|
+
import type { ScvdClient } from "./scvd-client"
|
|
4
|
+
import { ScvdApiError, ScvdNetworkError } from "./scvd-client"
|
|
5
|
+
import {
|
|
6
|
+
createApiError,
|
|
7
|
+
createNetworkError,
|
|
8
|
+
createParseError,
|
|
9
|
+
createSyncSuccess,
|
|
10
|
+
isRetryableError,
|
|
11
|
+
type SyncError,
|
|
12
|
+
type SyncOutcome,
|
|
13
|
+
} from "./scvd-errors"
|
|
14
|
+
import {
|
|
15
|
+
acquireSyncLock,
|
|
16
|
+
buildIndex,
|
|
17
|
+
loadIndex,
|
|
18
|
+
releaseSyncLock,
|
|
19
|
+
type ScvdIndex,
|
|
20
|
+
type ScvdIndexMetadata,
|
|
21
|
+
saveIndex,
|
|
22
|
+
} from "./scvd-index"
|
|
23
|
+
|
|
24
|
+
export type SyncResult = SyncOutcome
|
|
25
|
+
|
|
26
|
+
const RETRY_MAX_ATTEMPTS = 3
|
|
27
|
+
const RETRY_BASE_DELAY_MS = 1000
|
|
28
|
+
|
|
29
|
+
function buildErrorResult(error: unknown): SyncError {
|
|
30
|
+
const message = error instanceof Error ? error.message : "Unknown sync error"
|
|
31
|
+
|
|
32
|
+
if (error instanceof ScvdNetworkError) {
|
|
33
|
+
return createNetworkError(message)
|
|
34
|
+
}
|
|
35
|
+
if (error instanceof ScvdApiError) {
|
|
36
|
+
return createApiError(error.httpStatus, message)
|
|
37
|
+
}
|
|
38
|
+
return createParseError(message)
|
|
10
39
|
}
|
|
11
40
|
|
|
12
|
-
function
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
41
|
+
function shouldRetrySyncError(error: unknown): boolean {
|
|
42
|
+
if (!(error instanceof ScvdNetworkError)) {
|
|
43
|
+
return false
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return isRetryableError(buildErrorResult(error))
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function errorReasonFromResult(result: SyncError): string {
|
|
50
|
+
return result.reason
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function persistErrorMetadata(indexPath: string, errorResult: SyncError): Promise<void> {
|
|
54
|
+
const existing = await loadIndex(indexPath)
|
|
55
|
+
if (!existing) return
|
|
56
|
+
|
|
57
|
+
const now = new Date().toISOString()
|
|
58
|
+
const prevMetadata = existing.metadata
|
|
59
|
+
existing.metadata = {
|
|
60
|
+
lastSuccess: prevMetadata?.lastSuccess ?? null,
|
|
61
|
+
lastAttempt: now,
|
|
62
|
+
errorCount: (prevMetadata?.errorCount ?? 0) + 1,
|
|
63
|
+
lastError: errorResult.message,
|
|
64
|
+
lastErrorReason: errorReasonFromResult(errorResult),
|
|
65
|
+
}
|
|
66
|
+
await saveIndex(existing, indexPath)
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function syncAllUnlocked(client: ScvdClient, indexPath: string): Promise<SyncResult> {
|
|
70
|
+
const fetchResult = await withRetry(() => client.fetchAllFindings(), {
|
|
71
|
+
maxAttempts: RETRY_MAX_ATTEMPTS,
|
|
72
|
+
baseDelayMs: RETRY_BASE_DELAY_MS,
|
|
73
|
+
shouldRetry: shouldRetrySyncError,
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
if (!fetchResult.success) {
|
|
77
|
+
const errorResult = buildErrorResult(fetchResult.error)
|
|
78
|
+
errorResult.attempts = fetchResult.attempts
|
|
79
|
+
await persistErrorMetadata(indexPath, errorResult)
|
|
80
|
+
return errorResult
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (fetchResult.value === undefined) {
|
|
84
|
+
const errorResult = createParseError("SCVD sync returned no findings payload")
|
|
85
|
+
errorResult.attempts = fetchResult.attempts
|
|
86
|
+
await persistErrorMetadata(indexPath, errorResult)
|
|
87
|
+
return errorResult
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const findings = fetchResult.value
|
|
91
|
+
const index = buildIndex(findings)
|
|
92
|
+
const now = new Date().toISOString()
|
|
93
|
+
index.metadata = {
|
|
94
|
+
lastSuccess: now,
|
|
95
|
+
lastAttempt: now,
|
|
96
|
+
errorCount: 0,
|
|
97
|
+
lastError: null,
|
|
98
|
+
lastErrorReason: null,
|
|
99
|
+
}
|
|
100
|
+
await saveIndex(index, indexPath)
|
|
101
|
+
|
|
102
|
+
return createSyncSuccess({
|
|
103
|
+
newFindings: findings.length,
|
|
104
|
+
totalIndexed: index.totalFindings,
|
|
105
|
+
lastSync: index.lastSync,
|
|
106
|
+
attempts: fetchResult.attempts,
|
|
107
|
+
})
|
|
21
108
|
}
|
|
22
109
|
|
|
23
110
|
export async function syncAll(client: ScvdClient, indexPath: string): Promise<SyncResult> {
|
|
24
|
-
|
|
25
|
-
const findings = await client.fetchAllFindings();
|
|
26
|
-
const index = buildIndex(findings);
|
|
27
|
-
await saveIndex(index, indexPath);
|
|
111
|
+
const logger = createLogger()
|
|
28
112
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
113
|
+
if (!acquireSyncLock()) {
|
|
114
|
+
return createParseError("Sync already in progress")
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
logger.debug("[sync] starting", "source=scvd mode=full")
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const result = await syncAllUnlocked(client, indexPath)
|
|
121
|
+
if (result.success) {
|
|
122
|
+
logger.debug(
|
|
123
|
+
"[sync] complete",
|
|
124
|
+
`source=scvd newFindings=${result.newFindings} totalIndexed=${result.totalIndexed}`,
|
|
125
|
+
)
|
|
126
|
+
} else {
|
|
127
|
+
const reason = result.status === "error" ? result.reason : result.status
|
|
128
|
+
logger.debug("[sync] failed", `source=scvd reason=${reason}`)
|
|
129
|
+
}
|
|
130
|
+
return result
|
|
35
131
|
} catch (error) {
|
|
36
|
-
|
|
132
|
+
const errorResult = buildErrorResult(error)
|
|
133
|
+
logger.debug("[sync] failed", `source=scvd reason=${errorResult.reason}`)
|
|
134
|
+
await persistErrorMetadata(indexPath, errorResult).catch(() => {
|
|
135
|
+
logger.debug("Failed to persist sync error metadata")
|
|
136
|
+
})
|
|
137
|
+
return errorResult
|
|
138
|
+
} finally {
|
|
139
|
+
releaseSyncLock()
|
|
37
140
|
}
|
|
38
141
|
}
|
|
39
142
|
|
|
40
|
-
export async function syncIncremental(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
)
|
|
143
|
+
export async function syncIncremental(client: ScvdClient, indexPath: string): Promise<SyncResult> {
|
|
144
|
+
const logger = createLogger()
|
|
145
|
+
|
|
146
|
+
if (!acquireSyncLock()) {
|
|
147
|
+
return createParseError("Sync already in progress")
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
logger.debug("[sync] starting", "source=scvd mode=incremental")
|
|
151
|
+
|
|
44
152
|
try {
|
|
45
|
-
const [
|
|
46
|
-
client.fetchStats(),
|
|
153
|
+
const [statsResult, existingIndex] = await Promise.all([
|
|
154
|
+
withRetry(() => client.fetchStats(), {
|
|
155
|
+
maxAttempts: RETRY_MAX_ATTEMPTS,
|
|
156
|
+
baseDelayMs: RETRY_BASE_DELAY_MS,
|
|
157
|
+
shouldRetry: shouldRetrySyncError,
|
|
158
|
+
}),
|
|
47
159
|
loadIndex(indexPath),
|
|
48
|
-
])
|
|
160
|
+
])
|
|
161
|
+
|
|
162
|
+
if (!statsResult.success) {
|
|
163
|
+
const errorResult = buildErrorResult(statsResult.error)
|
|
164
|
+
errorResult.attempts = statsResult.attempts
|
|
165
|
+
await persistErrorMetadata(indexPath, errorResult).catch(() => {
|
|
166
|
+
logger.debug("Failed to persist sync error metadata")
|
|
167
|
+
})
|
|
168
|
+
return errorResult
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (statsResult.value === undefined) {
|
|
172
|
+
const errorResult = createParseError("SCVD sync returned no stats payload")
|
|
173
|
+
errorResult.attempts = statsResult.attempts
|
|
174
|
+
await persistErrorMetadata(indexPath, errorResult).catch(() => {
|
|
175
|
+
logger.debug("Failed to persist sync error metadata")
|
|
176
|
+
})
|
|
177
|
+
return errorResult
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const stats = statsResult.value
|
|
49
181
|
|
|
50
182
|
if (existingIndex && existingIndex.totalFindings === stats.total) {
|
|
51
|
-
return {
|
|
52
|
-
success: true,
|
|
183
|
+
return createSyncSuccess({
|
|
53
184
|
newFindings: 0,
|
|
54
185
|
totalIndexed: existingIndex.totalFindings,
|
|
55
186
|
lastSync: existingIndex.lastSync,
|
|
56
|
-
}
|
|
187
|
+
})
|
|
57
188
|
}
|
|
58
189
|
|
|
59
|
-
return await
|
|
190
|
+
return await syncAllUnlocked(client, indexPath)
|
|
60
191
|
} catch (error) {
|
|
61
|
-
|
|
192
|
+
const errorResult = buildErrorResult(error)
|
|
193
|
+
await persistErrorMetadata(indexPath, errorResult).catch(() => {
|
|
194
|
+
logger.debug("Failed to persist sync error metadata")
|
|
195
|
+
})
|
|
196
|
+
return errorResult
|
|
197
|
+
} finally {
|
|
198
|
+
releaseSyncLock()
|
|
62
199
|
}
|
|
63
200
|
}
|
|
201
|
+
const STALE_THRESHOLD_DAYS = 7
|
|
202
|
+
|
|
203
|
+
export function isSyncStale(
|
|
204
|
+
index: ScvdIndex | null,
|
|
205
|
+
thresholdDays: number = STALE_THRESHOLD_DAYS,
|
|
206
|
+
): boolean {
|
|
207
|
+
if (!index || !index.lastSync) return true
|
|
208
|
+
const lastSyncDate = new Date(index.lastSync)
|
|
209
|
+
const now = new Date()
|
|
210
|
+
const diffMs = now.getTime() - lastSyncDate.getTime()
|
|
211
|
+
const diffDays = diffMs / (1000 * 60 * 60 * 24)
|
|
212
|
+
return diffDays > thresholdDays
|
|
213
|
+
}
|
|
64
214
|
|
|
65
215
|
export async function getSyncStatus(indexPath: string): Promise<{
|
|
66
|
-
lastSync: string | null
|
|
67
|
-
totalFindings: number
|
|
68
|
-
healthy: boolean
|
|
216
|
+
lastSync: string | null
|
|
217
|
+
totalFindings: number
|
|
218
|
+
healthy: boolean
|
|
219
|
+
stale: boolean
|
|
220
|
+
metadata: ScvdIndexMetadata | null
|
|
221
|
+
hint?: string
|
|
69
222
|
}> {
|
|
70
|
-
const
|
|
223
|
+
const logger = createLogger()
|
|
224
|
+
const index = await loadIndex(indexPath)
|
|
71
225
|
|
|
72
226
|
if (!index) {
|
|
73
227
|
return {
|
|
74
228
|
lastSync: null,
|
|
75
229
|
totalFindings: 0,
|
|
76
230
|
healthy: false,
|
|
77
|
-
|
|
231
|
+
stale: true,
|
|
232
|
+
metadata: null,
|
|
233
|
+
hint: "SCVD data is missing. Run argus_sync_knowledge to populate.",
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const stale = isSyncStale(index)
|
|
238
|
+
|
|
239
|
+
if (stale) {
|
|
240
|
+
const lastSyncDate = new Date(index.lastSync)
|
|
241
|
+
const daysSince = Math.floor((Date.now() - lastSyncDate.getTime()) / (1000 * 60 * 60 * 24))
|
|
242
|
+
logger.debug("[sync] stale", `source=scvd daysSince=${daysSince}`)
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
lastSync: index.lastSync,
|
|
246
|
+
totalFindings: index.totalFindings,
|
|
247
|
+
healthy: true,
|
|
248
|
+
stale: true,
|
|
249
|
+
metadata: index.metadata ?? null,
|
|
250
|
+
hint: "SCVD data is stale. Run argus_sync_knowledge to update.",
|
|
251
|
+
}
|
|
78
252
|
}
|
|
79
253
|
|
|
80
254
|
return {
|
|
81
255
|
lastSync: index.lastSync,
|
|
82
256
|
totalFindings: index.totalFindings,
|
|
83
257
|
healthy: true,
|
|
84
|
-
|
|
258
|
+
stale: false,
|
|
259
|
+
metadata: index.metadata ?? null,
|
|
260
|
+
}
|
|
85
261
|
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
export type SourceMode = "baked-in" | "on-demand" | "hybrid"
|
|
2
|
+
|
|
3
|
+
export interface SourceManifest {
|
|
4
|
+
name: string
|
|
5
|
+
mode: SourceMode
|
|
6
|
+
url: string
|
|
7
|
+
license: string
|
|
8
|
+
updateCadence: string
|
|
9
|
+
lastUpdated?: string
|
|
10
|
+
hash?: string
|
|
11
|
+
version?: string
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class IngestionRegistry {
|
|
15
|
+
private sources = new Map<string, SourceManifest>()
|
|
16
|
+
|
|
17
|
+
register(manifest: SourceManifest): void {
|
|
18
|
+
this.sources.set(manifest.name, manifest)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
get(name: string): SourceManifest | null {
|
|
22
|
+
return this.sources.get(name) ?? null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
list(): SourceManifest[] {
|
|
26
|
+
return Array.from(this.sources.values())
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
getByMode(mode: SourceMode): SourceManifest[] {
|
|
30
|
+
return Array.from(this.sources.values()).filter((m) => m.mode === mode)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function createDefaultRegistry(): IngestionRegistry {
|
|
35
|
+
const registry = new IngestionRegistry()
|
|
36
|
+
|
|
37
|
+
registry.register({
|
|
38
|
+
name: "cyfrin",
|
|
39
|
+
mode: "baked-in",
|
|
40
|
+
url: "https://github.com/Cyfrin/audit-checklist",
|
|
41
|
+
license: "unspecified",
|
|
42
|
+
updateCadence: "per-release",
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
registry.register({
|
|
46
|
+
name: "kadenzipfel",
|
|
47
|
+
mode: "baked-in",
|
|
48
|
+
url: "https://github.com/kadenzipfel/smart-contract-vulnerabilities",
|
|
49
|
+
license: "MIT",
|
|
50
|
+
updateCadence: "per-release",
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
registry.register({
|
|
54
|
+
name: "defifofum",
|
|
55
|
+
mode: "baked-in",
|
|
56
|
+
url: "https://github.com/DeFiFoFum/fofum-solidity-skills",
|
|
57
|
+
license: "MIT",
|
|
58
|
+
updateCadence: "per-release",
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
registry.register({
|
|
62
|
+
name: "smartbugs",
|
|
63
|
+
mode: "baked-in",
|
|
64
|
+
url: "https://github.com/smartbugs/smartbugs-curated",
|
|
65
|
+
license: "Apache-2.0",
|
|
66
|
+
updateCadence: "per-release",
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
registry.register({
|
|
70
|
+
name: "sunweb3sec",
|
|
71
|
+
mode: "baked-in",
|
|
72
|
+
url: "https://github.com/SunWeb3Sec/DeFiHackLabs",
|
|
73
|
+
license: "reference-only",
|
|
74
|
+
updateCadence: "per-release",
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
registry.register({
|
|
78
|
+
name: "scvd",
|
|
79
|
+
mode: "hybrid",
|
|
80
|
+
url: "https://api.scvd.dev",
|
|
81
|
+
license: "CC0",
|
|
82
|
+
updateCadence: "on-sync",
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
registry.register({
|
|
86
|
+
name: "trailofbits",
|
|
87
|
+
mode: "hybrid",
|
|
88
|
+
url: "https://github.com/trailofbits/solidity-security-research",
|
|
89
|
+
license: "varies",
|
|
90
|
+
updateCadence: "on-install",
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
registry.register({
|
|
94
|
+
name: "solodit",
|
|
95
|
+
mode: "on-demand",
|
|
96
|
+
url: "https://solodit.xyz",
|
|
97
|
+
license: "varies",
|
|
98
|
+
updateCadence: "per-request",
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
return registry
|
|
102
|
+
}
|
package/src/managers/index.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export type {
|
|
1
|
+
export type { AuditStateManager, BackgroundManager, Managers } from "./types"
|
package/src/managers/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AuditState } from "../state/types"
|
|
1
|
+
import type { AuditState } from "../state/types"
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* BackgroundManager interface
|
|
@@ -12,32 +12,32 @@ export interface BackgroundManager {
|
|
|
12
12
|
* @param options - Optional configuration (priority, timeout, etc.)
|
|
13
13
|
* @returns taskId - Unique identifier for tracking this task
|
|
14
14
|
*/
|
|
15
|
-
dispatch(agentName: string, prompt: string, options?: { priority?: number }): string
|
|
15
|
+
dispatch(agentName: string, prompt: string, options?: { priority?: number }): string
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
18
|
* Cancel a running background task
|
|
19
19
|
* @param taskId - The task ID to cancel
|
|
20
20
|
*/
|
|
21
|
-
cancel(taskId: string): void
|
|
21
|
+
cancel(taskId: string): void
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Get the result of a completed background task
|
|
25
25
|
* @param taskId - The task ID to retrieve results for
|
|
26
26
|
* @returns Promise resolving to the task result
|
|
27
27
|
*/
|
|
28
|
-
getResult(taskId: string): Promise<unknown
|
|
28
|
+
getResult(taskId: string): Promise<unknown>
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* Register a callback to be invoked when a task completes
|
|
32
32
|
* @param callback - Function called with (taskId, result) when task finishes
|
|
33
33
|
*/
|
|
34
|
-
onComplete(callback: (taskId: string, result: unknown) => void): void
|
|
34
|
+
onComplete(callback: (taskId: string, result: unknown) => void): void
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* Get the number of currently active/running tasks
|
|
38
38
|
* @returns Number of active tasks
|
|
39
39
|
*/
|
|
40
|
-
getActiveCount(): number
|
|
40
|
+
getActiveCount(): number
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
/**
|
|
@@ -49,30 +49,35 @@ export interface AuditStateManager {
|
|
|
49
49
|
* Load audit state from persistent storage
|
|
50
50
|
* @returns Promise resolving to AuditState or null if not found
|
|
51
51
|
*/
|
|
52
|
-
load(): Promise<AuditState | null
|
|
52
|
+
load(): Promise<AuditState | null>
|
|
53
53
|
|
|
54
54
|
/**
|
|
55
55
|
* Save audit state to persistent storage
|
|
56
56
|
* @param state - The AuditState to persist
|
|
57
57
|
*/
|
|
58
|
-
save(state: AuditState): Promise<void
|
|
58
|
+
save(state: AuditState): Promise<void>
|
|
59
59
|
|
|
60
60
|
/**
|
|
61
61
|
* Get the current in-memory audit state
|
|
62
62
|
* @returns The current AuditState or null if not loaded
|
|
63
63
|
*/
|
|
64
|
-
get(): AuditState | null
|
|
64
|
+
get(): AuditState | null
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Update the audit state with a partial patch
|
|
68
68
|
* @param patch - Partial AuditState object with fields to update
|
|
69
69
|
*/
|
|
70
|
-
update(patch: Partial<AuditState>): Promise<void
|
|
70
|
+
update(patch: Partial<AuditState>): Promise<void>
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
73
|
* Reset the audit state (clear all data)
|
|
74
74
|
*/
|
|
75
|
-
reset(): Promise<void
|
|
75
|
+
reset(): Promise<void>
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Archive current state (if meaningful) then reset
|
|
79
|
+
*/
|
|
80
|
+
archive(): Promise<void>
|
|
76
81
|
}
|
|
77
82
|
|
|
78
83
|
/**
|
|
@@ -80,6 +85,6 @@ export interface AuditStateManager {
|
|
|
80
85
|
* Container for all manager instances
|
|
81
86
|
*/
|
|
82
87
|
export type Managers = {
|
|
83
|
-
backgroundManager: BackgroundManager
|
|
84
|
-
auditStateManager: AuditStateManager
|
|
85
|
-
}
|
|
88
|
+
backgroundManager: BackgroundManager
|
|
89
|
+
auditStateManager: AuditStateManager
|
|
90
|
+
}
|
package/src/plugin-interface.ts
CHANGED
|
@@ -11,14 +11,25 @@ export function createPluginInterface(args: {
|
|
|
11
11
|
}): PluginReturn {
|
|
12
12
|
const { tools, hooks } = args
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
result["
|
|
21
|
-
|
|
14
|
+
const result: PluginReturn = {
|
|
15
|
+
tool: tools,
|
|
16
|
+
config: hooks.config,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (hooks["chat.params"]) {
|
|
20
|
+
result["chat.params"] = hooks["chat.params"]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (hooks["chat.message"]) {
|
|
24
|
+
result["chat.message"] = hooks["chat.message"]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (hooks["experimental.chat.system.transform"]) {
|
|
28
|
+
result["experimental.chat.system.transform"] = hooks["experimental.chat.system.transform"]
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (hooks["experimental.session.compacting"]) {
|
|
32
|
+
result["experimental.session.compacting"] = hooks["experimental.session.compacting"]
|
|
22
33
|
}
|
|
23
34
|
|
|
24
35
|
if (hooks["tool.execute.after"]) {
|
|
@@ -1,63 +1,73 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
import { createLogger } from "./logger"
|
|
4
|
+
|
|
5
|
+
const logger = createLogger()
|
|
4
6
|
|
|
5
7
|
export function hasBinary(name: string): boolean {
|
|
6
8
|
try {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
+
const result = Bun.spawnSync(["which", name], {
|
|
10
|
+
stdout: "ignore",
|
|
11
|
+
stderr: "ignore",
|
|
12
|
+
timeout: 5_000,
|
|
13
|
+
})
|
|
14
|
+
return result.exitCode === 0
|
|
9
15
|
} catch (_e) {
|
|
10
|
-
return false
|
|
16
|
+
return false
|
|
11
17
|
}
|
|
12
18
|
}
|
|
13
19
|
|
|
14
|
-
export function parseSolcVersion(target: string): string | undefined {
|
|
15
|
-
const foundryToml = join(target, "foundry.toml")
|
|
16
|
-
if (
|
|
17
|
-
const content =
|
|
18
|
-
const match = content.match(/solc\s*=\s*["']([^"']+)["']/)
|
|
19
|
-
if (match?.[1]) return match[1]
|
|
20
|
+
export async function parseSolcVersion(target: string): Promise<string | undefined> {
|
|
21
|
+
const foundryToml = join(target, "foundry.toml")
|
|
22
|
+
if (await Bun.file(foundryToml).exists()) {
|
|
23
|
+
const content = await Bun.file(foundryToml).text()
|
|
24
|
+
const match = content.match(/solc\s*=\s*["']([^"']+)["']/)
|
|
25
|
+
if (match?.[1]) return match[1]
|
|
20
26
|
}
|
|
21
27
|
|
|
22
|
-
const solFiles = [
|
|
23
|
-
if (
|
|
24
|
-
solFiles.push(target)
|
|
28
|
+
const solFiles: string[] = []
|
|
29
|
+
if (target.endsWith(".sol") && (await Bun.file(target).exists())) {
|
|
30
|
+
solFiles.push(target)
|
|
25
31
|
} else {
|
|
26
|
-
const srcDir = join(target, "src")
|
|
32
|
+
const srcDir = join(target, "src")
|
|
27
33
|
if (existsSync(srcDir)) {
|
|
28
34
|
try {
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
35
|
+
const proc = Bun.spawn(["find", srcDir, "-maxdepth", "3", "-name", "*.sol"], {
|
|
36
|
+
stdout: "pipe",
|
|
37
|
+
stderr: "pipe",
|
|
38
|
+
signal: AbortSignal.timeout(10_000),
|
|
32
39
|
})
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
.
|
|
36
|
-
|
|
40
|
+
const exitCode = await proc.exited
|
|
41
|
+
if (exitCode === 0) {
|
|
42
|
+
const output = await new Response(proc.stdout).text()
|
|
43
|
+
solFiles.push(...output.trim().split("\n").filter(Boolean))
|
|
44
|
+
}
|
|
37
45
|
} catch (_findErr) {
|
|
46
|
+
logger.debug("find command failed for .sol files")
|
|
38
47
|
}
|
|
39
48
|
}
|
|
40
49
|
}
|
|
41
50
|
|
|
42
51
|
for (const file of solFiles) {
|
|
43
|
-
if (!
|
|
52
|
+
if (!file.endsWith(".sol") || !(await Bun.file(file).exists())) continue
|
|
44
53
|
try {
|
|
45
|
-
const content =
|
|
46
|
-
const pragma = content.match(/pragma\s+solidity\s+[\^~>=<]*\s*([\d.]+)/)
|
|
47
|
-
if (pragma?.[1]) return pragma[1]
|
|
54
|
+
const content = await Bun.file(file).text()
|
|
55
|
+
const pragma = content.match(/pragma\s+solidity\s+[\^~>=<]*\s*([\d.]+)/)
|
|
56
|
+
if (pragma?.[1]) return pragma[1]
|
|
48
57
|
} catch (_readErr) {
|
|
58
|
+
logger.debug("Failed to read .sol file for pragma detection")
|
|
49
59
|
}
|
|
50
60
|
}
|
|
51
|
-
return undefined
|
|
61
|
+
return undefined
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
export function extractContractNames(filePath: string): string[] {
|
|
55
|
-
if (!
|
|
64
|
+
export async function extractContractNames(filePath: string): Promise<string[]> {
|
|
65
|
+
if (!(await Bun.file(filePath).exists())) return []
|
|
56
66
|
try {
|
|
57
|
-
const content =
|
|
58
|
-
const matches = content.matchAll(/\b(?:contract|library|interface)\s+(\w+)/g)
|
|
59
|
-
return Array.from(matches, (m) => m[1]).filter(Boolean) as string[]
|
|
67
|
+
const content = await Bun.file(filePath).text()
|
|
68
|
+
const matches = content.matchAll(/\b(?:contract|library|interface)\s+(\w+)/g)
|
|
69
|
+
return Array.from(matches, (m) => m[1]).filter(Boolean) as string[]
|
|
60
70
|
} catch (_e) {
|
|
61
|
-
return []
|
|
71
|
+
return []
|
|
62
72
|
}
|
|
63
73
|
}
|