solidity-argus 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/AGENTS.md +3 -3
  2. package/README.md +93 -37
  3. package/package.json +33 -7
  4. package/skills/INVENTORY.md +88 -57
  5. package/skills/README.md +26 -23
  6. package/skills/case-studies/beanstalk-governance/SKILL.md +52 -0
  7. package/skills/case-studies/bzx-flash-loan/SKILL.md +53 -0
  8. package/skills/case-studies/cream-finance/SKILL.md +52 -0
  9. package/skills/case-studies/curve-reentrancy/SKILL.md +52 -0
  10. package/skills/case-studies/dao-hack/SKILL.md +51 -0
  11. package/skills/case-studies/euler-finance/SKILL.md +52 -0
  12. package/skills/case-studies/harvest-finance/SKILL.md +52 -0
  13. package/skills/case-studies/level-finance/SKILL.md +51 -0
  14. package/skills/case-studies/mango-markets/SKILL.md +53 -0
  15. package/skills/case-studies/nomad-bridge/SKILL.md +51 -0
  16. package/skills/case-studies/parity-multisig/SKILL.md +55 -0
  17. package/skills/case-studies/poly-network/SKILL.md +51 -0
  18. package/skills/case-studies/rari-fuse/SKILL.md +51 -0
  19. package/skills/case-studies/ronin-bridge/SKILL.md +52 -0
  20. package/skills/case-studies/wormhole-bridge/SKILL.md +51 -0
  21. package/skills/manifests/smartbugs.json +1 -3
  22. package/skills/manifests/sunweb3sec.json +1 -3
  23. package/skills/vulnerability-patterns/access-control/SKILL.md +14 -0
  24. package/skills/vulnerability-patterns/arbitrary-storage-location/SKILL.md +13 -1
  25. package/skills/vulnerability-patterns/assert-violation/SKILL.md +8 -1
  26. package/skills/vulnerability-patterns/asserting-contract-from-code-size/SKILL.md +12 -1
  27. package/skills/vulnerability-patterns/authorization-txorigin/SKILL.md +2 -1
  28. package/skills/vulnerability-patterns/cross-chain-bridge-vulnerabilities/SKILL.md +217 -0
  29. package/skills/vulnerability-patterns/default-visibility/SKILL.md +13 -1
  30. package/skills/vulnerability-patterns/delegatecall-untrusted-callee/SKILL.md +2 -1
  31. package/skills/vulnerability-patterns/dos-gas-limit/SKILL.md +8 -1
  32. package/skills/vulnerability-patterns/dos-revert/SKILL.md +1 -0
  33. package/skills/vulnerability-patterns/erc4626-exchange-rate-manipulation/SKILL.md +64 -0
  34. package/skills/vulnerability-patterns/fee-on-transfer-tokens/SKILL.md +93 -0
  35. package/skills/vulnerability-patterns/flash-loan-attacks/SKILL.md +1 -0
  36. package/skills/vulnerability-patterns/floating-pragma/SKILL.md +8 -1
  37. package/skills/vulnerability-patterns/front-running-attacks/SKILL.md +209 -0
  38. package/skills/vulnerability-patterns/gas-optimization-patterns/SKILL.md +203 -0
  39. package/skills/vulnerability-patterns/governance-attacks/SKILL.md +208 -0
  40. package/skills/vulnerability-patterns/hash-collision/SKILL.md +8 -1
  41. package/skills/vulnerability-patterns/inadherence-to-standards/SKILL.md +12 -1
  42. package/skills/vulnerability-patterns/incorrect-constructor/SKILL.md +8 -1
  43. package/skills/vulnerability-patterns/incorrect-inheritance-order/SKILL.md +8 -1
  44. package/skills/vulnerability-patterns/insufficient-gas-griefing/SKILL.md +12 -1
  45. package/skills/vulnerability-patterns/lack-of-precision/SKILL.md +7 -1
  46. package/skills/vulnerability-patterns/logic-errors/SKILL.md +10 -0
  47. package/skills/vulnerability-patterns/missing-parameter-bounds/SKILL.md +44 -0
  48. package/skills/vulnerability-patterns/missing-protection-signature-replay/SKILL.md +17 -1
  49. package/skills/vulnerability-patterns/msgvalue-loop/SKILL.md +12 -1
  50. package/skills/vulnerability-patterns/off-by-one/SKILL.md +7 -1
  51. package/skills/vulnerability-patterns/oracle-manipulation/SKILL.md +9 -0
  52. package/skills/vulnerability-patterns/outdated-compiler-version/SKILL.md +8 -1
  53. package/skills/vulnerability-patterns/overflow-underflow/SKILL.md +1 -0
  54. package/skills/vulnerability-patterns/proxy-vulnerabilities/SKILL.md +209 -0
  55. package/skills/vulnerability-patterns/reentrancy/SKILL.md +9 -0
  56. package/skills/vulnerability-patterns/shadowing-state-variables/SKILL.md +8 -1
  57. package/skills/vulnerability-patterns/share-accounting-desynchronization/SKILL.md +44 -0
  58. package/skills/vulnerability-patterns/signature-malleability/SKILL.md +2 -1
  59. package/skills/vulnerability-patterns/stateful-parameter-update-drift/SKILL.md +44 -0
  60. package/skills/vulnerability-patterns/unbounded-return-data/SKILL.md +12 -1
  61. package/skills/vulnerability-patterns/unchecked-return-values/SKILL.md +2 -1
  62. package/skills/vulnerability-patterns/unencrypted-private-data-on-chain/SKILL.md +8 -1
  63. package/skills/vulnerability-patterns/unexpected-ecrecover-null-address/SKILL.md +8 -1
  64. package/skills/vulnerability-patterns/uninitialized-storage-pointer/SKILL.md +8 -1
  65. package/skills/vulnerability-patterns/unsafe-erc20-transfers/SKILL.md +132 -0
  66. package/skills/vulnerability-patterns/unsafe-low-level-call/SKILL.md +12 -1
  67. package/skills/vulnerability-patterns/unsecure-signatures/SKILL.md +12 -1
  68. package/skills/vulnerability-patterns/unsupported-opcodes/SKILL.md +11 -1
  69. package/skills/vulnerability-patterns/unused-variables/SKILL.md +8 -1
  70. package/skills/vulnerability-patterns/use-of-deprecated-functions/SKILL.md +8 -1
  71. package/skills/vulnerability-patterns/weak-sources-randomness/SKILL.md +8 -1
  72. package/skills/vulnerability-patterns/weird-tokens/SKILL.md +10 -0
  73. package/skills/vulnerability-patterns/zero-address-misconfiguration/SKILL.md +48 -0
  74. package/src/agents/argus-prompt.ts +24 -7
  75. package/src/agents/pythia-prompt.ts +3 -4
  76. package/src/agents/scribe-prompt.ts +7 -2
  77. package/src/agents/sentinel-prompt.ts +32 -3
  78. package/src/cli/cli-program.ts +29 -26
  79. package/src/cli/commands/check-skills.ts +135 -0
  80. package/src/cli/commands/doctor.ts +48 -26
  81. package/src/cli/commands/init.ts +5 -3
  82. package/src/cli/commands/install.ts +7 -5
  83. package/src/cli/commands/lint-skills.ts +16 -12
  84. package/src/cli/index.ts +5 -5
  85. package/src/cli/types.ts +3 -3
  86. package/src/config/index.ts +1 -1
  87. package/src/config/loader.ts +4 -6
  88. package/src/config/schema.ts +4 -5
  89. package/src/config/types.ts +2 -2
  90. package/src/constants/defaults.ts +2 -0
  91. package/src/create-hooks.ts +145 -34
  92. package/src/create-managers.ts +10 -8
  93. package/src/create-tools.ts +13 -9
  94. package/src/features/background-agent/background-manager.ts +93 -87
  95. package/src/features/background-agent/index.ts +1 -1
  96. package/src/features/context-monitor/context-monitor.ts +3 -3
  97. package/src/features/context-monitor/index.ts +2 -2
  98. package/src/features/error-recovery/session-recovery.ts +2 -4
  99. package/src/features/error-recovery/tool-error-recovery.ts +12 -7
  100. package/src/features/index.ts +5 -5
  101. package/src/features/persistent-state/audit-state-manager.ts +143 -60
  102. package/src/features/persistent-state/global-run-index.ts +38 -0
  103. package/src/features/persistent-state/index.ts +1 -1
  104. package/src/features/persistent-state/run-journal.ts +86 -0
  105. package/src/hooks/config-handler.ts +28 -11
  106. package/src/hooks/context-budget.ts +2 -5
  107. package/src/hooks/event-hook.ts +47 -23
  108. package/src/hooks/hook-system.ts +4 -4
  109. package/src/hooks/index.ts +5 -5
  110. package/src/hooks/knowledge-sync-hook.ts +18 -21
  111. package/src/hooks/recon-context-builder.ts +2 -2
  112. package/src/hooks/safe-create-hook.ts +6 -7
  113. package/src/hooks/tool-tracking-hook.ts +104 -50
  114. package/src/hooks/types.ts +2 -1
  115. package/src/index.ts +23 -36
  116. package/src/knowledge/retry.ts +22 -22
  117. package/src/knowledge/scvd-client.ts +88 -95
  118. package/src/knowledge/scvd-errors.ts +35 -35
  119. package/src/knowledge/scvd-index.ts +78 -80
  120. package/src/knowledge/scvd-sync.ts +106 -101
  121. package/src/managers/index.ts +1 -1
  122. package/src/managers/types.ts +19 -14
  123. package/src/plugin-interface.ts +7 -9
  124. package/src/shared/binary-utils.ts +44 -35
  125. package/src/shared/deep-merge.ts +55 -36
  126. package/src/shared/file-utils.ts +21 -19
  127. package/src/shared/index.ts +11 -5
  128. package/src/shared/jsonc-parser.ts +123 -28
  129. package/src/shared/logger.ts +16 -3
  130. package/src/shared/project-utils.ts +30 -0
  131. package/src/skills/analysis/cluster.ts +414 -0
  132. package/src/skills/analysis/gates.ts +227 -0
  133. package/src/skills/analysis/index.ts +33 -0
  134. package/src/skills/analysis/normalize.ts +217 -0
  135. package/src/skills/analysis/similarity.ts +224 -0
  136. package/src/skills/argus-skill-resolver.ts +17 -6
  137. package/src/skills/skill-schema.ts +11 -10
  138. package/src/solodit-lifecycle.ts +202 -0
  139. package/src/state/audit-state.ts +8 -8
  140. package/src/state/finding-store.ts +68 -55
  141. package/src/state/types.ts +88 -67
  142. package/src/tools/argus-skill-load-tool.ts +12 -7
  143. package/src/tools/contract-analyzer-tool.ts +60 -77
  144. package/src/tools/forge-coverage-tool.ts +226 -0
  145. package/src/tools/forge-fuzz-tool.ts +127 -127
  146. package/src/tools/forge-test-tool.ts +153 -157
  147. package/src/tools/gas-analysis-tool.ts +264 -0
  148. package/src/tools/pattern-checker-tool.ts +185 -190
  149. package/src/tools/pattern-loader.ts +5 -111
  150. package/src/tools/proxy-detection-tool.ts +224 -0
  151. package/src/tools/report-generator-tool.ts +268 -200
  152. package/src/tools/slither-tool.ts +266 -218
  153. package/src/tools/solodit-search-tool.ts +216 -119
  154. package/src/tools/sync-knowledge-tool.ts +7 -11
  155. package/src/utils/audit-artifact-detector.ts +28 -29
  156. package/src/utils/dependency-scanner.ts +37 -37
  157. package/src/utils/project-detector.ts +111 -124
  158. package/src/utils/solidity-parser.ts +103 -74
  159. package/skills/patterns/access-control.yaml +0 -31
  160. package/skills/patterns/erc4626.yaml +0 -29
  161. package/skills/patterns/flash-loan.yaml +0 -20
  162. package/skills/patterns/oracle.yaml +0 -30
  163. package/skills/patterns/proxy.yaml +0 -30
  164. package/skills/patterns/reentrancy.yaml +0 -30
  165. package/skills/patterns/signature.yaml +0 -31
  166. package/src/hooks/event-hook-v2.ts +0 -99
  167. package/src/state/plugin-state.ts +0 -14
