Files
mukul975 c47eed6a64 Production hardening: security fixes, code quality, 724 skills complete
- 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
2026-03-19 13:26:49 +01:00

326 lines
13 KiB
Python

#!/usr/bin/env python3
"""Application cryptographic audit agent.
Audits application code and configurations for cryptographic weaknesses
including weak algorithms, insecure key sizes, hardcoded keys, deprecated
protocols, and missing certificate validation. Scans source files, config
files, and TLS endpoints.
"""
import argparse
import json
import os
import re
import ssl
import socket
import sys
from datetime import datetime, timezone
try:
from cryptography import x509
from cryptography.hazmat.backends import default_backend
HAS_CRYPTO = True
except ImportError:
HAS_CRYPTO = False
WEAK_PATTERNS = {
"weak_hash": {
"pattern": r'\b(MD5|md5|SHA1|sha1|SHA-1)\b(?!.*hmac)',
"severity": "HIGH",
"description": "Weak hash algorithm detected (MD5/SHA1)",
"recommendation": "Use SHA-256 or SHA-3",
},
"weak_cipher": {
"pattern": r'\b(DES|3DES|RC4|RC2|Blowfish|IDEA)\b',
"severity": "HIGH",
"description": "Weak cipher algorithm detected",
"recommendation": "Use AES-256-GCM or ChaCha20-Poly1305",
},
"ecb_mode": {
"pattern": r'\b(ECB|MODE_ECB|ecb)\b',
"severity": "CRITICAL",
"description": "ECB mode detected - does not provide semantic security",
"recommendation": "Use GCM, CBC with HMAC, or CTR mode",
},
"hardcoded_key": {
"pattern": r'(?:key|secret|password|token)\s*[=:]\s*["\'][A-Za-z0-9+/=]{16,}["\']',
"severity": "CRITICAL",
"description": "Possible hardcoded cryptographic key or secret",
"recommendation": "Use environment variables or a secrets manager",
},
"small_rsa_key": {
"pattern": r'(?:key_?size|bits)\s*[=:]\s*(?:512|768|1024)\b',
"severity": "CRITICAL",
"description": "RSA key size too small (<2048 bits)",
"recommendation": "Use minimum 2048-bit, preferably 4096-bit RSA keys",
},
"weak_random": {
"pattern": r'\b(?:random\.random|math\.random|Math\.random|rand\(\)|srand)\b',
"severity": "HIGH",
"description": "Non-cryptographic random number generator used",
"recommendation": "Use secrets module, os.urandom, or crypto.getRandomValues",
},
"ssl_no_verify": {
"pattern": r'(?:verify\s*=\s*False|CERT_NONE|SSL_VERIFY_NONE|InsecureRequestWarning|verify_ssl\s*=\s*False)',
"severity": "HIGH",
"description": "TLS certificate verification disabled",
"recommendation": "Enable certificate verification in all TLS connections",
},
"tls_v1": {
"pattern": r'(?:TLSv1[^._23]|SSLv[23]|PROTOCOL_TLS(?!v1_[23])|TLS_1_0|TLS_1_1)',
"severity": "HIGH",
"description": "Deprecated TLS/SSL protocol version detected",
"recommendation": "Use TLS 1.2 or TLS 1.3 minimum",
},
"padding_oracle": {
"pattern": r'(?:PKCS1v15|pkcs1_v1_5|PKCS5Padding)(?!.*OAEP)',
"severity": "MEDIUM",
"description": "PKCS#1 v1.5 padding used (vulnerable to padding oracle attacks)",
"recommendation": "Use OAEP padding for RSA, or switch to AEAD ciphers",
},
"static_iv": {
"pattern": r'(?:iv|IV|nonce)\s*[=:]\s*(?:b?["\'][^"\']{1,32}["\']|bytes\([^)]*\))',
"severity": "HIGH",
"description": "Possible static/hardcoded IV or nonce",
"recommendation": "Generate random IV/nonce for each encryption operation",
},
}
SCAN_EXTENSIONS = {
".py", ".js", ".ts", ".java", ".go", ".rb", ".php", ".cs", ".c", ".cpp",
".h", ".yaml", ".yml", ".json", ".xml", ".conf", ".cfg", ".ini", ".env",
".toml", ".properties",
}
def scan_file(file_path):
"""Scan a single file for cryptographic weaknesses."""
findings = []
try:
with open(file_path, "r", encoding="utf-8", errors="ignore") as f:
lines = f.readlines()
except (IOError, PermissionError):
return findings
for line_num, line in enumerate(lines, 1):
for rule_id, rule in WEAK_PATTERNS.items():
if re.search(rule["pattern"], line):
findings.append({
"rule": rule_id,
"file": file_path,
"line": line_num,
"content": line.strip()[:120],
"severity": rule["severity"],
"description": rule["description"],
"recommendation": rule["recommendation"],
})
return findings
def scan_directory(directory, extensions=None, exclude_dirs=None):
"""Recursively scan a directory for cryptographic weaknesses."""
if extensions is None:
extensions = SCAN_EXTENSIONS
if exclude_dirs is None:
exclude_dirs = {"node_modules", ".git", "__pycache__", "venv", ".venv",
"vendor", "dist", "build", ".tox"}
all_findings = []
files_scanned = 0
for root, dirs, files in os.walk(directory):
dirs[:] = [d for d in dirs if d not in exclude_dirs]
for fname in files:
ext = os.path.splitext(fname)[1].lower()
if ext not in extensions:
continue
full_path = os.path.join(root, fname)
findings = scan_file(full_path)
all_findings.extend(findings)
files_scanned += 1
print(f"[+] Scanned {files_scanned} files, found {len(all_findings)} issues")
return all_findings, files_scanned
def audit_tls_endpoint(host, port=443):
"""Audit TLS configuration of a remote endpoint."""
findings = []
print(f"[*] Auditing TLS: {host}:{port}")
try:
context = ssl.create_default_context()
with socket.create_connection((host, port), timeout=10) as sock:
with context.wrap_socket(sock, server_hostname=host) as ssock:
cert_der = ssock.getpeercert(binary_form=True)
cipher = ssock.cipher()
protocol = ssock.version()
findings.append({
"check": "TLS Protocol",
"value": protocol,
"severity": "CRITICAL" if "TLSv1.0" in protocol or "TLSv1.1" in protocol
else "INFO",
"description": f"Negotiated protocol: {protocol}",
})
if cipher:
cipher_name, tls_ver, key_bits = cipher
findings.append({
"check": "Cipher Suite",
"value": cipher_name,
"severity": "HIGH" if any(w in cipher_name for w in ["RC4", "DES", "NULL", "EXPORT"])
else "INFO",
"description": f"Cipher: {cipher_name} ({key_bits} bits)",
})
if HAS_CRYPTO and cert_der:
cert = x509.load_der_x509_certificate(cert_der, default_backend())
not_after = cert.not_valid_after_utc if hasattr(cert, 'not_valid_after_utc') else cert.not_valid_after
days_remaining = (not_after - datetime.now(timezone.utc)).days
pub_key = cert.public_key()
key_size = getattr(pub_key, 'key_size', 0)
sig_algo = cert.signature_algorithm_oid._name if hasattr(cert.signature_algorithm_oid, '_name') else str(cert.signature_hash_algorithm)
findings.append({
"check": "Certificate Expiry",
"value": f"{days_remaining} days",
"severity": "CRITICAL" if days_remaining < 0
else "HIGH" if days_remaining < 30
else "MEDIUM" if days_remaining < 90
else "INFO",
"description": f"Expires: {not_after.isoformat()} ({days_remaining} days)",
})
findings.append({
"check": "Key Size",
"value": f"{key_size} bits",
"severity": "CRITICAL" if key_size < 2048
else "MEDIUM" if key_size < 4096
else "INFO",
})
findings.append({
"check": "Signature Algorithm",
"value": str(sig_algo),
"severity": "HIGH" if "sha1" in str(sig_algo).lower() else "INFO",
})
except ssl.SSLError as e:
findings.append({"check": "TLS Connection", "severity": "CRITICAL",
"description": f"SSL error: {e}"})
except socket.timeout:
findings.append({"check": "TLS Connection", "severity": "HIGH",
"description": "Connection timed out"})
except Exception as e:
findings.append({"check": "TLS Connection", "severity": "HIGH",
"description": f"Error: {e}"})
return findings
def format_summary(code_findings, tls_findings, files_scanned, target):
"""Print audit summary."""
all_findings = code_findings + tls_findings
print(f"\n{'='*60}")
print(f" Cryptographic Audit Report")
print(f"{'='*60}")
print(f" Target : {target}")
print(f" Files Scanned : {files_scanned}")
print(f" Code Findings : {len(code_findings)}")
print(f" TLS Findings : {len(tls_findings)}")
severity_counts = {}
for f in all_findings:
sev = f.get("severity", "INFO")
severity_counts[sev] = severity_counts.get(sev, 0) + 1
print(f"\n By Severity:")
for sev in ["CRITICAL", "HIGH", "MEDIUM", "LOW", "INFO"]:
count = severity_counts.get(sev, 0)
if count > 0:
print(f" {sev:10s}: {count}")
if code_findings:
by_rule = {}
for f in code_findings:
by_rule.setdefault(f["rule"], []).append(f)
print(f"\n Code Issues by Rule:")
for rule, items in sorted(by_rule.items(), key=lambda x: -len(x[1])):
print(f" {rule:25s}: {len(items)} ({items[0]['severity']})")
print(f"\n Top Code Findings:")
for f in code_findings[:15]:
if f["severity"] in ("CRITICAL", "HIGH"):
print(f" [{f['severity']:8s}] {f['file']}:{f['line']} - {f['description']}")
if tls_findings:
print(f"\n TLS Audit Results:")
for f in tls_findings:
print(f" [{f['severity']:8s}] {f.get('check', '')}: "
f"{f.get('value', f.get('description', ''))}")
return severity_counts
def main():
parser = argparse.ArgumentParser(
description="Application cryptographic audit agent"
)
parser.add_argument("--target", required=True,
help="Source directory to scan or TLS host to audit")
parser.add_argument("--tls-port", type=int, default=443,
help="TLS port for endpoint audit (default: 443)")
parser.add_argument("--tls-only", action="store_true",
help="Only audit TLS endpoint, skip code scan")
parser.add_argument("--code-only", action="store_true",
help="Only scan code, skip TLS audit")
parser.add_argument("--exclude-dirs", nargs="+",
help="Additional directories to exclude from scan")
parser.add_argument("--output", "-o", help="Output JSON report path")
parser.add_argument("--verbose", "-v", action="store_true")
args = parser.parse_args()
code_findings = []
tls_findings = []
files_scanned = 0
if not args.tls_only and os.path.isdir(args.target):
exclude = {"node_modules", ".git", "__pycache__", "venv", ".venv", "vendor"}
if args.exclude_dirs:
exclude.update(args.exclude_dirs)
code_findings, files_scanned = scan_directory(args.target, exclude_dirs=exclude)
if not args.code_only and not os.path.isdir(args.target):
tls_findings = audit_tls_endpoint(args.target, args.tls_port)
elif not args.code_only and os.path.isdir(args.target):
pass # Can't audit TLS on a directory
severity_counts = format_summary(code_findings, tls_findings, files_scanned, args.target)
report = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"tool": "Crypto Audit",
"target": args.target,
"files_scanned": files_scanned,
"severity_counts": severity_counts,
"code_findings": code_findings,
"tls_findings": tls_findings,
"risk_level": (
"CRITICAL" if severity_counts.get("CRITICAL", 0) > 0
else "HIGH" if severity_counts.get("HIGH", 0) > 0
else "MEDIUM" if severity_counts.get("MEDIUM", 0) > 0
else "LOW"
),
}
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2)
print(f"\n[+] Report saved to {args.output}")
elif args.verbose:
print(json.dumps(report, indent=2))
if __name__ == "__main__":
main()