Files
Anthropic-Cybersecurity-Skills/skills/securing-container-registry-images/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

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()