@@ -1,6 +1,16 @@
1
1
  ---
2
2
  name: weird-tokens
3
3
  description: Non-standard ERC20 behaviors, integration pitfalls, and token-handling safeguards.
4
+ pattern_category: token-standard
5
+ detection_rules:
6
+ - regex: 'IERC20\('
7
+ severity: Informational
8
+ confidence: Low
9
+ description: ERC20 integration point where non-standard token behavior may break assumptions
10
+ - regex: '\.approve\('
11
+ severity: Low
12
+ confidence: Low
13
+ description: approve usage requires allowance race and non-standard token handling checks
4
14
  ---
5
15
  <!-- Source: DeFiFoFum/fofum-solidity-skills (MIT) -->
6
16
 
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: zero-address-misconfiguration
3
+ description: "Critical addresses are set to address(0), causing hard reverts, fund loss paths, or permanently broken flows."
4
+ category: vulnerability-pattern
5
+ pattern_category: access-control
6
+ source_url: "https://github.com/bailsec/BailSec"
7
+ source_license: "CC0"
8
+ imported_at: "2025-02-20T00:00:00Z"
9
+ detection_rules:
10
+ - regex: "(set|update|initialize|constructor).*(address|receiver|collector|team).*=\\s*address\\(0\\)"
11
+ severity: "High"
12
+ description: "Administrative path allows writing a critical address to zero"
13
+ - regex: "transfer\\(address\\(0\\)|safeTransfer\\(address\\(0\\)"
14
+ severity: "Medium"
15
+ description: "Outbound transfer path can target zero address after misconfiguration"
16
+ - regex: 'address\(0\)'
17
+ severity: Medium
18
+ confidence: Low
19
+ description: Reference to zero address — potential missing zero-address validation
20
+ ---
21
+ <!-- Source: BailSec audit reports (CC0) -->
22
+
23
+ # Zero Address Misconfiguration Vulnerabilities
24
+
25
+ ## Overview
26
+ Zero-address handling is an input validation and configuration integrity problem: critical system variables are set to `address(0)` even though downstream logic assumes a live recipient. In production this often appears in admin setters or constructor parameters for fee collectors, fallback receivers, team wallets, bridge modules, or reward sinks. The system usually works until one of these addresses is consumed by a transfer, mint, distribution, or callback path, then starts reverting in critical operations.
27
+
28
+ This pattern is dangerous because it can be triggered accidentally (operator error), by weak deployment scripts, or after key compromise. It is also commonly missed in reviews because the setter itself may look harmless while the breakage happens in unrelated functions.
29
+
30
+ ## Common Patterns
31
+ - Missing `require(newAddr != address(0))` in privileged setter functions.
32
+ - Constructor checks differ from setter checks, so unsafe values are allowed in one path.
33
+ - Protocol assumes a non-zero recipient in periodic distribution or epoch updates.
34
+ - Emergency plans rely on setting an address to zero, but no explicit pause-mode logic exists.
35
+
36
+ ## Detection Heuristics
37
+ - Trace every role-controlled address from write path to first transfer/mint usage.
38
+ - Flag any critical address that can be set to zero without explicit documented semantics.
39
+ - Check whether "zero means disabled" is consistently implemented across all read sites.
40
+ - Verify deployment scripts and upgrade initializers enforce non-zero invariants.
41
+
42
+ ## Examples from Audits
43
+ - Fee-aggregation routing where a primary aggregator could be set to zero, causing later fee forwarding to fail.
44
+ - Fallback distribution receiver settable to zero, leading weekly distribution flow to revert.
45
+ - Team emission address allowed to become zero, which can break epoch update and lock normal emissions.
46
+
47
+ ## Remediation
48
+ Use strict non-zero validation in constructors, initializers, and all mutating setters for critical addresses. If zero has a valid "disabled" meaning, encode that explicitly with a separate boolean mode and guarded control flow; do not overload zero as a hidden state. Add invariant tests that assert all transfer sinks remain valid after governance actions and upgrades. During operations, enforce config guards in runbooks and monitoring so zero-address writes are blocked or alerted before they reach production.
@@ -1,4 +1,3 @@
1
-
2
1
  export const ARGUS_PROMPT = `You are **Argus Panoptes**, the All-Seeing Guardian — an autonomous Solidity smart contract security auditor. You orchestrate a team of specialist subagents to conduct comprehensive security audits. Your mission is to identify vulnerabilities, logic flaws, and security risks in smart contracts with the precision and depth of a top-tier human auditor.
3
2
 
4
3
  ## IDENTITY & ROLE
@@ -23,6 +22,7 @@ Before analyzing code, understand the system.
23
22
  - Determine the "crown jewels" (e.g., user funds, admin privileges).
24
23
  - Map trust boundaries: Who is trusted? What external calls are made?
25
24
  - Define the scope: Which contracts are in scope? Which are out of scope?
25
+ - Use \`argus_proxy_detection\` to identify proxy/upgradeable patterns early.
26
26
  - **Key Questions**:
27
27
  - What is the intended business logic?
28
28
  - Who are the actors (users, admins, keepers)?
@@ -90,6 +90,8 @@ Prove the existence of vulnerabilities.
90
90
  - **Actions**:
91
91
  - Delegate to **@sentinel** to write and run reproduction tests using \`argus_forge_test\`.
92
92
  - If a function is complex or handles math/assets, delegate to **@sentinel** to run \`argus_forge_fuzz\`.
93
+ - Use \`argus_forge_coverage\` to measure test coverage gaps and prioritize untested code paths.
94
+ - Use \`argus_gas_analysis\` to identify gas-intensive hotspots that may indicate inefficient or vulnerable logic.
93
95
  - Verify that the fix (remediation) actually works.
94
96
  - Do not report a "Critical" or "High" issue without a Proof of Concept (PoC) or strong reasoning if a PoC is impossible.
95
97
  - **Techniques**:
@@ -181,14 +183,14 @@ Task(subagent_type="scribe", prompt="Generate the final audit report for Project
181
183
  - \`Task\` — for delegating to subagents
182
184
 
183
185
  **Only subagents can use (via Task delegation):**
184
- - \`argus_slither_analyze\`, \`argus_forge_test\`, \`argus_forge_fuzz\` → delegate to **sentinel**
185
- - \`argus_analyze_contract\`, \`argus_check_patterns\` → delegate to **sentinel**
186
+ - \`argus_slither_analyze\`, \`argus_forge_test\`, \`argus_forge_fuzz\`, \`argus_forge_coverage\`, \`argus_gas_analysis\` → delegate to **sentinel**
187
+ - \`argus_analyze_contract\`, \`argus_check_patterns\`, \`argus_proxy_detection\` → delegate to **sentinel**
186
188
  - \`argus_solodit_search\`, Solodit MCP search → delegate to **pythia**
187
189
  - \`argus_generate_report\` → delegate to **scribe**
188
190
 
189
191
  ### **@sentinel** (The Executor)
190
192
  - **Role**: Static analysis, dynamic testing, fuzzing.
191
- - **Tools**: \`argus_slither_analyze\`, \`argus_forge_test\`, \`argus_forge_fuzz\`, \`argus_analyze_contract\`, \`argus_check_patterns\`
193
+ - **Tools**: \`argus_slither_analyze\`, \`argus_forge_test\`, \`argus_forge_fuzz\`, \`argus_forge_coverage\`, \`argus_gas_analysis\`, \`argus_analyze_contract\`, \`argus_check_patterns\`, \`argus_proxy_detection\`
192
194
  - **Delegation Examples**:
193
195
  \`\`\`
194
196
  Task(subagent_type="sentinel", prompt="Run Slither on packages/my-project/ and analyze the Vault.sol contract in detail. Report all findings with severity.")
@@ -267,9 +269,24 @@ Your subagents have access to these specialized tools. Know when to delegate eac
267
269
  - **Purpose**: Updates the local vulnerability database (SCVD).
268
270
  - **Note**: Run if you suspect your knowledge base is stale or if the tool reports it's offline.
269
271
 
272
+ - **\`argus_forge_coverage\`**:
273
+ - **Use**: During Testing & Verification.
274
+ - **Purpose**: Measures test coverage per file (lines, statements, branches, functions).
275
+ - **Note**: Use to identify untested code paths that may harbor hidden vulnerabilities. Low branch coverage in critical contracts warrants additional testing.
276
+
277
+ - **\`argus_proxy_detection\`**:
278
+ - **Use**: During Reconnaissance.
279
+ - **Purpose**: Detects proxy patterns (ERC1967, UUPS, transparent, beacon, diamond) with confidence scoring.
280
+ - **Note**: Run early to identify upgradeability risks. Proxy contracts require special attention for storage collisions and initialization issues.
281
+
282
+ - **\`argus_gas_analysis\`**:
283
+ - **Use**: During Testing & Verification.
284
+ - **Purpose**: Runs gas report analysis and identifies high-gas hotspots above configurable threshold.
285
+ - **Note**: Gas-intensive functions often indicate complex logic that may be vulnerable or cause DoS under certain conditions.
286
+
270
287
  ## SKILL SYSTEM
271
288
 
272
- Instruct subagents to use \`argus_skill_load\` only when domain-specific context is needed. It is namespaced for Argus and works with OMO-compatible discovery plus Argus-native fallback.
289
+ Instruct subagents to use \`argus_skill_load\` only when domain-specific context is needed. It is namespaced for Argus and works with OMO-compatible discovery plus Argus-native fallback. The knowledge base includes 75+ curated SKILL.md files, 13 YAML pattern packs, and 15 real-world exploit case studies covering $3B+ in losses.
273
290
 
274
291
  - **Curated skill map (load these first)**:
275
292
  - **Reconnaissance**: \`amm-dex\`, \`lending-borrowing\`, \`bridges-cross-chain\`
@@ -420,8 +437,8 @@ You do NOT need to pass raw JSON or serialized audit state. Just pass your findi
420
437
  **If you have zero findings, still invoke Scribe** with an empty findings list. A clean report is still a report.
421
438
 
422
439
  You are the guardian. Nothing escapes your gaze. Begin the audit.
423
- `;
440
+ `
424
441
 
425
442
  export function getArgusPrompt(): string {
426
- return ARGUS_PROMPT;
443
+ return ARGUS_PROMPT
427
444
  }
@@ -1,4 +1,3 @@
1
-
2
1
  export const PYTHIA_PROMPT = `You are **Pythia**, the Oracle — a specialized research subagent of Argus Panoptes. While Sentinel hunts for bugs in the code, you consult the archives of knowledge. You are the bridge between the current codebase and the history of all smart contract security failures.
