crucible-mcp 0.4.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
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.
- crucible/cli.py +532 -12
- crucible/enforcement/budget.py +179 -0
- crucible/enforcement/bundled/error-handling.yaml +84 -0
- crucible/enforcement/bundled/security.yaml +123 -0
- crucible/enforcement/bundled/smart-contract.yaml +110 -0
- crucible/enforcement/compliance.py +486 -0
- crucible/enforcement/models.py +71 -1
- crucible/hooks/claudecode.py +388 -0
- crucible/hooks/precommit.py +117 -25
- crucible/knowledge/loader.py +186 -0
- crucible/knowledge/principles/API_DESIGN.md +176 -0
- crucible/knowledge/principles/COMMITS.md +127 -0
- crucible/knowledge/principles/DATABASE.md +138 -0
- crucible/knowledge/principles/DOCUMENTATION.md +201 -0
- crucible/knowledge/principles/ERROR_HANDLING.md +157 -0
- crucible/knowledge/principles/FP.md +162 -0
- crucible/knowledge/principles/GITIGNORE.md +218 -0
- crucible/knowledge/principles/OBSERVABILITY.md +147 -0
- crucible/knowledge/principles/PRECOMMIT.md +201 -0
- crucible/knowledge/principles/SECURITY.md +136 -0
- crucible/knowledge/principles/SMART_CONTRACT.md +153 -0
- crucible/knowledge/principles/SYSTEM_DESIGN.md +153 -0
- crucible/knowledge/principles/TESTING.md +129 -0
- crucible/knowledge/principles/TYPE_SAFETY.md +170 -0
- crucible/review/core.py +78 -7
- crucible/server.py +81 -14
- crucible/skills/accessibility-engineer/SKILL.md +71 -0
- crucible/skills/backend-engineer/SKILL.md +69 -0
- crucible/skills/customer-success/SKILL.md +69 -0
- crucible/skills/data-engineer/SKILL.md +70 -0
- crucible/skills/devops-engineer/SKILL.md +69 -0
- crucible/skills/fde-engineer/SKILL.md +69 -0
- crucible/skills/formal-verification/SKILL.md +86 -0
- crucible/skills/gas-optimizer/SKILL.md +89 -0
- crucible/skills/incident-responder/SKILL.md +91 -0
- crucible/skills/mev-researcher/SKILL.md +87 -0
- crucible/skills/mobile-engineer/SKILL.md +70 -0
- crucible/skills/performance-engineer/SKILL.md +68 -0
- crucible/skills/product-engineer/SKILL.md +68 -0
- crucible/skills/protocol-architect/SKILL.md +83 -0
- crucible/skills/security-engineer/SKILL.md +63 -0
- crucible/skills/tech-lead/SKILL.md +92 -0
- crucible/skills/uiux-engineer/SKILL.md +70 -0
- crucible/skills/web3-engineer/SKILL.md +79 -0
- crucible/tools/git.py +17 -4
- crucible_mcp-1.0.0.dist-info/METADATA +198 -0
- crucible_mcp-1.0.0.dist-info/RECORD +66 -0
- crucible_mcp-0.4.0.dist-info/METADATA +0 -160
- crucible_mcp-0.4.0.dist-info/RECORD +0 -28
- {crucible_mcp-0.4.0.dist-info → crucible_mcp-1.0.0.dist-info}/WHEEL +0 -0
- {crucible_mcp-0.4.0.dist-info → crucible_mcp-1.0.0.dist-info}/entry_points.txt +0 -0
- {crucible_mcp-0.4.0.dist-info → crucible_mcp-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
"""Token budget estimation and tracking for LLM compliance assertions."""
|
|
2
|
+
|
|
3
|
+
from crucible.enforcement.models import (
|
|
4
|
+
Assertion,
|
|
5
|
+
AssertionType,
|
|
6
|
+
BudgetState,
|
|
7
|
+
ComplianceConfig,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
# Average tokens per character (rough estimate for code)
|
|
11
|
+
TOKENS_PER_CHAR = 0.25
|
|
12
|
+
|
|
13
|
+
# Base overhead for each LLM call (system prompt, response format, etc.)
|
|
14
|
+
BASE_OVERHEAD_TOKENS = 200
|
|
15
|
+
|
|
16
|
+
# Minimum tokens for compliance prompt
|
|
17
|
+
MIN_COMPLIANCE_TOKENS = 50
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def estimate_assertion_tokens(assertion: Assertion, content_length: int) -> int:
|
|
21
|
+
"""Estimate tokens needed to run an LLM assertion.
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
assertion: The assertion to estimate
|
|
25
|
+
content_length: Length of content to analyze in characters
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
Estimated token count for input
|
|
29
|
+
"""
|
|
30
|
+
if assertion.type != AssertionType.LLM:
|
|
31
|
+
return 0
|
|
32
|
+
|
|
33
|
+
# Content tokens
|
|
34
|
+
content_tokens = int(content_length * TOKENS_PER_CHAR)
|
|
35
|
+
|
|
36
|
+
# Compliance prompt tokens
|
|
37
|
+
compliance_tokens = 0
|
|
38
|
+
if assertion.compliance:
|
|
39
|
+
compliance_tokens = max(
|
|
40
|
+
MIN_COMPLIANCE_TOKENS,
|
|
41
|
+
int(len(assertion.compliance) * TOKENS_PER_CHAR),
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return BASE_OVERHEAD_TOKENS + content_tokens + compliance_tokens
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def estimate_total_budget(
|
|
48
|
+
assertions: list[Assertion],
|
|
49
|
+
content_length: int,
|
|
50
|
+
) -> int:
|
|
51
|
+
"""Estimate total tokens needed to run all LLM assertions.
|
|
52
|
+
|
|
53
|
+
Args:
|
|
54
|
+
assertions: List of assertions (filters to LLM only)
|
|
55
|
+
content_length: Length of content to analyze
|
|
56
|
+
|
|
57
|
+
Returns:
|
|
58
|
+
Estimated total token count
|
|
59
|
+
"""
|
|
60
|
+
total = 0
|
|
61
|
+
for assertion in assertions:
|
|
62
|
+
if assertion.type == AssertionType.LLM:
|
|
63
|
+
total += estimate_assertion_tokens(assertion, content_length)
|
|
64
|
+
return total
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def sort_by_priority(assertions: list[Assertion]) -> list[Assertion]:
|
|
68
|
+
"""Sort assertions by priority (critical first).
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
assertions: Assertions to sort
|
|
72
|
+
|
|
73
|
+
Returns:
|
|
74
|
+
Sorted list (critical > high > medium > low)
|
|
75
|
+
"""
|
|
76
|
+
return sorted(assertions, key=lambda a: a.priority.rank)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def select_within_budget(
|
|
80
|
+
assertions: list[Assertion],
|
|
81
|
+
content_length: int,
|
|
82
|
+
budget: int,
|
|
83
|
+
) -> tuple[list[Assertion], list[Assertion]]:
|
|
84
|
+
"""Select assertions that fit within token budget.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
assertions: Assertions to select from (should be pre-sorted by priority)
|
|
88
|
+
content_length: Length of content to analyze
|
|
89
|
+
budget: Token budget (0 = unlimited)
|
|
90
|
+
|
|
91
|
+
Returns:
|
|
92
|
+
Tuple of (selected_assertions, skipped_assertions)
|
|
93
|
+
"""
|
|
94
|
+
if budget == 0:
|
|
95
|
+
# Unlimited budget
|
|
96
|
+
return list(assertions), []
|
|
97
|
+
|
|
98
|
+
selected: list[Assertion] = []
|
|
99
|
+
skipped: list[Assertion] = []
|
|
100
|
+
tokens_used = 0
|
|
101
|
+
|
|
102
|
+
for assertion in assertions:
|
|
103
|
+
if assertion.type != AssertionType.LLM:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
estimated = estimate_assertion_tokens(assertion, content_length)
|
|
107
|
+
|
|
108
|
+
if tokens_used + estimated <= budget:
|
|
109
|
+
selected.append(assertion)
|
|
110
|
+
tokens_used += estimated
|
|
111
|
+
else:
|
|
112
|
+
skipped.append(assertion)
|
|
113
|
+
|
|
114
|
+
return selected, skipped
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def filter_llm_assertions(assertions: list[Assertion]) -> list[Assertion]:
|
|
118
|
+
"""Filter to only LLM-type assertions.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
assertions: All assertions
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
Only assertions with type=llm
|
|
125
|
+
"""
|
|
126
|
+
return [a for a in assertions if a.type == AssertionType.LLM]
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def create_budget_state(config: ComplianceConfig) -> BudgetState:
|
|
130
|
+
"""Create initial budget state from config.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
config: Compliance configuration
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Fresh BudgetState
|
|
137
|
+
"""
|
|
138
|
+
return BudgetState(total_budget=config.token_budget)
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def prepare_llm_assertions(
|
|
142
|
+
assertions: list[Assertion],
|
|
143
|
+
content_length: int,
|
|
144
|
+
config: ComplianceConfig,
|
|
145
|
+
) -> tuple[list[Assertion], BudgetState]:
|
|
146
|
+
"""Prepare LLM assertions for execution.
|
|
147
|
+
|
|
148
|
+
Filters to LLM assertions, sorts by priority, and selects within budget.
|
|
149
|
+
|
|
150
|
+
Args:
|
|
151
|
+
assertions: All loaded assertions
|
|
152
|
+
content_length: Length of content to analyze
|
|
153
|
+
config: Compliance configuration
|
|
154
|
+
|
|
155
|
+
Returns:
|
|
156
|
+
Tuple of (assertions_to_run, budget_state)
|
|
157
|
+
"""
|
|
158
|
+
# Filter to LLM assertions only
|
|
159
|
+
llm_assertions = filter_llm_assertions(assertions)
|
|
160
|
+
|
|
161
|
+
if not llm_assertions:
|
|
162
|
+
return [], create_budget_state(config)
|
|
163
|
+
|
|
164
|
+
# Sort by priority
|
|
165
|
+
sorted_assertions = sort_by_priority(llm_assertions)
|
|
166
|
+
|
|
167
|
+
# Select within budget
|
|
168
|
+
selected, skipped = select_within_budget(
|
|
169
|
+
sorted_assertions,
|
|
170
|
+
content_length,
|
|
171
|
+
config.token_budget,
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
# Create budget state
|
|
175
|
+
state = create_budget_state(config)
|
|
176
|
+
for assertion in skipped:
|
|
177
|
+
state.skip(assertion.id)
|
|
178
|
+
|
|
179
|
+
return selected, state
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
version: "1.0"
|
|
2
|
+
name: error-handling
|
|
3
|
+
description: Error handling best practices to prevent silent failures
|
|
4
|
+
linked_knowledge: ERROR_HANDLING.md
|
|
5
|
+
|
|
6
|
+
assertions:
|
|
7
|
+
# High: Silent failures
|
|
8
|
+
- id: no-bare-except
|
|
9
|
+
type: pattern
|
|
10
|
+
pattern: "except\\s*:"
|
|
11
|
+
message: "Bare except catches everything including SystemExit/KeyboardInterrupt - catch specific exceptions"
|
|
12
|
+
severity: warning
|
|
13
|
+
priority: high
|
|
14
|
+
languages: [python]
|
|
15
|
+
|
|
16
|
+
- id: no-pass-in-except
|
|
17
|
+
type: pattern
|
|
18
|
+
pattern: "except[^:]*:\\s*\\n\\s*pass\\s*$"
|
|
19
|
+
message: "Empty except block silently swallows errors - log or re-raise"
|
|
20
|
+
severity: warning
|
|
21
|
+
priority: high
|
|
22
|
+
languages: [python]
|
|
23
|
+
|
|
24
|
+
- id: no-empty-catch
|
|
25
|
+
type: pattern
|
|
26
|
+
pattern: "catch\\s*\\([^)]*\\)\\s*\\{\\s*\\}"
|
|
27
|
+
message: "Empty catch block silently swallows errors - log or re-throw"
|
|
28
|
+
severity: warning
|
|
29
|
+
priority: high
|
|
30
|
+
languages: [javascript, typescript]
|
|
31
|
+
|
|
32
|
+
# Medium: Pokemon exception handling
|
|
33
|
+
- id: no-catch-exception
|
|
34
|
+
type: pattern
|
|
35
|
+
pattern: "except\\s+Exception\\s*:"
|
|
36
|
+
message: "Catching Exception is too broad - catch specific exception types"
|
|
37
|
+
severity: info
|
|
38
|
+
priority: medium
|
|
39
|
+
languages: [python]
|
|
40
|
+
|
|
41
|
+
- id: no-catch-base-exception
|
|
42
|
+
type: pattern
|
|
43
|
+
pattern: "except\\s+BaseException\\s*:"
|
|
44
|
+
message: "Catching BaseException includes SystemExit/KeyboardInterrupt - use Exception or specific types"
|
|
45
|
+
severity: warning
|
|
46
|
+
priority: medium
|
|
47
|
+
languages: [python]
|
|
48
|
+
|
|
49
|
+
# Medium: Error suppression
|
|
50
|
+
- id: no-ignore-errors-flag
|
|
51
|
+
type: pattern
|
|
52
|
+
pattern: "errors\\s*=\\s*[\"']ignore[\"']"
|
|
53
|
+
message: "errors='ignore' silently drops data - use 'replace' or handle explicitly"
|
|
54
|
+
severity: warning
|
|
55
|
+
priority: medium
|
|
56
|
+
languages: [python]
|
|
57
|
+
|
|
58
|
+
# Info: Best practices
|
|
59
|
+
- id: prefer-contextlib-suppress
|
|
60
|
+
type: pattern
|
|
61
|
+
pattern: "except\\s+\\w+\\s*:\\s*\\n\\s*pass\\s*$"
|
|
62
|
+
message: "Consider contextlib.suppress() for intentionally ignoring specific exceptions"
|
|
63
|
+
severity: info
|
|
64
|
+
priority: low
|
|
65
|
+
languages: [python]
|
|
66
|
+
|
|
67
|
+
# LLM assertion for complex error handling patterns
|
|
68
|
+
- id: error-handling-semantic
|
|
69
|
+
type: llm
|
|
70
|
+
compliance: |
|
|
71
|
+
Check that error handling follows best practices:
|
|
72
|
+
1. Errors are not silently swallowed (no empty except/catch blocks that do nothing)
|
|
73
|
+
2. Exceptions are logged or reported before being suppressed
|
|
74
|
+
3. Error messages provide enough context to debug issues
|
|
75
|
+
4. Errors at API/system boundaries are handled (not just propagated)
|
|
76
|
+
message: "Error handling may need improvement"
|
|
77
|
+
severity: warning
|
|
78
|
+
priority: medium
|
|
79
|
+
languages: [python, javascript, typescript]
|
|
80
|
+
applicability:
|
|
81
|
+
exclude:
|
|
82
|
+
- "**/test_*.py"
|
|
83
|
+
- "**/*_test.py"
|
|
84
|
+
- "**/tests/**"
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
version: "1.0"
|
|
2
|
+
name: security
|
|
3
|
+
description: Security assertions to catch common vulnerabilities
|
|
4
|
+
linked_knowledge: SECURITY.md
|
|
5
|
+
|
|
6
|
+
assertions:
|
|
7
|
+
# Critical: Code execution risks
|
|
8
|
+
- id: no-eval
|
|
9
|
+
type: pattern
|
|
10
|
+
pattern: "\\beval\\s*\\("
|
|
11
|
+
message: "eval() is dangerous - use ast.literal_eval() for data or safer alternatives"
|
|
12
|
+
severity: error
|
|
13
|
+
priority: critical
|
|
14
|
+
languages: [python, javascript, typescript]
|
|
15
|
+
|
|
16
|
+
- id: no-exec
|
|
17
|
+
type: pattern
|
|
18
|
+
pattern: "\\bexec\\s*\\("
|
|
19
|
+
message: "exec() allows arbitrary code execution - avoid or sandbox carefully"
|
|
20
|
+
severity: error
|
|
21
|
+
priority: critical
|
|
22
|
+
languages: [python]
|
|
23
|
+
|
|
24
|
+
# Critical: Shell injection
|
|
25
|
+
- id: no-shell-true
|
|
26
|
+
type: pattern
|
|
27
|
+
pattern: "shell\\s*=\\s*True"
|
|
28
|
+
message: "shell=True enables shell injection - use shell=False with argument list"
|
|
29
|
+
severity: error
|
|
30
|
+
priority: critical
|
|
31
|
+
languages: [python]
|
|
32
|
+
|
|
33
|
+
- id: no-os-system
|
|
34
|
+
type: pattern
|
|
35
|
+
pattern: "os\\.system\\s*\\("
|
|
36
|
+
message: "os.system() is vulnerable to shell injection - use subprocess with shell=False"
|
|
37
|
+
severity: error
|
|
38
|
+
priority: critical
|
|
39
|
+
languages: [python]
|
|
40
|
+
|
|
41
|
+
# Critical: Deserialization
|
|
42
|
+
- id: no-pickle-load
|
|
43
|
+
type: pattern
|
|
44
|
+
pattern: "pickle\\.load|pickle\\.loads|cPickle\\.load"
|
|
45
|
+
message: "pickle can execute arbitrary code - use json or msgpack for untrusted data"
|
|
46
|
+
severity: error
|
|
47
|
+
priority: critical
|
|
48
|
+
languages: [python]
|
|
49
|
+
|
|
50
|
+
- id: no-yaml-unsafe-load
|
|
51
|
+
type: pattern
|
|
52
|
+
pattern: "yaml\\.load\\s*\\([^)]*\\)(?!.*Loader)"
|
|
53
|
+
message: "yaml.load() without Loader is unsafe - use yaml.safe_load()"
|
|
54
|
+
severity: error
|
|
55
|
+
priority: critical
|
|
56
|
+
languages: [python]
|
|
57
|
+
|
|
58
|
+
# High: Hardcoded secrets
|
|
59
|
+
- id: no-hardcoded-password
|
|
60
|
+
type: pattern
|
|
61
|
+
pattern: "(?i)(password|passwd|pwd)\\s*=\\s*[\"'][^\"']{4,}[\"']"
|
|
62
|
+
message: "Possible hardcoded password - use environment variables or secrets manager"
|
|
63
|
+
severity: error
|
|
64
|
+
priority: high
|
|
65
|
+
applicability:
|
|
66
|
+
exclude:
|
|
67
|
+
- "**/test_*.py"
|
|
68
|
+
- "**/*_test.py"
|
|
69
|
+
- "**/tests/**"
|
|
70
|
+
- "**/*.md"
|
|
71
|
+
|
|
72
|
+
- id: no-hardcoded-api-key
|
|
73
|
+
type: pattern
|
|
74
|
+
pattern: "(?i)(api[_-]?key|apikey|secret[_-]?key)\\s*=\\s*[\"'][a-zA-Z0-9]{16,}[\"']"
|
|
75
|
+
message: "Possible hardcoded API key - use environment variables or secrets manager"
|
|
76
|
+
severity: error
|
|
77
|
+
priority: high
|
|
78
|
+
applicability:
|
|
79
|
+
exclude:
|
|
80
|
+
- "**/test_*.py"
|
|
81
|
+
- "**/*_test.py"
|
|
82
|
+
- "**/tests/**"
|
|
83
|
+
- "**/*.md"
|
|
84
|
+
|
|
85
|
+
# High: SQL injection
|
|
86
|
+
- id: no-string-sql
|
|
87
|
+
type: pattern
|
|
88
|
+
pattern: "execute\\s*\\(\\s*[\"']\\s*SELECT|execute\\s*\\(\\s*f[\"']|execute\\s*\\([^)]*%\\s*\\("
|
|
89
|
+
message: "Possible SQL injection - use parameterized queries"
|
|
90
|
+
severity: error
|
|
91
|
+
priority: high
|
|
92
|
+
languages: [python]
|
|
93
|
+
|
|
94
|
+
# Medium: Weak crypto
|
|
95
|
+
- id: no-md5-security
|
|
96
|
+
type: pattern
|
|
97
|
+
pattern: "hashlib\\.md5|MD5\\s*\\(|md5\\.new"
|
|
98
|
+
message: "MD5 is cryptographically broken - use SHA-256 or better for security"
|
|
99
|
+
severity: warning
|
|
100
|
+
priority: medium
|
|
101
|
+
languages: [python]
|
|
102
|
+
|
|
103
|
+
- id: no-sha1-security
|
|
104
|
+
type: pattern
|
|
105
|
+
pattern: "hashlib\\.sha1|SHA1\\s*\\(|sha1\\.new"
|
|
106
|
+
message: "SHA-1 is deprecated for security - use SHA-256 or better"
|
|
107
|
+
severity: warning
|
|
108
|
+
priority: medium
|
|
109
|
+
languages: [python]
|
|
110
|
+
|
|
111
|
+
# Medium: Insecure random
|
|
112
|
+
- id: no-random-for-security
|
|
113
|
+
type: pattern
|
|
114
|
+
pattern: "\\brandom\\.(choice|randint|random|uniform)\\s*\\("
|
|
115
|
+
message: "random module is not cryptographically secure - use secrets module"
|
|
116
|
+
severity: warning
|
|
117
|
+
priority: medium
|
|
118
|
+
languages: [python]
|
|
119
|
+
applicability:
|
|
120
|
+
exclude:
|
|
121
|
+
- "**/test_*.py"
|
|
122
|
+
- "**/*_test.py"
|
|
123
|
+
- "**/tests/**"
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
version: "1.0"
|
|
2
|
+
name: smart-contract
|
|
3
|
+
description: Smart contract security patterns for Solidity/Vyper
|
|
4
|
+
linked_knowledge: SMART_CONTRACT.md
|
|
5
|
+
|
|
6
|
+
assertions:
|
|
7
|
+
# Critical: Reentrancy
|
|
8
|
+
- id: state-after-external-call
|
|
9
|
+
type: pattern
|
|
10
|
+
pattern: "\\.call\\{[^}]*\\}\\([^)]*\\)[^;]*;[^}]*\\b(balances|_balances|userBalances|amounts)\\s*\\["
|
|
11
|
+
message: "State change after external call - potential reentrancy vulnerability (CEI violation)"
|
|
12
|
+
severity: error
|
|
13
|
+
priority: critical
|
|
14
|
+
languages: [solidity]
|
|
15
|
+
|
|
16
|
+
- id: transfer-without-reentrancy-guard
|
|
17
|
+
type: llm
|
|
18
|
+
compliance: |
|
|
19
|
+
Check for reentrancy vulnerabilities:
|
|
20
|
+
1. External calls (.call, .transfer, .send) should come AFTER state changes (CEI pattern)
|
|
21
|
+
2. Functions making external calls should have reentrancy guards (nonReentrant modifier)
|
|
22
|
+
3. State variables should be updated before external calls, not after
|
|
23
|
+
4. Check-effects-interactions pattern: checks first, then effects (state changes), then interactions (external calls)
|
|
24
|
+
message: "Potential reentrancy vulnerability - apply CEI pattern and/or nonReentrant modifier"
|
|
25
|
+
severity: error
|
|
26
|
+
priority: critical
|
|
27
|
+
model: opus
|
|
28
|
+
languages: [solidity]
|
|
29
|
+
|
|
30
|
+
# Critical: Access control
|
|
31
|
+
- id: missing-access-control
|
|
32
|
+
type: pattern
|
|
33
|
+
pattern: "function\\s+\\w+\\s*\\([^)]*\\)\\s+(external|public)(?![^{]*onlyOwner|[^{]*onlyRole|[^{]*require\\s*\\(\\s*msg\\.sender)"
|
|
34
|
+
message: "Public/external function may need access control"
|
|
35
|
+
severity: warning
|
|
36
|
+
priority: high
|
|
37
|
+
languages: [solidity]
|
|
38
|
+
|
|
39
|
+
- id: tx-origin-auth
|
|
40
|
+
type: pattern
|
|
41
|
+
pattern: "require\\s*\\(\\s*tx\\.origin\\s*==|tx\\.origin\\s*==\\s*msg\\.sender"
|
|
42
|
+
message: "tx.origin for auth is vulnerable to phishing - use msg.sender"
|
|
43
|
+
severity: error
|
|
44
|
+
priority: critical
|
|
45
|
+
languages: [solidity]
|
|
46
|
+
|
|
47
|
+
# High: Integer overflow (pre-0.8.0)
|
|
48
|
+
- id: unchecked-arithmetic
|
|
49
|
+
type: pattern
|
|
50
|
+
pattern: "unchecked\\s*\\{[^}]*[+\\-*/]"
|
|
51
|
+
message: "Unchecked arithmetic - ensure overflow/underflow is intentional and safe"
|
|
52
|
+
severity: warning
|
|
53
|
+
priority: high
|
|
54
|
+
languages: [solidity]
|
|
55
|
+
|
|
56
|
+
# High: Front-running
|
|
57
|
+
- id: block-timestamp-comparison
|
|
58
|
+
type: pattern
|
|
59
|
+
pattern: "block\\.timestamp\\s*[<>=]|[<>=]\\s*block\\.timestamp"
|
|
60
|
+
message: "block.timestamp can be manipulated by miners (~15 sec) - avoid for critical logic"
|
|
61
|
+
severity: warning
|
|
62
|
+
priority: medium
|
|
63
|
+
languages: [solidity]
|
|
64
|
+
|
|
65
|
+
# High: Denial of service
|
|
66
|
+
- id: unbounded-loop
|
|
67
|
+
type: pattern
|
|
68
|
+
pattern: "for\\s*\\([^;]*;[^;]*\\.length\\s*;"
|
|
69
|
+
message: "Loop bounded by dynamic array length - may cause out-of-gas DoS"
|
|
70
|
+
severity: warning
|
|
71
|
+
priority: high
|
|
72
|
+
languages: [solidity]
|
|
73
|
+
|
|
74
|
+
# Medium: Best practices
|
|
75
|
+
- id: missing-zero-address-check
|
|
76
|
+
type: pattern
|
|
77
|
+
pattern: "address\\s+\\w+\\s*=[^;]*;(?![^}]*require\\s*\\([^)]*!=\\s*address\\(0\\))"
|
|
78
|
+
message: "Consider checking for zero address on address parameters"
|
|
79
|
+
severity: info
|
|
80
|
+
priority: medium
|
|
81
|
+
languages: [solidity]
|
|
82
|
+
|
|
83
|
+
- id: hardcoded-gas
|
|
84
|
+
type: pattern
|
|
85
|
+
pattern: "\\.call\\{[^}]*gas:\\s*\\d+[^}]*\\}"
|
|
86
|
+
message: "Hardcoded gas values may break with EVM changes - use gasleft() or remove gas limit"
|
|
87
|
+
severity: warning
|
|
88
|
+
priority: medium
|
|
89
|
+
languages: [solidity]
|
|
90
|
+
|
|
91
|
+
# LLM: Complex security patterns
|
|
92
|
+
- id: smart-contract-security-review
|
|
93
|
+
type: llm
|
|
94
|
+
compliance: |
|
|
95
|
+
Perform a security review of this smart contract code:
|
|
96
|
+
1. Check for reentrancy vulnerabilities (external calls before state changes)
|
|
97
|
+
2. Verify access control on privileged functions
|
|
98
|
+
3. Check for integer overflow/underflow risks
|
|
99
|
+
4. Look for front-running vulnerabilities
|
|
100
|
+
5. Check for DoS vectors (unbounded loops, block gas limit issues)
|
|
101
|
+
6. Verify proper validation of external inputs
|
|
102
|
+
7. Check for unsafe delegatecall usage
|
|
103
|
+
8. Look for flash loan attack vectors
|
|
104
|
+
message: "Smart contract security issue detected"
|
|
105
|
+
severity: error
|
|
106
|
+
priority: critical
|
|
107
|
+
model: opus
|
|
108
|
+
languages: [solidity, vyper]
|
|
109
|
+
applicability:
|
|
110
|
+
glob: "**/*.sol"
|