mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-12 14:14:56 +03:00
c47eed6a64
- Fix 25 shell=True subprocess calls with list-based commands - Fix 49 verify=False in defensive skills (env-var override) - Add timeout to 231 HTTP/subprocess/socket calls - Fix 6 SQL injection patterns with whitelist validation - Replace 8 __import__() with standard imports - Remove 701 unused imports across 442 files - Add authorized-testing disclaimers to all offensive skills - Complete 11 incomplete skill directories - Expand 10 stub SKILL.md files with full content - Fix 2 YAML parse errors in frontmatter - Fix 5 pre-existing syntax errors - Convert 22 hardcoded paths/ports to environment variables - Back up 21 redundant skill pairs to .bak - Fix 2 global declaration errors - 724/724 skills with full folder anatomy (SKILL.md + agent.py + api-reference.md + LICENSE) - 0 compile errors across all 724 agent.py files
169 lines
6.7 KiB
Python
169 lines
6.7 KiB
Python
#!/usr/bin/env python3
|
|
"""Smart Contract Security Agent - runs Slither and Mythril analysis on Solidity contracts."""
|
|
|
|
import json
|
|
import argparse
|
|
import logging
|
|
import subprocess
|
|
from collections import defaultdict
|
|
from datetime import datetime
|
|
|
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
|
logger = logging.getLogger(__name__)
|
|
|
|
SWC_REGISTRY = {
|
|
"SWC-101": "Integer Overflow and Underflow",
|
|
"SWC-104": "Unchecked Call Return Value",
|
|
"SWC-106": "Unprotected SELFDESTRUCT",
|
|
"SWC-107": "Reentrancy",
|
|
"SWC-110": "Assert Violation",
|
|
"SWC-112": "Delegatecall to Untrusted Callee",
|
|
"SWC-113": "DoS with Failed Call",
|
|
"SWC-115": "Authorization through tx.origin",
|
|
"SWC-116": "Block values as a proxy for time",
|
|
"SWC-120": "Weak Sources of Randomness",
|
|
}
|
|
|
|
|
|
def run_slither(contract_path):
|
|
"""Run Slither static analysis on Solidity contract."""
|
|
cmd = ["slither", contract_path, "--json", "-"]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=120)
|
|
try:
|
|
return json.loads(result.stdout) if result.stdout else {}
|
|
except json.JSONDecodeError:
|
|
logger.error("Slither JSON parse failed")
|
|
return {}
|
|
|
|
|
|
def run_mythril(contract_path, timeout=300):
|
|
"""Run Mythril symbolic execution analysis."""
|
|
cmd = ["myth", "analyze", contract_path, "--execution-timeout", str(timeout), "-o", "json"]
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=timeout + 60)
|
|
try:
|
|
return json.loads(result.stdout) if result.stdout else {}
|
|
except json.JSONDecodeError:
|
|
logger.error("Mythril JSON parse failed")
|
|
return {}
|
|
|
|
|
|
def analyze_slither_results(slither_output):
|
|
"""Parse and categorize Slither detector findings."""
|
|
findings = []
|
|
by_severity = defaultdict(int)
|
|
by_detector = defaultdict(int)
|
|
for detector in slither_output.get("results", {}).get("detectors", []):
|
|
severity = detector.get("impact", "informational").lower()
|
|
by_severity[severity] += 1
|
|
det_name = detector.get("check", "unknown")
|
|
by_detector[det_name] += 1
|
|
elements = detector.get("elements", [])
|
|
location = ""
|
|
if elements:
|
|
elem = elements[0]
|
|
location = f"{elem.get('source_mapping', {}).get('filename_short', '')}:" \
|
|
f"L{elem.get('source_mapping', {}).get('lines', [0])[0] if elem.get('source_mapping', {}).get('lines') else 0}"
|
|
findings.append({
|
|
"detector": det_name,
|
|
"severity": severity,
|
|
"description": detector.get("description", "")[:200],
|
|
"location": location,
|
|
"confidence": detector.get("confidence", ""),
|
|
})
|
|
return {
|
|
"total": len(findings),
|
|
"by_severity": dict(by_severity),
|
|
"by_detector": dict(sorted(by_detector.items(), key=lambda x: x[1], reverse=True)[:15]),
|
|
"findings": sorted(findings, key=lambda x: {"high": 0, "medium": 1, "low": 2, "informational": 3}.get(x["severity"], 4)),
|
|
}
|
|
|
|
|
|
def analyze_mythril_results(mythril_output):
|
|
"""Parse Mythril symbolic execution findings."""
|
|
findings = []
|
|
by_swc = defaultdict(int)
|
|
for issue in mythril_output.get("issues", []):
|
|
swc_id = issue.get("swc-id", "")
|
|
swc_key = f"SWC-{swc_id}" if swc_id else "unknown"
|
|
by_swc[swc_key] += 1
|
|
severity = issue.get("severity", "Medium").lower()
|
|
findings.append({
|
|
"swc_id": swc_key,
|
|
"swc_title": SWC_REGISTRY.get(swc_key, issue.get("title", "")),
|
|
"severity": severity,
|
|
"description": issue.get("description", "")[:200],
|
|
"contract": issue.get("contract", ""),
|
|
"function": issue.get("function", ""),
|
|
"line_number": issue.get("lineno", 0),
|
|
})
|
|
return {
|
|
"total": len(findings),
|
|
"by_swc": dict(by_swc),
|
|
"findings": findings,
|
|
}
|
|
|
|
|
|
def deduplicate_findings(slither_findings, mythril_findings):
|
|
"""Merge and deduplicate findings from both tools."""
|
|
combined = []
|
|
seen = set()
|
|
for f in slither_findings.get("findings", []):
|
|
key = (f.get("location", ""), f.get("detector", ""))
|
|
if key not in seen:
|
|
seen.add(key)
|
|
combined.append({**f, "source": "slither"})
|
|
for f in mythril_findings.get("findings", []):
|
|
key = (f.get("contract", "") + str(f.get("line_number", 0)), f.get("swc_id", ""))
|
|
if key not in seen:
|
|
seen.add(key)
|
|
combined.append({**f, "source": "mythril"})
|
|
return combined
|
|
|
|
|
|
def generate_report(contract_path, slither_analysis, mythril_analysis, combined):
|
|
critical_high = sum(1 for f in combined if f.get("severity") in ("high", "critical"))
|
|
return {
|
|
"timestamp": datetime.utcnow().isoformat(),
|
|
"contract": contract_path,
|
|
"slither_analysis": {
|
|
"total_findings": slither_analysis["total"],
|
|
"by_severity": slither_analysis["by_severity"],
|
|
"top_detectors": slither_analysis["by_detector"],
|
|
},
|
|
"mythril_analysis": {
|
|
"total_findings": mythril_analysis["total"],
|
|
"by_swc": mythril_analysis["by_swc"],
|
|
},
|
|
"combined_findings": len(combined),
|
|
"critical_high_findings": critical_high,
|
|
"audit_result": "FAIL" if critical_high > 0 else "PASS",
|
|
"findings": combined[:30],
|
|
}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Solidity Smart Contract Security Analysis Agent")
|
|
parser.add_argument("--contract", required=True, help="Path to Solidity contract or project directory")
|
|
parser.add_argument("--mythril-timeout", type=int, default=300, help="Mythril execution timeout (seconds)")
|
|
parser.add_argument("--skip-mythril", action="store_true", help="Skip Mythril (slow symbolic execution)")
|
|
parser.add_argument("--output", default="smart_contract_audit_report.json")
|
|
args = parser.parse_args()
|
|
|
|
slither_output = run_slither(args.contract)
|
|
slither_analysis = analyze_slither_results(slither_output)
|
|
mythril_analysis = {"total": 0, "by_swc": {}, "findings": []}
|
|
if not args.skip_mythril:
|
|
mythril_output = run_mythril(args.contract, args.mythril_timeout)
|
|
mythril_analysis = analyze_mythril_results(mythril_output)
|
|
combined = deduplicate_findings(slither_analysis, mythril_analysis)
|
|
report = generate_report(args.contract, slither_analysis, mythril_analysis, combined)
|
|
with open(args.output, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
logger.info("Smart contract audit: %d findings (%d critical/high), result: %s",
|
|
report["combined_findings"], report["critical_high_findings"], report["audit_result"])
|
|
print(json.dumps(report, indent=2, default=str))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|