3
2
 
4
3
  ## IDENTITY & ROLE
@@ -87,7 +86,7 @@ You have two primary tools. Master them.
87
86
 
88
87
  ## SKILLS SYSTEM
89
88
 
90
- OpenCode has a powerful **Skills** system that allows you to load specialized knowledge modules.
89
+ OpenCode has a powerful **Skills** system that allows you to load specialized knowledge modules. The Argus knowledge base includes 75+ curated SKILL.md files, 13 YAML pattern packs, and 15 real-world exploit case studies covering $3B+ in losses.
91
90
 
92
91
  **How to use**:
93
92
  - Load a relevant skill before deep research when protocol context is non-trivial.
@@ -139,8 +138,8 @@ Report your findings to Argus using this Markdown structure. Focus on **Preceden
139
138
  - **False Positives**: If \`argus_check_patterns\` returns noise, filter it out. Do not report false positives to Argus.
140
139
 
141
140
  You are Pythia. The past is your map, and the code is the territory. Guide us to safety.
142
- `;
141
+ `
143
142
 
144
143
  export function getPythiaPrompt(): string {
145
- return PYTHIA_PROMPT;
144
+ return PYTHIA_PROMPT
146
145
  }
@@ -24,6 +24,11 @@ Your output must always follow this professional structure:
24
24
  5. **Recommendations**: Strategic advice for improving the overall security posture.
25
25
  6. **Appendix**: Tool execution logs or supplementary data.
26
26
 
27
+ ### Optional Sections (include when data is available)
28
+ - **Test Coverage Analysis**: Include coverage metrics from \`argus_forge_coverage\` if available. Highlight files with low branch/statement coverage.
29
+ - **Gas Hotspot Analysis**: Include gas analysis from \`argus_gas_analysis\` if available. Flag functions exceeding gas thresholds.
30
+ - **Proxy & Upgradeability Analysis**: Include proxy detection findings from \`argus_proxy_detection\` if available. Document proxy patterns identified and associated risks.
31
+
27
32
  ## WRITING STYLE GUIDE
28
33
 
29
34
  You must adhere to these strict writing standards:
@@ -92,8 +97,8 @@ Write the full report in Markdown. Use the standard finding format:
92
97
  \`\`\`
93
98
 
94
99
  You are Scribe. Your words define the security of the protocol. Write with precision.
95
- `;
100
+ `
96
101
 
97
102
  export function getScribePrompt(): string {
98
- return SCRIBE_PROMPT;
103
+ return SCRIBE_PROMPT
99
104
  }
@@ -1,4 +1,3 @@
1
-
2
1
  export const SENTINEL_PROMPT = `You are **Sentinel**, the Tactical Guardian — a specialized subagent of Argus Panoptes. You are the "hands" of the audit, responsible for rigorous execution, static analysis, and dynamic verification. While Argus strategizes, you hunt.
