Files
Anthropic-Cybersecurity-Skills/skills/generating-and-analyzing-sboms/scripts/agent.py
T
mukul975 8cae0648ec Add 55 new skills across 3 new domains + 6 undercovered areas (762 -> 817)
Demand-driven expansion targeting the fastest-growing 2025-2026 threat and
skills categories (ISC2/WEF/CrowdStrike/Mandiant signals):

- AI Security (NEW domain, 12 skills): LLM red-teaming with garak/PyRIT,
  prompt injection (direct/indirect/RAG), MCP tool-poisoning, agentic tool
  invocation, guardrails, model/data poisoning, system-prompt leakage,
  embedding/vector weaknesses, model extraction, continuous red-teaming
- Supply Chain Security (NEW domain, 5 skills): SBOMs, dependency confusion,
  malicious-npm triage, typosquatting, SLSA/Sigstore provenance
- Hardware & Firmware Security (NEW domain, 4 skills): CHIPSEC/UEFI audit,
  Secure Boot bypass, TPM measured-boot attestation, ESP bootkit hunting
- Identity (10): Entra ID/ROADtools, GraphRunner, AADInternals, ADCS/Certipy,
  shadow credentials, coercion, BloodHound CE, device-code phishing, SSO abuse
- Cloud-native (8): Stratus, Pacu, CloudFox, container escape, K8s RBAC,
  Falco, Trivy, kube-bench
- Offensive C2 (6): Sliver, Havoc, NetExec, DPAPI, NTLM relay ESC8, redirectors
- DFIR (6): Hayabusa, Chainsaw, KAPE, Velociraptor, EZ Tools, Plaso
- Backfill (4): OpenCTI, MISP, honeytokens, post-quantum crypto migration

Each skill follows the repo taxonomy (SKILL.md + references/{standards,api-reference}.md
+ scripts/agent.py + LICENSE), with researched real tool commands (no placeholders),
complete frontmatter, and ATT&CK/ATLAS + NIST CSF mappings. Updates README domain
table, skill count, and index.json.
2026-06-22 19:08:16 +02:00

160 lines
5.3 KiB
Python

#!/usr/bin/env python3
"""
SBOM generation + vulnerability correlation helper.
Wraps Syft (SBOM generation), Grype (vulnerability scanning), and Cosign
(attestation) with real flags. Can run the full pipeline (generate -> scan ->
optionally attest) and summarize Grype JSON by severity for CI gating.
References:
https://github.com/anchore/syft
https://github.com/anchore/grype
https://github.com/sigstore/cosign
"""
import argparse
import json
import shutil
import subprocess
import sys
from collections import Counter
SEVERITY_ORDER = ["negligible", "low", "medium", "high", "critical"]
def require(tool):
path = shutil.which(tool)
if not path:
print(f"[!] '{tool}' not found on PATH", file=sys.stderr)
return path
def run(cmd, dry_run, capture=False):
print("[*] " + " ".join(cmd), file=sys.stderr)
if dry_run:
return 0, ""
try:
if capture:
proc = subprocess.run(cmd, check=False, text=True, capture_output=True)
if proc.returncode != 0 and proc.stderr:
print(proc.stderr, file=sys.stderr)
return proc.returncode, proc.stdout
return subprocess.run(cmd, check=False).returncode, ""
except FileNotFoundError:
print(f"[!] command not found: {cmd[0]}", file=sys.stderr)
return 127, ""
def syft_generate(source, fmt, outfile, dry_run):
syft = require("syft") or "syft"
cmd = [syft, source, "-o", f"{fmt}={outfile}"]
rc, _ = run(cmd, dry_run)
return rc
def grype_scan(sbom_path, out_json, fail_on=None, only_fixed=False, dry_run=False):
grype = require("grype") or "grype"
cmd = [grype, f"sbom:{sbom_path}", "-o", "json"]
if only_fixed:
cmd.append("--only-fixed")
if fail_on:
cmd += ["--fail-on", fail_on]
rc, out = run(cmd, dry_run, capture=True)
if out and not dry_run:
with open(out_json, "w", encoding="utf-8") as fh:
fh.write(out)
return rc, out
def summarize(grype_json_text):
"""Count vulnerabilities by severity from Grype JSON output."""
try:
data = json.loads(grype_json_text)
except (json.JSONDecodeError, TypeError):
print("[!] could not parse Grype JSON", file=sys.stderr)
return Counter()
counts = Counter()
for match in data.get("matches", []):
sev = (match.get("vulnerability", {})
.get("severity", "Unknown")).lower()
counts[sev] += 1
return counts
def print_summary(counts):
total = sum(counts.values())
print(f"[+] Total vulnerabilities: {total}")
for sev in reversed(SEVERITY_ORDER):
if counts.get(sev):
print(f" {sev:>10}: {counts[sev]}")
for sev, n in counts.items():
if sev not in SEVERITY_ORDER:
print(f" {sev:>10}: {n}")
def cosign_attest(image, predicate, attest_type, key, dry_run):
cosign = require("cosign") or "cosign"
cmd = [cosign, "attest", "--predicate", predicate, "--type", attest_type, image]
if key:
cmd[2:2] = ["--key", key] # insert after 'attest'
rc, _ = run(cmd, dry_run)
return rc
def main():
p = argparse.ArgumentParser(description="SBOM generate/scan/attest helper")
p.add_argument("--dry-run", action="store_true")
sub = p.add_subparsers(dest="cmd", required=True)
g = sub.add_parser("generate", help="Generate an SBOM with Syft")
g.add_argument("--source", required=True, help="image, dir:., file:..., etc.")
g.add_argument("--format", default="cyclonedx-json")
g.add_argument("--out", required=True)
s = sub.add_parser("scan", help="Scan an SBOM with Grype")
s.add_argument("--sbom", required=True)
s.add_argument("--out", default="vulns.json")
s.add_argument("--fail-on")
s.add_argument("--only-fixed", action="store_true")
pl = sub.add_parser("pipeline", help="generate -> scan -> summarize (+attest)")
pl.add_argument("--source", required=True)
pl.add_argument("--format", default="cyclonedx-json")
pl.add_argument("--sbom-out", default="sbom.json")
pl.add_argument("--vulns-out", default="vulns.json")
pl.add_argument("--fail-on")
pl.add_argument("--only-fixed", action="store_true")
pl.add_argument("--attest-image", help="If set, attest the SBOM to this image")
pl.add_argument("--attest-type", default="cyclonedx")
pl.add_argument("--cosign-key")
args = p.parse_args()
if args.cmd == "generate":
return syft_generate(args.source, args.format, args.out, args.dry_run)
if args.cmd == "scan":
rc, out = grype_scan(args.sbom, args.out, args.fail_on,
args.only_fixed, args.dry_run)
if out:
print_summary(summarize(out))
return rc
if args.cmd == "pipeline":
rc = syft_generate(args.source, args.format, args.sbom_out, args.dry_run)
if rc not in (0,) and not args.dry_run:
print("[!] SBOM generation failed", file=sys.stderr)
return rc
scan_rc, out = grype_scan(args.sbom_out, args.vulns_out, args.fail_on,
args.only_fixed, args.dry_run)
if out:
print_summary(summarize(out))
if args.attest_image:
cosign_attest(args.attest_image, args.sbom_out, args.attest_type,
args.cosign_key, args.dry_run)
return scan_rc
return 2
if __name__ == "__main__":
sys.exit(main())