mirror of
https://github.com/mukul975/Anthropic-Cybersecurity-Skills.git
synced 2026-06-11 21:54:56 +03:00
27c6414ca5
Complete skill folder anatomy across all cybersecurity skills: - scripts/agent.py: 80-150 line Python agents using real libraries (impacket, boto3, azure-mgmt-*, kubernetes, pefile, yara, scapy, shodan, stix2, etc.) - references/api-reference.md: real API documentation with method signatures - LICENSE: MIT license for all skill folders
170 lines
7.0 KiB
Python
170 lines
7.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for auditing API schema validation security using OpenAPI specs."""
|
|
|
|
import json
|
|
import argparse
|
|
import re
|
|
from datetime import datetime
|
|
from pathlib import Path
|
|
|
|
try:
|
|
import yaml
|
|
except ImportError:
|
|
yaml = None
|
|
|
|
try:
|
|
import jsonschema
|
|
except ImportError:
|
|
jsonschema = None
|
|
|
|
|
|
def load_openapi_spec(spec_path):
|
|
"""Load an OpenAPI specification file."""
|
|
with open(spec_path) as f:
|
|
if spec_path.endswith((".yaml", ".yml")):
|
|
return yaml.safe_load(f)
|
|
return json.load(f)
|
|
|
|
|
|
def audit_schema_validation(spec):
|
|
"""Audit OpenAPI spec for missing schema validation."""
|
|
findings = []
|
|
paths = spec.get("paths", {})
|
|
for path, methods in paths.items():
|
|
for method, details in methods.items():
|
|
if method in ("get", "post", "put", "patch", "delete"):
|
|
request_body = details.get("requestBody", {})
|
|
if method in ("post", "put", "patch") and not request_body:
|
|
findings.append({
|
|
"path": path, "method": method.upper(),
|
|
"issue": "no_request_body_schema", "severity": "HIGH",
|
|
})
|
|
elif request_body:
|
|
content = request_body.get("content", {})
|
|
for media, media_def in content.items():
|
|
schema = media_def.get("schema", {})
|
|
if not schema:
|
|
findings.append({
|
|
"path": path, "method": method.upper(),
|
|
"issue": "empty_request_schema", "severity": "HIGH",
|
|
})
|
|
elif schema.get("additionalProperties") is not False:
|
|
findings.append({
|
|
"path": path, "method": method.upper(),
|
|
"issue": "additional_properties_allowed",
|
|
"severity": "MEDIUM",
|
|
"risk": "mass_assignment",
|
|
})
|
|
params = details.get("parameters", [])
|
|
for param in params:
|
|
if not param.get("schema"):
|
|
findings.append({
|
|
"path": path, "method": method.upper(),
|
|
"parameter": param.get("name"),
|
|
"issue": "parameter_no_schema", "severity": "MEDIUM",
|
|
})
|
|
elif param.get("schema", {}).get("type") == "string":
|
|
schema = param["schema"]
|
|
if not schema.get("maxLength") and not schema.get("pattern"):
|
|
findings.append({
|
|
"path": path, "method": method.upper(),
|
|
"parameter": param.get("name"),
|
|
"issue": "string_no_max_length", "severity": "MEDIUM",
|
|
"risk": "injection",
|
|
})
|
|
responses = details.get("responses", {})
|
|
for code, resp in responses.items():
|
|
content = resp.get("content", {})
|
|
for media, media_def in content.items():
|
|
schema = media_def.get("schema", {})
|
|
if not schema:
|
|
findings.append({
|
|
"path": path, "method": method.upper(),
|
|
"response_code": code,
|
|
"issue": "no_response_schema", "severity": "MEDIUM",
|
|
"risk": "data_exposure",
|
|
})
|
|
return findings
|
|
|
|
|
|
def check_security_definitions(spec):
|
|
"""Check security scheme definitions in OpenAPI spec."""
|
|
findings = []
|
|
version = spec.get("openapi", spec.get("swagger", ""))
|
|
if version.startswith("3"):
|
|
security_schemes = spec.get("components", {}).get("securitySchemes", {})
|
|
else:
|
|
security_schemes = spec.get("securityDefinitions", {})
|
|
if not security_schemes:
|
|
findings.append({"issue": "no_security_schemes_defined", "severity": "CRITICAL"})
|
|
global_security = spec.get("security", [])
|
|
if not global_security:
|
|
findings.append({"issue": "no_global_security", "severity": "HIGH"})
|
|
paths = spec.get("paths", {})
|
|
for path, methods in paths.items():
|
|
for method, details in methods.items():
|
|
if method in ("get", "post", "put", "patch", "delete"):
|
|
op_security = details.get("security")
|
|
if op_security == []:
|
|
findings.append({
|
|
"path": path, "method": method.upper(),
|
|
"issue": "security_explicitly_disabled", "severity": "CRITICAL",
|
|
})
|
|
return findings
|
|
|
|
|
|
def validate_request_payload(schema_path, payload_path):
|
|
"""Validate a request payload against a JSON schema."""
|
|
if jsonschema is None:
|
|
return {"error": "jsonschema not installed"}
|
|
with open(schema_path) as f:
|
|
schema = json.load(f)
|
|
with open(payload_path) as f:
|
|
payload = json.load(f)
|
|
errors = []
|
|
v = jsonschema.Draft7Validator(schema)
|
|
for error in v.iter_errors(payload):
|
|
errors.append({
|
|
"path": list(error.path),
|
|
"message": error.message,
|
|
"schema_path": list(error.schema_path),
|
|
})
|
|
return {"valid": len(errors) == 0, "errors": errors}
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="API Schema Validation Security Agent")
|
|
parser.add_argument("--spec", help="OpenAPI spec file (JSON/YAML)")
|
|
parser.add_argument("--schema", help="JSON Schema for validation")
|
|
parser.add_argument("--payload", help="Request payload to validate")
|
|
parser.add_argument("--output", default="schema_validation_report.json")
|
|
parser.add_argument("--action", choices=["audit", "security", "validate", "full"],
|
|
default="full")
|
|
args = parser.parse_args()
|
|
|
|
report = {"generated_at": datetime.utcnow().isoformat(), "findings": {}}
|
|
|
|
if args.spec:
|
|
spec = load_openapi_spec(args.spec)
|
|
if args.action in ("audit", "full"):
|
|
f = audit_schema_validation(spec)
|
|
report["findings"]["schema_audit"] = f
|
|
print(f"[+] Schema validation issues: {len(f)}")
|
|
if args.action in ("security", "full"):
|
|
f = check_security_definitions(spec)
|
|
report["findings"]["security_schemes"] = f
|
|
print(f"[+] Security definition issues: {len(f)}")
|
|
|
|
if args.action == "validate" and args.schema and args.payload:
|
|
result = validate_request_payload(args.schema, args.payload)
|
|
report["findings"]["validation"] = result
|
|
print(f"[+] Validation: {'PASS' if result.get('valid') else 'FAIL'}")
|
|
|
|
with open(args.output, "w") as fout:
|
|
json.dump(report, fout, indent=2, default=str)
|
|
print(f"[+] Report saved to {args.output}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|