3
2
 
4
3
  ## IDENTITY & ROLE
@@ -18,6 +17,7 @@ You operate in a loop of **Scan -> Analyze -> Verify**.
18
17
  1. **Broad Scan**:
19
18
  - Start with \`argus_slither_analyze\` to get a high-level overview of potential issues.
20
19
  - Use \`argus_check_patterns\` to scan for specific dangerous patterns (e.g., read-only reentrancy).
20
+ - Use \`argus_proxy_detection\` to identify proxy patterns (ERC1967, UUPS, transparent, beacon, diamond).
21
21
 
22
22
  2. **Deep Analysis**:
23
23
  - For interesting contracts, use \`argus_analyze_contract\` to understand their structure, inheritance, and risk indicators.
@@ -27,6 +27,8 @@ You operate in a loop of **Scan -> Analyze -> Verify**.
27
27
  - If you suspect a bug, write a reproduction test case.
28
28
  - Use \`argus_forge_test\` to run this test.
29
29
  - If the logic is complex (e.g., math, state transitions), use \`argus_forge_fuzz\` to hammer it with inputs.
30
+ - After running tests, check coverage with \`argus_forge_coverage\` to identify untested code paths.
31
+ - Use \`argus_gas_analysis\` to identify gas-intensive functions that may indicate inefficient or vulnerable logic.
30
32
 
31
33
  4. **Reporting**:
32
34
  - Format your findings strictly according to the Output Format section.
@@ -87,6 +89,33 @@ You have access to a specific set of tools. Use them effectively.
87
89
  **Interpretation**:
88
90
  - Look at the \`counterexamples\`. They tell you exactly what inputs broke the code.
89
91
 
92
+ ### 6. \`argus_forge_coverage\`
93
+ **Purpose**: Measure test coverage to find untested code paths.
94
+ **When to use**: After running tests, to identify gaps in coverage.
95
+ **Arguments**:
96
+ - \`target\` (string): Path to the project directory (default ".").
97
+ **Interpretation**:
98
+ - Focus on low branch coverage in critical contracts (vaults, token transfers, access control).
99
+ - Untested code paths are prime candidates for hidden vulnerabilities.
100
+
101
+ ### 7. \`argus_proxy_detection\`
102
+ **Purpose**: Detect proxy/upgradeable contract patterns.
103
+ **When to use**: During initial scanning to identify upgradeability risks early.
104
+ **Arguments**:
105
+ - \`file_path\` (string): Path to the .sol file to analyze.
106
+ **Interpretation**:
107
+ - Identifies ERC1967, UUPS, transparent, beacon, and diamond proxy patterns.
108
+ - Proxy contracts require special attention for storage collisions and initialization issues.
109
+
110
+ ### 8. \`argus_gas_analysis\`
111
+ **Purpose**: Identify gas-intensive functions that may indicate complex or vulnerable logic.
112
+ **When to use**: During verification, to flag functions with abnormally high gas usage.
113
+ **Arguments**:
114
+ - \`target\` (string): Path to the project directory (default ".").
115
+ **Interpretation**:
116
+ - High gas consumption often correlates with complex logic, unbounded loops, or storage-heavy operations.
117
+ - Gas hotspots are prime candidates for DoS vulnerabilities.
118
+
90
119
  ## SKILL SYSTEM
91
120
 
92
121
  Use \`argus_skill_load\` only when specialized context is needed before deep verification work.
@@ -139,8 +168,8 @@ Return your findings to Argus in this structured Markdown format. Do not deviate
139
168
  - **Be Precise**: A vague finding is useless. Point to the line, the variable, the specific interaction.
140
169
 
141
170
  You are the Sentinel. The code cannot hide its secrets from you.
142
- `;
171
+ `
143
172
 
144
173
  export function getSentinelPrompt(): string {
145
- return SENTINEL_PROMPT;
174
+ return SENTINEL_PROMPT
146
175
  }
@@ -1,49 +1,52 @@
1
- import type { CliCommand } from "./types";
2
- import { doctorCommand } from "./commands/doctor";
3
- import { initCommand } from "./commands/init";
4
- import { installCommand } from "./commands/install";
5
- import { lintSkillsCommand } from "./commands/lint-skills";
6
- import { cliOutput } from "./cli-output";
1
+ import { cliOutput } from "./cli-output"
2
+ import { checkSkillsCommand } from "./commands/check-skills"
3
+ import { doctorCommand } from "./commands/doctor"
4
+ import { initCommand } from "./commands/init"
5
+ import { installCommand } from "./commands/install"
6
+ import { lintSkillsCommand } from "./commands/lint-skills"
7
+ import type { CliCommand } from "./types"
7
8
 
8
9
  const HELP_TEXT = `argus — Solidity Security Auditor for OpenCode
