athena-mcp 1.0.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/README.md +477 -0
- package/install.js +327 -0
- package/mcp/servers.json +100 -0
- package/mcp/tools/README.md +64 -0
- package/mcp/tools/__init__.py +1 -0
- package/mcp/tools/aderyn_runner.py +226 -0
- package/mcp/tools/eas_attest.py +404 -0
- package/mcp/tools/evidence_chain.py +363 -0
- package/mcp/tools/exploit_simulator.py +545 -0
- package/mcp/tools/fuzz_runner.py +440 -0
- package/mcp/tools/gev_analyzer.py +362 -0
- package/mcp/tools/halmos_runner.py +408 -0
- package/mcp/tools/incremental_auditor.py +441 -0
- package/mcp/tools/knowledge_base.py +378 -0
- package/mcp/tools/poc_generator.py +479 -0
- package/mcp/tools/protocol_scanner.py +456 -0
- package/mcp/tools/repair_validator.py +421 -0
- package/mcp/tools/slither_runner.py +221 -0
- package/package.json +52 -0
- package/requirements.txt +20 -0
- package/skills/glm-audit-skill/SKILL.md +73 -0
- package/skills/glm-audit-skill/references/audit-agents/access-control-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/asymmetry-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/boundary-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/economic-security-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/execution-trace-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/first-principles-agent.md +42 -0
- package/skills/glm-audit-skill/references/audit-agents/flow-gap-agent.md +38 -0
- package/skills/glm-audit-skill/references/audit-agents/invariant-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/math-precision-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/numerical-gap-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/periphery-agent.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/shared-rules.md +37 -0
- package/skills/glm-audit-skill/references/audit-agents/trust-gap-agent.md +39 -0
- package/skills/glm-audit-skill/references/judging.md +45 -0
- package/skills/glm-audit-skill/references/report-formatting.md +22 -0
- package/skills/glm-audit-skill/references/senior-auditor-sop.md +34 -0
|
@@ -0,0 +1,378 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
MCP Server: Knowledge Base
|
|
4
|
+
ChromaDB-backed RAG knowledge base for vulnerability patterns and audit insights.
|
|
5
|
+
"""
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
import asyncio
|
|
9
|
+
import logging
|
|
10
|
+
import os
|
|
11
|
+
from pathlib import Path
|
|
12
|
+
|
|
13
|
+
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s')
|
|
14
|
+
logger = logging.getLogger("knowledge_base")
|
|
15
|
+
|
|
16
|
+
TOOL_NAME = "knowledge_base"
|
|
17
|
+
TOOL_VERSION = "1.0.0"
|
|
18
|
+
|
|
19
|
+
# Default ChromaDB path
|
|
20
|
+
DEFAULT_CHROMA_PATH = os.environ.get(
|
|
21
|
+
"CHROMA_DB_PATH",
|
|
22
|
+
os.path.join(os.path.dirname(__file__), "..", "data", "chroma_db")
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
# Fallback knowledge base for when ChromaDB is unavailable
|
|
26
|
+
FALLBACK_KNOWLEDGE = [
|
|
27
|
+
{
|
|
28
|
+
"id": "reentrancy-1",
|
|
29
|
+
"category": "reentrancy",
|
|
30
|
+
"title": "Classic Reentrancy (DAO Attack Pattern)",
|
|
31
|
+
"content": "A reentrancy vulnerability occurs when a contract makes an external call before updating its state. An attacker can recursively call the vulnerable function to drain funds. Fix: Use ReentrancyGuard from OpenZeppelin or follow Checks-Effects-Interactions pattern.",
|
|
32
|
+
"severity": "high",
|
|
33
|
+
"references": ["SWC-107", "CWE-841"]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "reentrancy-2",
|
|
37
|
+
"category": "reentrancy",
|
|
38
|
+
"title": "Cross-function Reentrancy",
|
|
39
|
+
"content": "Cross-function reentrancy occurs when an attacker exploits shared state between two functions. Even if individual functions are protected, the shared state can be manipulated through reentrancy across function boundaries.",
|
|
40
|
+
"severity": "high",
|
|
41
|
+
"references": ["SWC-107"]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": "overflow-1",
|
|
45
|
+
"category": "integer_overflow",
|
|
46
|
+
"title": "Integer Overflow/Underflow",
|
|
47
|
+
"content": "In Solidity <0.8.0, arithmetic operations silently overflow/underflow. An attacker can exploit this to manipulate balances, token supplies, or other numeric values. Fix: Use SafeMath library or Solidity >=0.8.0 with built-in overflow checks.",
|
|
48
|
+
"severity": "high",
|
|
49
|
+
"references": ["SWC-101", "CWE-190"]
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": "access-1",
|
|
53
|
+
"category": "access_control",
|
|
54
|
+
"title": "Missing Access Control",
|
|
55
|
+
"content": "Critical functions without proper access control can be called by any address. This includes owner-only operations like withdrawing funds, upgrading contracts, or modifying parameters. Fix: Implement onlyOwner modifier or role-based access control.",
|
|
56
|
+
"severity": "high",
|
|
57
|
+
"references": ["SWC-105", "CWE-284"]
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": "access-2",
|
|
61
|
+
"category": "access_control",
|
|
62
|
+
"title": "Unprotected Self-destruct",
|
|
63
|
+
"content": "The selfdestruct opcode can permanently destroy a contract. If not properly protected, an attacker can destroy the contract and send its balance to an arbitrary address. Fix: Remove selfdestruct or add strict access control.",
|
|
64
|
+
"severity": "high",
|
|
65
|
+
"references": ["SWC-106"]
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": "flashloan-1",
|
|
69
|
+
"category": "flash_loan",
|
|
70
|
+
"title": "Flash Loan Price Manipulation",
|
|
71
|
+
"content": "Flash loans allow borrowing large amounts without collateral within a single transaction. Attackers use them to manipulate oracle prices, drain liquidity pools, or exploit AMM pricing algorithms. Fix: Use TWAP oracles, implement slippage checks, and add reentrancy guards.",
|
|
72
|
+
"severity": "high",
|
|
73
|
+
"references": []
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": "frontend-1",
|
|
77
|
+
"category": "front_running",
|
|
78
|
+
"title": "Front-running / MEV",
|
|
79
|
+
"content": "Transactions in the mempool are visible before confirmation. Miners or bots can front-run profitable transactions, sandwich attacks, or extract MEV. Fix: Use commit-reveal schemes, private mempools, or MEV-protection services.",
|
|
80
|
+
"severity": "medium",
|
|
81
|
+
"references": ["SWC-114"]
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "oracle-1",
|
|
85
|
+
"category": "oracle_manipulation",
|
|
86
|
+
"title": "Oracle Manipulation",
|
|
87
|
+
"content": "Smart contracts relying on on-chain price oracles (like spot DEX prices) can be manipulated through large trades or flash loans. Fix: Use decentralized oracles (Chainlink), TWAP, or multiple oracle sources.",
|
|
88
|
+
"severity": "high",
|
|
89
|
+
"references": []
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": "denial-1",
|
|
93
|
+
"category": "denial_of_service",
|
|
94
|
+
"title": "Denial of Service via Gas Limit",
|
|
95
|
+
"content": "Functions that iterate over unbounded arrays can hit the block gas limit, making them unusable. This is especially critical for withdrawal or distribution functions. Fix: Use pull-over-push pattern, pagination, or limit array sizes.",
|
|
96
|
+
"severity": "medium",
|
|
97
|
+
"references": ["SWC-128"]
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": "logic-1",
|
|
101
|
+
"category": "business_logic",
|
|
102
|
+
"title": "Broken Business Logic",
|
|
103
|
+
"content": "Flaws in the contract's business logic can allow attackers to exploit the system in unintended ways. This includes incorrect calculations, missing validations, or flawed state transitions. Fix: Comprehensive testing, formal verification, and multiple audit rounds.",
|
|
104
|
+
"severity": "medium",
|
|
105
|
+
"references": []
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "tx-origin-1",
|
|
109
|
+
"category": "authentication",
|
|
110
|
+
"title": "tx.origin Authentication Bypass",
|
|
111
|
+
"content": "Using tx.origin for authentication is vulnerable to phishing attacks. A malicious contract can call the victim's contract while the victim initiates the transaction, bypassing msg.sender checks. Fix: Always use msg.sender for authentication.",
|
|
112
|
+
"severity": "medium",
|
|
113
|
+
"references": ["SWC-115"]
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "delegatecall-1",
|
|
117
|
+
"category": "delegatecall",
|
|
118
|
+
"title": "Delegatecall Vulnerabilities",
|
|
119
|
+
"content": "Delegatecall executes code in the context of the calling contract. If the target address can be controlled by an attacker, they can execute arbitrary code with the contract's privileges. Fix: Only delegatecall to trusted, immutable contracts.",
|
|
120
|
+
"severity": "high",
|
|
121
|
+
"references": ["SWC-112"]
|
|
122
|
+
}
|
|
123
|
+
]
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
class KnowledgeBase:
|
|
127
|
+
"""ChromaDB-backed knowledge base with fallback to static knowledge."""
|
|
128
|
+
|
|
129
|
+
def __init__(self, chroma_path: str = None):
|
|
130
|
+
self.chroma_path = chroma_path or DEFAULT_CHROMA_PATH
|
|
131
|
+
self.chroma_client = None
|
|
132
|
+
self.collection = None
|
|
133
|
+
self._initialized = False
|
|
134
|
+
|
|
135
|
+
async def initialize(self):
|
|
136
|
+
"""Initialize ChromaDB connection."""
|
|
137
|
+
if self._initialized:
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
try:
|
|
141
|
+
import chromadb
|
|
142
|
+
self.chroma_client = chromadb.PersistentClient(path=self.chroma_path)
|
|
143
|
+
self.collection = self.chroma_client.get_or_create_collection(
|
|
144
|
+
name="vulnerability_knowledge",
|
|
145
|
+
metadata={"hnsw:space": "cosine"}
|
|
146
|
+
)
|
|
147
|
+
self._initialized = True
|
|
148
|
+
logger.info(f"ChromaDB initialized at {self.chroma_path}")
|
|
149
|
+
|
|
150
|
+
# Seed with fallback knowledge if empty
|
|
151
|
+
if self.collection.count() == 0:
|
|
152
|
+
await self._seed_knowledge()
|
|
153
|
+
except ImportError:
|
|
154
|
+
logger.warning("chromadb not installed, using fallback knowledge base")
|
|
155
|
+
self._initialized = True
|
|
156
|
+
except Exception as e:
|
|
157
|
+
logger.warning(f"ChromaDB initialization failed: {e}, using fallback")
|
|
158
|
+
self._initialized = True
|
|
159
|
+
|
|
160
|
+
async def _seed_knowledge(self):
|
|
161
|
+
"""Seed ChromaDB with fallback knowledge."""
|
|
162
|
+
if not self.collection:
|
|
163
|
+
return
|
|
164
|
+
|
|
165
|
+
try:
|
|
166
|
+
documents = []
|
|
167
|
+
metadatas = []
|
|
168
|
+
ids = []
|
|
169
|
+
|
|
170
|
+
for item in FALLBACK_KNOWLEDGE:
|
|
171
|
+
documents.append(item["content"])
|
|
172
|
+
metadatas.append({
|
|
173
|
+
"category": item["category"],
|
|
174
|
+
"title": item["title"],
|
|
175
|
+
"severity": item["severity"],
|
|
176
|
+
"references": json.dumps(item.get("references", []))
|
|
177
|
+
})
|
|
178
|
+
ids.append(item["id"])
|
|
179
|
+
|
|
180
|
+
self.collection.add(
|
|
181
|
+
documents=documents,
|
|
182
|
+
metadatas=metadatas,
|
|
183
|
+
ids=ids
|
|
184
|
+
)
|
|
185
|
+
logger.info(f"Seeded {len(documents)} knowledge entries into ChromaDB")
|
|
186
|
+
except Exception as e:
|
|
187
|
+
logger.warning(f"Failed to seed ChromaDB: {e}")
|
|
188
|
+
|
|
189
|
+
async def query(self, query_text: str, top_k: int = 5) -> dict:
|
|
190
|
+
"""Query the knowledge base."""
|
|
191
|
+
await self.initialize()
|
|
192
|
+
|
|
193
|
+
# Try ChromaDB first
|
|
194
|
+
if self.collection:
|
|
195
|
+
try:
|
|
196
|
+
results = self.collection.query(
|
|
197
|
+
query_texts=[query_text],
|
|
198
|
+
n_results=min(top_k, self.collection.count() or 1),
|
|
199
|
+
include=["documents", "metadatas", "distances"]
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
entries = []
|
|
203
|
+
if results and results["documents"] and results["documents"][0]:
|
|
204
|
+
for i, doc in enumerate(results["documents"][0]):
|
|
205
|
+
metadata = results["metadatas"][0][i] if results["metadatas"] else {}
|
|
206
|
+
distance = results["distances"][0][i] if results["distances"] else None
|
|
207
|
+
entries.append({
|
|
208
|
+
"content": doc,
|
|
209
|
+
"category": metadata.get("category", ""),
|
|
210
|
+
"title": metadata.get("title", ""),
|
|
211
|
+
"severity": metadata.get("severity", ""),
|
|
212
|
+
"references": json.loads(metadata.get("references", "[]")),
|
|
213
|
+
"relevance_score": round(1 - distance, 4) if distance is not None else None
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
"success": True,
|
|
218
|
+
"source": "chromadb",
|
|
219
|
+
"query": query_text,
|
|
220
|
+
"results_count": len(entries),
|
|
221
|
+
"results": entries
|
|
222
|
+
}
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.warning(f"ChromaDB query failed: {e}")
|
|
225
|
+
|
|
226
|
+
# Fallback to keyword search
|
|
227
|
+
return self._fallback_search(query_text, top_k)
|
|
228
|
+
|
|
229
|
+
def _fallback_search(self, query_text: str, top_k: int) -> dict:
|
|
230
|
+
"""Keyword-based fallback search."""
|
|
231
|
+
query_lower = query_text.lower()
|
|
232
|
+
scored_results = []
|
|
233
|
+
|
|
234
|
+
for item in FALLBACK_KNOWLEDGE:
|
|
235
|
+
score = 0
|
|
236
|
+
# Score based on keyword matches
|
|
237
|
+
keywords = query_lower.split()
|
|
238
|
+
for keyword in keywords:
|
|
239
|
+
if keyword in item["title"].lower():
|
|
240
|
+
score += 3
|
|
241
|
+
if keyword in item["content"].lower():
|
|
242
|
+
score += 1
|
|
243
|
+
if keyword in item["category"].lower():
|
|
244
|
+
score += 2
|
|
245
|
+
|
|
246
|
+
if score > 0:
|
|
247
|
+
scored_results.append((score, item))
|
|
248
|
+
|
|
249
|
+
scored_results.sort(key=lambda x: x[0], reverse=True)
|
|
250
|
+
results = []
|
|
251
|
+
|
|
252
|
+
for score, item in scored_results[:top_k]:
|
|
253
|
+
results.append({
|
|
254
|
+
"content": item["content"],
|
|
255
|
+
"category": item["category"],
|
|
256
|
+
"title": item["title"],
|
|
257
|
+
"severity": item["severity"],
|
|
258
|
+
"references": item.get("references", []),
|
|
259
|
+
"relevance_score": min(score / 10.0, 1.0)
|
|
260
|
+
})
|
|
261
|
+
|
|
262
|
+
return {
|
|
263
|
+
"success": True,
|
|
264
|
+
"source": "fallback",
|
|
265
|
+
"query": query_text,
|
|
266
|
+
"results_count": len(results),
|
|
267
|
+
"results": results
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
# Global instance
|
|
272
|
+
kb = KnowledgeBase()
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
async def execute_tool(tool_name: str, arguments: dict) -> dict:
|
|
276
|
+
if tool_name == "query_knowledge":
|
|
277
|
+
query_text = arguments.get("query", "")
|
|
278
|
+
top_k = arguments.get("top_k", 5)
|
|
279
|
+
|
|
280
|
+
if not query_text:
|
|
281
|
+
return {"success": False, "error": "query is required"}
|
|
282
|
+
|
|
283
|
+
return await kb.query(query_text, top_k)
|
|
284
|
+
|
|
285
|
+
return {"success": False, "error": f"Unknown tool: {tool_name}"}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
def build_tool_definitions() -> list:
|
|
289
|
+
return [
|
|
290
|
+
{
|
|
291
|
+
"name": "query_knowledge",
|
|
292
|
+
"description": "Query the vulnerability knowledge base for relevant security patterns, attack vectors, and remediation advice. Uses ChromaDB vector search with keyword fallback.",
|
|
293
|
+
"inputSchema": {
|
|
294
|
+
"type": "object",
|
|
295
|
+
"properties": {
|
|
296
|
+
"query": {
|
|
297
|
+
"type": "string",
|
|
298
|
+
"description": "Natural language query about a vulnerability or security concern (e.g., 'reentrancy in ERC-20 token', 'flash loan attack patterns')"
|
|
299
|
+
},
|
|
300
|
+
"top_k": {
|
|
301
|
+
"type": "integer",
|
|
302
|
+
"description": "Maximum number of results to return (default: 5)",
|
|
303
|
+
"default": 5
|
|
304
|
+
}
|
|
305
|
+
},
|
|
306
|
+
"required": ["query"]
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
]
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
async def handle_request(request: dict) -> dict:
|
|
313
|
+
method = request.get("method")
|
|
314
|
+
params = request.get("params", {})
|
|
315
|
+
|
|
316
|
+
if method == "initialize":
|
|
317
|
+
return {
|
|
318
|
+
"protocolVersion": "2024-11-05",
|
|
319
|
+
"capabilities": {"tools": {}},
|
|
320
|
+
"serverInfo": {"name": TOOL_NAME, "version": TOOL_VERSION}
|
|
321
|
+
}
|
|
322
|
+
elif method == "tools/list":
|
|
323
|
+
return {"tools": build_tool_definitions()}
|
|
324
|
+
elif method == "tools/call":
|
|
325
|
+
tool_name = params.get("name")
|
|
326
|
+
arguments = params.get("arguments", {})
|
|
327
|
+
result = await execute_tool(tool_name, arguments)
|
|
328
|
+
return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
|
|
329
|
+
elif method == "ping":
|
|
330
|
+
return {}
|
|
331
|
+
return {"error": {"code": -32601, "message": f"Method not found: {method}"}}
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
async def main():
|
|
335
|
+
reader = asyncio.StreamReader()
|
|
336
|
+
protocol = asyncio.StreamReaderProtocol(reader)
|
|
337
|
+
loop = asyncio.get_event_loop()
|
|
338
|
+
await loop.connect_read_pipe(lambda: protocol, sys.stdin)
|
|
339
|
+
|
|
340
|
+
logger.info(f"{TOOL_NAME} MCP server started")
|
|
341
|
+
|
|
342
|
+
while True:
|
|
343
|
+
line = await reader.readline()
|
|
344
|
+
if not line:
|
|
345
|
+
break
|
|
346
|
+
|
|
347
|
+
line_str = line.decode("utf-8").strip()
|
|
348
|
+
if not line_str:
|
|
349
|
+
continue
|
|
350
|
+
|
|
351
|
+
try:
|
|
352
|
+
request = json.loads(line_str)
|
|
353
|
+
response = await handle_request(request)
|
|
354
|
+
response["jsonrpc"] = "2.0"
|
|
355
|
+
response["id"] = request.get("id")
|
|
356
|
+
sys.stdout.write(json.dumps(response) + "\n")
|
|
357
|
+
sys.stdout.flush()
|
|
358
|
+
except json.JSONDecodeError:
|
|
359
|
+
error_resp = {
|
|
360
|
+
"jsonrpc": "2.0",
|
|
361
|
+
"id": None,
|
|
362
|
+
"error": {"code": -32700, "message": "Parse error"}
|
|
363
|
+
}
|
|
364
|
+
sys.stdout.write(json.dumps(error_resp) + "\n")
|
|
365
|
+
sys.stdout.flush()
|
|
366
|
+
except Exception as e:
|
|
367
|
+
logger.exception("Error handling request")
|
|
368
|
+
error_resp = {
|
|
369
|
+
"jsonrpc": "2.0",
|
|
370
|
+
"id": request.get("id") if "request" in dir() else None,
|
|
371
|
+
"error": {"code": -32603, "message": f"Internal error: {str(e)}"}
|
|
372
|
+
}
|
|
373
|
+
sys.stdout.write(json.dumps(error_resp) + "\n")
|
|
374
|
+
sys.stdout.flush()
|
|
375
|
+
|
|
376
|
+
|
|
377
|
+
if __name__ == "__main__":
|
|
378
|
+
asyncio.run(main())
|