Files
Anthropic-Cybersecurity-Skills/skills/configuring-oauth2-authorization-flow/scripts/agent.py
T
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

141 lines
5.4 KiB
Python

#!/usr/bin/env python3
"""OAuth 2.0 authorization flow security audit agent."""
import json
import sys
import argparse
from datetime import datetime
try:
import requests
except ImportError:
print("Install: pip install requests")
sys.exit(1)
def discover_oauth_endpoints(issuer_url):
"""Discover OAuth 2.0 / OIDC endpoints from well-known configuration."""
discovery_url = f"{issuer_url.rstrip('/')}/.well-known/openid-configuration"
try:
resp = requests.get(discovery_url, timeout=10)
resp.raise_for_status()
config = resp.json()
return {
"issuer": config.get("issuer", ""),
"authorization_endpoint": config.get("authorization_endpoint", ""),
"token_endpoint": config.get("token_endpoint", ""),
"userinfo_endpoint": config.get("userinfo_endpoint", ""),
"jwks_uri": config.get("jwks_uri", ""),
"supported_grant_types": config.get("grant_types_supported", []),
"supported_scopes": config.get("scopes_supported", []),
"supported_response_types": config.get("response_types_supported", []),
"token_endpoint_auth_methods": config.get("token_endpoint_auth_methods_supported", []),
}
except Exception as e:
return {"error": str(e)}
def audit_oauth_security(config):
"""Audit OAuth configuration for security issues."""
findings = []
if "implicit" in config.get("supported_grant_types", []):
findings.append({
"issue": "Implicit grant type supported",
"severity": "HIGH",
"recommendation": "Disable implicit flow; use authorization code + PKCE",
})
if "password" in config.get("supported_grant_types", []):
findings.append({
"issue": "Resource owner password grant supported",
"severity": "MEDIUM",
"recommendation": "Disable ROPC grant; use authorization code flow",
})
auth_methods = config.get("token_endpoint_auth_methods", [])
if "none" in auth_methods:
findings.append({
"issue": "Token endpoint allows unauthenticated clients",
"severity": "MEDIUM",
"recommendation": "Require client_secret_basic or private_key_jwt",
})
if "code" in config.get("supported_response_types", []):
if "code id_token" not in config.get("supported_response_types", []):
findings.append({
"issue": "Authorization code flow available",
"severity": "INFO",
"note": "Ensure PKCE is enforced for public clients",
})
return findings
def test_token_endpoint(token_url, client_id, client_secret, grant_type="client_credentials"):
"""Test token endpoint with client credentials."""
try:
resp = requests.post(token_url, data={
"grant_type": grant_type,
"client_id": client_id,
"client_secret": client_secret,
}, timeout=10)
if resp.status_code == 200:
token_data = resp.json()
return {
"status": "success",
"token_type": token_data.get("token_type", ""),
"expires_in": token_data.get("expires_in", 0),
"scope": token_data.get("scope", ""),
}
return {"status": "failed", "code": resp.status_code, "body": resp.text[:200]}
except Exception as e:
return {"status": "error", "message": str(e)}
def run_audit(issuer_url, client_id=None, client_secret=None):
"""Execute OAuth 2.0 security audit."""
print(f"\n{'='*60}")
print(f" OAUTH 2.0 AUTHORIZATION FLOW AUDIT")
print(f" Issuer: {issuer_url}")
print(f" Generated: {datetime.utcnow().isoformat()} UTC")
print(f"{'='*60}\n")
config = discover_oauth_endpoints(issuer_url)
if "error" in config:
print(f" Error: {config['error']}")
return config
print(f"--- DISCOVERED ENDPOINTS ---")
print(f" Authorization: {config.get('authorization_endpoint', 'N/A')}")
print(f" Token: {config.get('token_endpoint', 'N/A')}")
print(f" JWKS: {config.get('jwks_uri', 'N/A')}")
print(f" Grant types: {config.get('supported_grant_types', [])}")
findings = audit_oauth_security(config)
print(f"\n--- SECURITY FINDINGS ({len(findings)}) ---")
for f in findings:
print(f" [{f['severity']}] {f['issue']}")
token_test = {}
if client_id and client_secret and config.get("token_endpoint"):
token_test = test_token_endpoint(config["token_endpoint"], client_id, client_secret)
print(f"\n--- TOKEN ENDPOINT TEST ---")
print(f" Status: {token_test.get('status', 'N/A')}")
return {"config": config, "findings": findings, "token_test": token_test}
def main():
parser = argparse.ArgumentParser(description="OAuth 2.0 Audit Agent")
parser.add_argument("--issuer", required=True, help="OAuth issuer URL")
parser.add_argument("--client-id", help="Client ID for token test")
parser.add_argument("--client-secret", help="Client secret for token test")
parser.add_argument("--output", help="Save report to JSON file")
args = parser.parse_args()
report = run_audit(args.issuer, args.client_id, args.client_secret)
if args.output:
with open(args.output, "w") as f:
json.dump(report, f, indent=2, default=str)
print(f"\n[+] Report saved to {args.output}")
if __name__ == "__main__":
main()