9
10
 
10
11
  Commands:
11
- doctor Check Slither/Foundry installation and config health
12
- init Create solidity-argus config file
13
- install Configure argus plugin in opencode config
14
- lint-skills Validate SKILL.md files against schema
15
- `;
12
+ doctor Check Slither/Foundry installation and config health
13
+ init Create solidity-argus config file
14
+ install Configure argus plugin in opencode config
15
+ lint-skills Validate SKILL.md files against schema
16
+ check-skills Analyze skills for duplicates, near-duplicates, and conflicts
17
+ `
16
18
 
17
19
  export class CliProgram {
18
- private commands: Map<string, CliCommand> = new Map();
20
+ private commands: Map<string, CliCommand> = new Map()
19
21
 
20
22
  registerCommand(command: CliCommand): void {
21
- this.commands.set(command.name, command);
23
+ this.commands.set(command.name, command)
22
24
  }
23
25
 
24
26
  async dispatch(args: string[]): Promise<number> {
25
- const subcommand = args[0];
27
+ const subcommand = args[0]
26
28
 
27
29
  if (!subcommand || subcommand === "--help" || subcommand === "-h") {
28
- cliOutput.log(HELP_TEXT);
29
- return 0;
30
+ cliOutput.log(HELP_TEXT)
31
+ return 0
30
32
  }
31
33
 
32
- const command = this.commands.get(subcommand);
34
+ const command = this.commands.get(subcommand)
33
35
  if (!command) {
34
- cliOutput.error(`Unknown command '${subcommand}'. Run 'argus' for help.`);
35
- return 1;
36
+ cliOutput.error(`Unknown command '${subcommand}'. Run 'argus' for help.`)
37
+ return 1
36
38
  }
37
39
 
38
- return command.execute(args.slice(1));
40
+ return command.execute(args.slice(1))
39
41
  }
40
42
  }
