mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 13:44: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
277 lines
11 KiB
Python
277 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for performing security headers audit.
|
|
|
|
Analyzes HTTP response headers for HSTS, CSP, X-Frame-Options,
|
|
cookie security attributes, and information disclosure to identify
|
|
missing or misconfigured browser-level protections.
|
|
"""
|
|
|
|
import requests
|
|
import json
|
|
import sys
|
|
import re
|
|
from datetime import datetime
|
|
|
|
|
|
class SecurityHeadersAgent:
|
|
"""Audits HTTP security headers on web applications."""
|
|
|
|
def __init__(self, target_url):
|
|
self.target_url = target_url
|
|
if not target_url.startswith("http"):
|
|
self.target_url = f"https://{target_url}"
|
|
self.session = requests.Session()
|
|
self.session.verify = True
|
|
|
|
def fetch_headers(self, path="/"):
|
|
"""Fetch response headers from a target path."""
|
|
url = f"{self.target_url.rstrip('/')}{path}"
|
|
try:
|
|
resp = self.session.get(url, timeout=10, allow_redirects=True)
|
|
return {
|
|
"url": url,
|
|
"status": resp.status_code,
|
|
"headers": dict(resp.headers),
|
|
"cookies": [
|
|
{"name": c.name, "value": c.value[:20], "attributes": {
|
|
"secure": c.secure, "httponly": "httponly" in c._rest,
|
|
"samesite": c._rest.get("SameSite", c._rest.get("samesite", "Not set")),
|
|
"path": c.path, "domain": c.domain,
|
|
}} for c in resp.cookies
|
|
],
|
|
}
|
|
except requests.RequestException as e:
|
|
return {"url": url, "error": str(e)}
|
|
|
|
def check_hsts(self, headers):
|
|
"""Check HTTP Strict Transport Security configuration."""
|
|
hsts = headers.get("Strict-Transport-Security", headers.get("strict-transport-security", ""))
|
|
finding = {
|
|
"header": "Strict-Transport-Security",
|
|
"present": bool(hsts),
|
|
"value": hsts,
|
|
"severity": "High" if not hsts else "Info",
|
|
}
|
|
if hsts:
|
|
finding["has_includeSubDomains"] = "includesubdomains" in hsts.lower()
|
|
finding["has_preload"] = "preload" in hsts.lower()
|
|
max_age_match = re.search(r"max-age=(\d+)", hsts)
|
|
if max_age_match:
|
|
max_age = int(max_age_match.group(1))
|
|
finding["max_age"] = max_age
|
|
finding["max_age_sufficient"] = max_age >= 31536000
|
|
if max_age < 31536000:
|
|
finding["severity"] = "Medium"
|
|
finding["recommendation"] = "Increase max-age to at least 31536000 (1 year)"
|
|
else:
|
|
finding["recommendation"] = "Add: Strict-Transport-Security: max-age=31536000; includeSubDomains; preload"
|
|
return finding
|
|
|
|
def check_csp(self, headers):
|
|
"""Analyze Content Security Policy for weaknesses."""
|
|
csp = headers.get("Content-Security-Policy", headers.get("content-security-policy", ""))
|
|
report_only = headers.get("Content-Security-Policy-Report-Only", "")
|
|
|
|
finding = {
|
|
"header": "Content-Security-Policy",
|
|
"present": bool(csp),
|
|
"value": csp[:500] if csp else "",
|
|
"report_only": bool(report_only),
|
|
"severity": "High" if not csp else "Info",
|
|
"issues": [],
|
|
}
|
|
|
|
if csp:
|
|
if "'unsafe-inline'" in csp:
|
|
finding["issues"].append("unsafe-inline allows inline script execution (XSS risk)")
|
|
finding["severity"] = "High"
|
|
if "'unsafe-eval'" in csp:
|
|
finding["issues"].append("unsafe-eval allows eval() calls (XSS risk)")
|
|
finding["severity"] = "High"
|
|
if " * " in f" {csp} " or csp.strip().endswith("*"):
|
|
finding["issues"].append("Wildcard (*) allows loading from any origin")
|
|
finding["severity"] = "Medium"
|
|
if "default-src" not in csp:
|
|
finding["issues"].append("Missing default-src fallback directive")
|
|
if report_only and not csp:
|
|
finding["issues"].append("CSP is report-only, not enforcing")
|
|
finding["severity"] = "Medium"
|
|
else:
|
|
finding["recommendation"] = "Implement Content-Security-Policy with script-src using nonces"
|
|
|
|
return finding
|
|
|
|
def check_frame_options(self, headers):
|
|
"""Check X-Frame-Options and frame-ancestors."""
|
|
xfo = headers.get("X-Frame-Options", headers.get("x-frame-options", ""))
|
|
csp = headers.get("Content-Security-Policy", "")
|
|
frame_ancestors = ""
|
|
if "frame-ancestors" in csp:
|
|
match = re.search(r"frame-ancestors\s+([^;]+)", csp)
|
|
if match:
|
|
frame_ancestors = match.group(1).strip()
|
|
|
|
finding = {
|
|
"header": "X-Frame-Options",
|
|
"present": bool(xfo),
|
|
"value": xfo,
|
|
"frame_ancestors": frame_ancestors,
|
|
"severity": "Medium" if not xfo and not frame_ancestors else "Info",
|
|
}
|
|
if not xfo and not frame_ancestors:
|
|
finding["recommendation"] = "Add X-Frame-Options: DENY or CSP frame-ancestors 'none'"
|
|
return finding
|
|
|
|
def check_content_type_options(self, headers):
|
|
"""Check X-Content-Type-Options."""
|
|
xcto = headers.get("X-Content-Type-Options", headers.get("x-content-type-options", ""))
|
|
return {
|
|
"header": "X-Content-Type-Options",
|
|
"present": bool(xcto),
|
|
"value": xcto,
|
|
"correct": xcto.lower() == "nosniff" if xcto else False,
|
|
"severity": "Medium" if not xcto else "Info",
|
|
"recommendation": "Add: X-Content-Type-Options: nosniff" if not xcto else None,
|
|
}
|
|
|
|
def check_referrer_policy(self, headers):
|
|
"""Check Referrer-Policy."""
|
|
rp = headers.get("Referrer-Policy", headers.get("referrer-policy", ""))
|
|
return {
|
|
"header": "Referrer-Policy",
|
|
"present": bool(rp),
|
|
"value": rp,
|
|
"severity": "Medium" if not rp else "Info",
|
|
"recommendation": "Add: Referrer-Policy: strict-origin-when-cross-origin" if not rp else None,
|
|
}
|
|
|
|
def check_permissions_policy(self, headers):
|
|
"""Check Permissions-Policy."""
|
|
pp = headers.get("Permissions-Policy", headers.get("permissions-policy", ""))
|
|
return {
|
|
"header": "Permissions-Policy",
|
|
"present": bool(pp),
|
|
"value": pp[:200] if pp else "",
|
|
"severity": "Low" if not pp else "Info",
|
|
"recommendation": "Add: Permissions-Policy: camera=(), microphone=(), geolocation=()" if not pp else None,
|
|
}
|
|
|
|
def check_info_disclosure(self, headers):
|
|
"""Check for information disclosure headers."""
|
|
findings = []
|
|
disclosure_headers = ["Server", "X-Powered-By", "X-AspNet-Version", "X-Generator"]
|
|
for h in disclosure_headers:
|
|
value = headers.get(h, headers.get(h.lower(), ""))
|
|
if value:
|
|
findings.append({
|
|
"header": h,
|
|
"value": value,
|
|
"severity": "Low",
|
|
"recommendation": f"Remove or genericize {h} header",
|
|
})
|
|
return findings
|
|
|
|
def check_cookie_security(self, cookies):
|
|
"""Audit cookie security attributes."""
|
|
findings = []
|
|
for cookie in cookies:
|
|
attrs = cookie.get("attributes", {})
|
|
issues = []
|
|
if not attrs.get("secure"):
|
|
issues.append("Missing Secure flag")
|
|
if not attrs.get("httponly"):
|
|
issues.append("Missing HttpOnly flag")
|
|
samesite = str(attrs.get("samesite", "Not set"))
|
|
if samesite == "Not set" or samesite == "None":
|
|
issues.append(f"SameSite={samesite}")
|
|
|
|
if issues:
|
|
findings.append({
|
|
"cookie": cookie["name"],
|
|
"issues": issues,
|
|
"severity": "High" if "Secure" in str(issues) else "Medium",
|
|
})
|
|
return findings
|
|
|
|
def calculate_grade(self, header_findings):
|
|
"""Calculate a letter grade based on findings."""
|
|
score = 100
|
|
for f in header_findings:
|
|
if not f.get("present", True):
|
|
sev = f.get("severity", "Info")
|
|
if sev == "High":
|
|
score -= 20
|
|
elif sev == "Medium":
|
|
score -= 10
|
|
elif sev == "Low":
|
|
score -= 5
|
|
if f.get("issues"):
|
|
score -= len(f["issues"]) * 5
|
|
|
|
if score >= 90:
|
|
return "A"
|
|
elif score >= 80:
|
|
return "B"
|
|
elif score >= 60:
|
|
return "C"
|
|
elif score >= 40:
|
|
return "D"
|
|
return "F"
|
|
|
|
def run_audit(self, paths=None):
|
|
"""Run complete security headers audit."""
|
|
if paths is None:
|
|
paths = ["/"]
|
|
|
|
all_findings = []
|
|
for path in paths:
|
|
response_data = self.fetch_headers(path)
|
|
if "error" in response_data:
|
|
continue
|
|
|
|
headers = response_data["headers"]
|
|
findings = {
|
|
"path": path,
|
|
"hsts": self.check_hsts(headers),
|
|
"csp": self.check_csp(headers),
|
|
"x_frame_options": self.check_frame_options(headers),
|
|
"x_content_type_options": self.check_content_type_options(headers),
|
|
"referrer_policy": self.check_referrer_policy(headers),
|
|
"permissions_policy": self.check_permissions_policy(headers),
|
|
"info_disclosure": self.check_info_disclosure(headers),
|
|
"cookie_security": self.check_cookie_security(response_data.get("cookies", [])),
|
|
}
|
|
all_findings.append(findings)
|
|
|
|
header_checks = []
|
|
if all_findings:
|
|
f = all_findings[0]
|
|
header_checks = [f["hsts"], f["csp"], f["x_frame_options"],
|
|
f["x_content_type_options"], f["referrer_policy"],
|
|
f["permissions_policy"]]
|
|
|
|
report = {
|
|
"target": self.target_url,
|
|
"audit_date": datetime.utcnow().isoformat(),
|
|
"grade": self.calculate_grade(header_checks),
|
|
"findings": all_findings,
|
|
}
|
|
print(json.dumps(report, indent=2))
|
|
return report
|
|
|
|
|
|
def main():
|
|
if len(sys.argv) < 2:
|
|
print("Usage: agent.py <target_url> [path1,path2,...]")
|
|
sys.exit(1)
|
|
|
|
target_url = sys.argv[1]
|
|
paths = sys.argv[2].split(",") if len(sys.argv) > 2 else ["/"]
|
|
|
|
agent = SecurityHeadersAgent(target_url)
|
|
agent.run_audit(paths)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|