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
197 lines
8.0 KiB
Python
197 lines
8.0 KiB
Python
#!/usr/bin/env python3
|
|
"""Agent for auditing container registry image security: scanning, signing, and SBOM."""
|
|
|
|
import boto3
|
|
import subprocess
|
|
import json
|
|
import os
|
|
import argparse
|
|
from datetime import datetime
|
|
|
|
|
|
def scan_image_trivy(image, severity="HIGH,CRITICAL", output_format="json"):
|
|
"""Scan a container image with Trivy for vulnerabilities."""
|
|
print(f"[*] Scanning {image} with Trivy...")
|
|
cmd = ["trivy", "image", "--severity", severity, "--format", output_format, image]
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
|
if output_format == "json" and result.stdout:
|
|
data = json.loads(result.stdout)
|
|
total_vulns = 0
|
|
for target in data.get("Results", []):
|
|
vulns = target.get("Vulnerabilities", [])
|
|
total_vulns += len(vulns)
|
|
if vulns:
|
|
print(f" Target: {target.get('Target', 'unknown')}: {len(vulns)} vulnerabilities")
|
|
for v in vulns[:5]:
|
|
print(f" [{v.get('Severity', '?')}] {v.get('VulnerabilityID', '?')} "
|
|
f"- {v.get('PkgName', '?')} {v.get('InstalledVersion', '?')}")
|
|
print(f"[*] Total vulnerabilities found: {total_vulns}")
|
|
return data
|
|
else:
|
|
print(result.stdout)
|
|
return None
|
|
except FileNotFoundError:
|
|
print(" [-] Trivy not installed. Install: brew install trivy")
|
|
return None
|
|
except subprocess.TimeoutExpired:
|
|
print(" [-] Scan timed out")
|
|
return None
|
|
|
|
|
|
def generate_sbom(image, output_format="spdx-json", output_file="sbom.json"):
|
|
"""Generate SBOM using Syft for a container image."""
|
|
print(f"\n[*] Generating SBOM for {image}...")
|
|
cmd = ["syft", image, "-o", output_format]
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=300)
|
|
if result.stdout:
|
|
with open(output_file, "w") as f:
|
|
f.write(result.stdout)
|
|
data = json.loads(result.stdout)
|
|
packages = data.get("packages", [])
|
|
print(f" [+] SBOM generated: {len(packages)} packages")
|
|
print(f" [+] Saved to {output_file}")
|
|
return data
|
|
return None
|
|
except FileNotFoundError:
|
|
print(" [-] Syft not installed. Install: curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh")
|
|
return None
|
|
|
|
|
|
def verify_image_signature(image, key_file=None):
|
|
"""Verify image signature using Cosign."""
|
|
print(f"\n[*] Verifying signature for {image}...")
|
|
cmd = ["cosign", "verify"]
|
|
if key_file:
|
|
cmd.extend(["--key", key_file])
|
|
cmd.append(image)
|
|
try:
|
|
result = subprocess.run(cmd, capture_output=True, text=True, timeout=60)
|
|
if result.returncode == 0:
|
|
print(f" [+] Signature VALID for {image}")
|
|
return True
|
|
else:
|
|
print(f" [-] Signature verification FAILED: {result.stderr.strip()}")
|
|
return False
|
|
except FileNotFoundError:
|
|
print(" [-] Cosign not installed")
|
|
return None
|
|
|
|
|
|
def audit_ecr_repository(repo_name, region="us-east-1"):
|
|
"""Audit an ECR repository for security configuration."""
|
|
ecr = boto3.client("ecr", region_name=region)
|
|
findings = []
|
|
try:
|
|
scan_config = ecr.describe_repositories(repositoryNames=[repo_name])["repositories"][0]
|
|
repo_uri = scan_config["repositoryUri"]
|
|
print(f"\n[*] Auditing ECR repository: {repo_name}")
|
|
print(f" URI: {repo_uri}")
|
|
|
|
img_scan = scan_config.get("imageScanningConfiguration", {})
|
|
if not img_scan.get("scanOnPush", False):
|
|
findings.append({"check": "scan_on_push", "status": "FAIL", "detail": "Scan on push disabled"})
|
|
print(" [!] Scan on push: DISABLED")
|
|
else:
|
|
print(" [+] Scan on push: ENABLED")
|
|
|
|
mutability = scan_config.get("imageTagMutability", "MUTABLE")
|
|
if mutability == "MUTABLE":
|
|
findings.append({"check": "tag_immutability", "status": "FAIL", "detail": "Tags are mutable"})
|
|
print(" [!] Tag immutability: MUTABLE (tags can be overwritten)")
|
|
else:
|
|
print(" [+] Tag immutability: IMMUTABLE")
|
|
|
|
try:
|
|
lifecycle = ecr.get_lifecycle_policy(repositoryName=repo_name)
|
|
print(" [+] Lifecycle policy: CONFIGURED")
|
|
except ecr.exceptions.LifecyclePolicyNotFoundException:
|
|
findings.append({"check": "lifecycle_policy", "status": "FAIL", "detail": "No lifecycle policy"})
|
|
print(" [!] Lifecycle policy: NOT CONFIGURED")
|
|
|
|
images = ecr.list_images(repositoryName=repo_name, filter={"tagStatus": "UNTAGGED"})
|
|
untagged = len(images.get("imageIds", []))
|
|
if untagged > 0:
|
|
print(f" [!] Untagged images: {untagged}")
|
|
|
|
except ecr.exceptions.RepositoryNotFoundException:
|
|
print(f" [-] Repository '{repo_name}' not found")
|
|
return findings
|
|
|
|
|
|
def get_ecr_scan_findings(repo_name, tag="latest", region="us-east-1"):
|
|
"""Get vulnerability scan findings for an ECR image."""
|
|
ecr = boto3.client("ecr", region_name=region)
|
|
try:
|
|
response = ecr.describe_image_scan_findings(
|
|
repositoryName=repo_name, imageId={"imageTag": tag},
|
|
)
|
|
findings = response.get("imageScanFindings", {})
|
|
severity_counts = findings.get("findingSeverityCounts", {})
|
|
print(f"\n[*] ECR scan findings for {repo_name}:{tag}")
|
|
for sev, count in sorted(severity_counts.items()):
|
|
print(f" [{sev}] {count}")
|
|
vuln_findings = findings.get("findings", [])
|
|
for v in vuln_findings[:10]:
|
|
print(f" - {v.get('name', '?')}: {v.get('description', '')[:100]}")
|
|
return findings
|
|
except Exception as e:
|
|
print(f" [-] Error: {e}")
|
|
return {}
|
|
|
|
|
|
def full_audit(image, repo_name=None, region="us-east-1", output_dir="."):
|
|
"""Run complete container image security audit."""
|
|
print("[*] Starting container image security audit...")
|
|
os.makedirs(output_dir, exist_ok=True)
|
|
report = {"audit_date": datetime.now().isoformat(), "image": image}
|
|
|
|
scan_results = scan_image_trivy(image)
|
|
report["trivy_scan"] = scan_results
|
|
|
|
sbom_path = os.path.join(output_dir, "sbom.json")
|
|
sbom = generate_sbom(image, output_file=sbom_path)
|
|
report["sbom_packages"] = len(sbom.get("packages", [])) if sbom else 0
|
|
|
|
sig_valid = verify_image_signature(image)
|
|
report["signature_valid"] = sig_valid
|
|
|
|
if repo_name:
|
|
ecr_findings = audit_ecr_repository(repo_name, region)
|
|
report["ecr_audit"] = ecr_findings
|
|
|
|
report_path = os.path.join(output_dir, "container_security_report.json")
|
|
with open(report_path, "w") as f:
|
|
json.dump(report, f, indent=2, default=str)
|
|
print(f"\n[*] Full report saved to {report_path}")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="Container Registry Image Security Agent")
|
|
parser.add_argument("action", choices=["scan", "sbom", "verify", "ecr-audit", "ecr-findings", "full-audit"])
|
|
parser.add_argument("--image", help="Container image reference")
|
|
parser.add_argument("--repo", help="ECR repository name")
|
|
parser.add_argument("--tag", default="latest", help="Image tag for ECR scan results")
|
|
parser.add_argument("--key", help="Cosign public key file")
|
|
parser.add_argument("--region", default="us-east-1")
|
|
parser.add_argument("-o", "--output", default=".")
|
|
args = parser.parse_args()
|
|
|
|
if args.action == "scan":
|
|
scan_image_trivy(args.image)
|
|
elif args.action == "sbom":
|
|
generate_sbom(args.image)
|
|
elif args.action == "verify":
|
|
verify_image_signature(args.image, args.key)
|
|
elif args.action == "ecr-audit":
|
|
audit_ecr_repository(args.repo, args.region)
|
|
elif args.action == "ecr-findings":
|
|
get_ecr_scan_findings(args.repo, args.tag, args.region)
|
|
elif args.action == "full-audit":
|
|
full_audit(args.image, args.repo, args.region, args.output)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|