41
43
 
42
44
  export function createCliProgram(): CliProgram {
43
- const program = new CliProgram();
44
- program.registerCommand(doctorCommand);
45
- program.registerCommand(initCommand);
46
- program.registerCommand(installCommand);
47
- program.registerCommand(lintSkillsCommand);
48
- return program;
45
+ const program = new CliProgram()
46
+ program.registerCommand(doctorCommand)
47
+ program.registerCommand(initCommand)
48
+ program.registerCommand(installCommand)
49
+ program.registerCommand(lintSkillsCommand)
50
+ program.registerCommand(checkSkillsCommand)
51
+ return program
49
52
  }
@@ -0,0 +1,135 @@
1
+ import { readdirSync, readFileSync } from "node:fs"
2
+ import { join } from "node:path"
3
+ import { loadArgusConfig } from "../../config/loader"
4
+ import { createLogger } from "../../shared/logger"
5
+ import {
6
+ DEFAULT_GATE_CONFIG,
7
+ formatReportJson,
8
+ formatReportText,
9
+ type GateConfig,
10
+ generateReport,
11
+ type SkillReport,
12
+ } from "../../skills/analysis/gates"
13
+ import { normalizeSkill, type SkillDoc } from "../../skills/analysis/normalize"
14
+ import { buildTfidfCorpus, computeAllPairs } from "../../skills/analysis/similarity"
15
+ import { resolveSkillRoots } from "../../skills/argus-skill-resolver"
16
+ import { cliOutput } from "../cli-output"
17
+ import type { CliCommand } from "../types"
18
+
19
+ const logger = createLogger()
20
+
21
+ function findSkillFiles(dir: string, maxDepth = 8): string[] {
22
+ const files: string[] = []
23
+ const stack: Array<{ path: string; depth: number }> = [{ path: dir, depth: 0 }]
24
+
25
+ while (stack.length > 0) {
26
+ const current = stack.pop()
27
+ if (!current || current.depth > maxDepth) continue
28
+
29
+ try {
30
+ const entries = readdirSync(current.path, { withFileTypes: true })
31
+ for (const entry of entries) {
32
+ const fullPath = join(current.path, entry.name)
33
+ if (entry.isDirectory()) {
34
+ stack.push({ path: fullPath, depth: current.depth + 1 })
35
+ } else if (entry.isFile() && entry.name.toUpperCase() === "SKILL.MD") {
36
+ files.push(fullPath)
37
+ }
38
+ }
39
+ } catch {}
40
+ }
41
+
42
+ return files
43
+ }
44
+
45
+ function parseFormatArg(args: string[]): "text" | "json" {
46
+ const formatIdx = args.indexOf("--format")
47
+ if (formatIdx !== -1 && args[formatIdx + 1] === "json") {
48
+ return "json"
49
+ }
50
+ return "text"
51
+ }
52
+
53
+ function parseThresholdArg(args: string[], flag: string, fallback: number): number {
54
+ const idx = args.indexOf(flag)
55
+ if (idx === -1) return fallback
56
+ const raw = args[idx + 1]
57
+ if (!raw) return fallback
58
+ const parsed = Number.parseFloat(raw)
59
+ return Number.isFinite(parsed) && parsed >= 0 && parsed <= 1 ? parsed : fallback
60
+ }
61
+
62
+ export function loadAndNormalizeSkills(cwd: string): SkillDoc[] {
63
+ let config: ReturnType<typeof loadArgusConfig> | undefined
64
+ try {
65
+ config = loadArgusConfig(cwd)
66
+ } catch {
67
+ logger.debug("Config load failed, using defaults")
68
+ }
69
+
70
+ const roots = resolveSkillRoots(cwd, config)
71
+ const docs: SkillDoc[] = []
72
+
73
+ for (const root of roots) {
74
+ const files = findSkillFiles(root.path)
75
+ for (const file of files) {
76
+ try {
77
+ const content = readFileSync(file, "utf8")
78
+ const doc = normalizeSkill(content)
79
+ if (doc) {
80
+ docs.push(doc)
81
+ }
82
+ } catch {
83
+ logger.debug("Skipping unreadable skill file")
84
+ }
85
+ }
86
+ }
87
+
88
+ return docs
89
+ }
90
+
91
+ export function runAnalysis(docs: SkillDoc[], config: GateConfig): SkillReport {
92
+ const corpus = buildTfidfCorpus(docs)
93
+ const pairs = computeAllPairs(docs, corpus)
94
+ return generateReport(docs, pairs, config)
95
+ }
96
+
97
+ export const checkSkillsCommand: CliCommand = {
98
+ name: "check-skills",
99
+ description:
100
+ "Analyze SKILL.md files for duplicates, near-duplicates, and detection rule conflicts",
101
+ async execute(args: string[]): Promise<number> {
102
+ const cwd = process.cwd()
103
+ const format = parseFormatArg(args)
104
+
105
+ const gateConfig: GateConfig = {
106
+ blockThreshold: parseThresholdArg(
107
+ args,
108
+ "--block-threshold",
109
+ DEFAULT_GATE_CONFIG.blockThreshold,
110
+ ),
111
+ warnThreshold: parseThresholdArg(args, "--warn-threshold", DEFAULT_GATE_CONFIG.warnThreshold),
112
+ infoThreshold: parseThresholdArg(args, "--info-threshold", DEFAULT_GATE_CONFIG.infoThreshold),
113
+ blockExactRegexConflict: !args.includes("--no-regex-conflict"),
114
+ }
115
+
116
+ const docs = loadAndNormalizeSkills(cwd)
117
+
118
+ if (docs.length === 0) {
119
+ cliOutput.log("No SKILL.md files found.")
120
+ return 0
121
+ }
122
+
123
+ cliOutput.log(`Analyzing ${docs.length} skills...`)
124
+
125
+ const report = runAnalysis(docs, gateConfig)
126
+
127
+ if (format === "json") {
128
+ cliOutput.log(formatReportJson(report))
129
+ } else {
130
+ cliOutput.log(formatReportText(report))
131
+ }
132
+
133
+ return report.summary.block > 0 ? 1 : 0
134
+ },
135
+ }
@@ -1,20 +1,22 @@
1
- import { execSync } from "node:child_process"
2
1
  import { existsSync, readdirSync, readFileSync } from "node:fs"
3
2
  import { basename, dirname, extname, join } from "node:path"
4
- import type { CliCommand } from "../types"
5
- import type { ArgusConfig } from "../../config/types"
6
3
  import { loadArgusConfig } from "../../config/loader"
4
+ import type { ArgusConfig } from "../../config/types"
5
+ import { createLogger } from "../../shared/logger"
7
6
  import {
8
7
  getRequiredAuditSkills,
9
8
  normalizeSkillName,
9
+ type ResolvedSkill,
10
10
  resolveArgusSkills,
11
11
  resolveSkillRoots,
12
- type ResolvedSkill,
13
12
  } from "../../skills/argus-skill-resolver"
14
13
  import { parseFrontmatter, validateSkillFrontmatter } from "../../skills/skill-schema"
15
14
  import { detectViaIr } from "../../tools/slither-tool"
16
15
  import { checkSoloditHealth } from "../../utils/solodit-health"
17
16
  import { cliOutput } from "../cli-output"
17
+ import type { CliCommand } from "../types"
18
+
19
+ const logger = createLogger()
18
20
 
19
21
  const GREEN = "\x1b[32m"
20
22
  const RED = "\x1b[31m"
@@ -23,13 +25,15 @@ const RESET = "\x1b[0m"
23
25
 
24
26
  function checkBinary(name: string): { found: boolean; version: string | null } {
25
27
  try {
26
- const version = execSync(`${name} --version`, {
28
+ const result = Bun.spawnSync([name, "--version"], {
29
+ stdout: "pipe",
30
+ stderr: "pipe",
27
31
  timeout: 5000,
28
- stdio: ["pipe", "pipe", "pipe"],
29
32
  })
30
- .toString()
31
- .trim()
32
- .split("\n")[0] ?? null
33
+ if (result.exitCode !== 0) {
34
+ return { found: false, version: null }
35
+ }
36
+ const version = new TextDecoder().decode(result.stdout).trim().split("\n")[0] ?? null
33
37
  return { found: true, version }
34
38
  } catch {
35
39
  return { found: false, version: null }
@@ -70,7 +74,8 @@ export function findDuplicateSkills(
70
74
  const nameToSources = new Map<string, Set<string>>()
71
75
  for (const { name, source } of entries) {
72
76
  if (!nameToSources.has(name)) nameToSources.set(name, new Set())
73
- nameToSources.get(name)!.add(source)
77
+ const sources = nameToSources.get(name)
78
+ if (sources) sources.add(source)
74
79
  }
75
80
  return Array.from(nameToSources)
76
81
  .filter(([, sources]) => sources.size > 1)
@@ -112,9 +117,7 @@ export function buildSkillHealthReport(
112
117
  }
113
118
 
114
119
  const duplicates = duplicateEntries ? findDuplicateSkills(duplicateEntries) : []
115
- const missingCategories = REQUIRED_CATEGORIES.filter(
116
- (cat) => (categoryBreakdown[cat] ?? 0) === 0,
117
- )
120
+ const missingCategories = REQUIRED_CATEGORIES.filter((cat) => (categoryBreakdown[cat] ?? 0) === 0)
118
121
 
119
122
  return {
120
123
  categoryBreakdown,
@@ -146,6 +149,7 @@ function scanMarkdownFiles(dir: string, maxDepth = 8): string[] {
146
149
  }
147
150
  }
148
151
  } catch {
152
+ logger.debug("Failed to read directory during skill scan")
149
153
  }
150
154
  }
151
155
  return files
@@ -175,6 +179,7 @@ function collectAllSkillNames(
175
179
  const name = normalizeSkillName(rawName)
176
180
  if (name) entries.push({ name, source: root.source })
177
181
  } catch {
182
+ logger.debug("Failed to parse skill file frontmatter")
178
183
  }
179
184
  }
180
185
  }
@@ -184,7 +189,7 @@ function collectAllSkillNames(
184
189
  export const doctorCommand: CliCommand = {
185
190
  name: "doctor",
186
191
  description: "Check tool dependencies and configuration",
187
- async execute(args: string[]): Promise<number> {
192
+ async execute(_args: string[]): Promise<number> {
188
193
  const cwd = process.cwd()
189
194
  let hasFailure = false
190
195
 
@@ -202,7 +207,9 @@ export const doctorCommand: CliCommand = {
202
207
  if (forge.found) {
203
208
  cliOutput.log(`${GREEN}✓${RESET} Forge: installed (${forge.version})`)
204
209
  } else {
205
- cliOutput.log(`${RED}✗${RESET} Forge: not found — curl -L https://foundry.paradigm.xyz | bash`)
210
+ cliOutput.log(
211
+ `${RED}✗${RESET} Forge: not found — curl -L https://foundry.paradigm.xyz | bash`,
212
+ )
206
213
  hasFailure = true
207
214
  }
208
215
 
@@ -210,7 +217,9 @@ export const doctorCommand: CliCommand = {
210
217
  if (solcSelect.found) {
211
218
  cliOutput.log(`${GREEN}✓${RESET} solc-select: installed (${solcSelect.version})`)
212
219
  } else {
213
- cliOutput.log(`${YELLOW}⚠${RESET} solc-select: not found — pipx install solc-select (needed for via_ir flatten fallback)`)
220
+ cliOutput.log(
221
+ `${YELLOW}⚠${RESET} solc-select: not found — pipx install solc-select (needed for via_ir flatten fallback)`,
222
+ )
214
223
  }
215
224
 
216
225
  const projectType = checkSolidityProject(cwd)
@@ -221,9 +230,13 @@ export const doctorCommand: CliCommand = {
221
230
  }
222
231
 
223
232
  if (projectType === "foundry" && detectViaIr(cwd)) {
224
- cliOutput.log(`${YELLOW}⚠${RESET} via_ir: enabled in foundry.toml — Slither will use flatten fallback`)
233
+ cliOutput.log(
234
+ `${YELLOW}⚠${RESET} via_ir: enabled in foundry.toml — Slither will use flatten fallback`,
235
+ )
225
236
  if (!forge.found) {
226
- cliOutput.log(`${RED}✗${RESET} forge is required for via_ir flatten fallback but is missing`)
237
+ cliOutput.log(
238
+ `${RED}✗${RESET} forge is required for via_ir flatten fallback but is missing`,
239
+ )
227
240
  hasFailure = true
228
241
  }
229
242
  if (!solcSelect.found) {
@@ -241,9 +254,13 @@ export const doctorCommand: CliCommand = {
241
254
  const missingSkills = requiredSkills.filter((skillName) => !resolvedSkills.has(skillName))
242
255
 
243
256
  if (missingSkills.length === 0) {
244
- cliOutput.log(`${GREEN}✓${RESET} Skills: required audit skills resolvable (${requiredSkills.join(", ")})`)
257
+ cliOutput.log(
258
+ `${GREEN}✓${RESET} Skills: required audit skills resolvable (${requiredSkills.join(", ")})`,
259
+ )
245
260
  } else {
246
- cliOutput.log(`${RED}✗${RESET} Skills: missing required skills (${missingSkills.join(", ")})`)
261
+ cliOutput.log(
262
+ `${RED}✗${RESET} Skills: missing required skills (${missingSkills.join(", ")})`,
263
+ )
247
264
  hasFailure = true
248
265
  }
249
266
  } catch {
@@ -254,15 +271,21 @@ export const doctorCommand: CliCommand = {
254
271
  const missingSkills = requiredSkills.filter((skillName) => !resolvedSkills.has(skillName))
255
272
 
256
273
  if (missingSkills.length === 0) {
257
- cliOutput.log(`${GREEN}✓${RESET} Skills: required audit skills resolvable (${requiredSkills.join(", ")})`)
274
+ cliOutput.log(
275
+ `${GREEN}✓${RESET} Skills: required audit skills resolvable (${requiredSkills.join(", ")})`,
276
+ )
258
277
  } else {
259
- cliOutput.log(`${RED}✗${RESET} Skills: missing required skills (${missingSkills.join(", ")})`)
278
+ cliOutput.log(
279
+ `${RED}✗${RESET} Skills: missing required skills (${missingSkills.join(", ")})`,
280
+ )
260
281
  hasFailure = true
261
282
  }
262
283
  }
263
284
 
264
285
  try {
265
- const response = await fetch("https://api.scvd.dev/stats", { signal: AbortSignal.timeout(5000) })
286
+ const response = await fetch("https://api.scvd.dev/stats", {
287
+ signal: AbortSignal.timeout(5000),
288
+ })
266
289
  if (response.ok) {
267
290
  cliOutput.log(`${GREEN}✓${RESET} SCVD API: reachable`)
268
291
  } else {
@@ -296,9 +319,7 @@ export const doctorCommand: CliCommand = {
296
319
  const allEntries = collectAllSkillNames(cwd, config)
297
320
  const report = buildSkillHealthReport(healthSkills, allEntries)
298
321
 
299
- const catParts = ALL_CATEGORIES.map(
300
- (cat) => `${cat}: ${report.categoryBreakdown[cat] ?? 0}`,
301
- )
322
+ const catParts = ALL_CATEGORIES.map((cat) => `${cat}: ${report.categoryBreakdown[cat] ?? 0}`)
302
323
  cliOutput.log(`${GREEN}✓${RESET} Categories: ${catParts.join(", ")}`)
303
324
 
304
325
  const tierParts = Object.entries(report.trustTierBreakdown).map(
@@ -334,6 +355,7 @@ export const doctorCommand: CliCommand = {
334
355
  }
335
356
  } catch {
336
357
  cliOutput.log(`${RED}✗${RESET} Could not analyze skill health`)
358
+ hasFailure = true
337
359
  }
338
360
 
339
361
  return hasFailure ? 1 : 0