npx skills add mukul975/Anthropic-Cybersecurity-SkillsMITRE ATT&CK
Overview
Kubesec is an open-source security risk analysis tool developed by ControlPlane that inspects Kubernetes resource manifests for common exploitable risks such as privilege escalation, writable host mounts, and excessive capabilities. It assigns a numerical security score to each resource and provides actionable recommendations for hardening. Kubesec can be used as a CLI binary, Docker container, kubectl plugin, admission webhook, or REST API endpoint.
When to Use
- When conducting security assessments that involve scanning kubernetes manifests with kubesec
- When following incident response procedures for related security events
- When performing scheduled security testing or auditing activities
- When validating security controls through hands-on testing
Prerequisites
- Kubernetes manifest files (YAML/JSON) for Deployments, Pods, DaemonSets, StatefulSets
- Docker or Go runtime for local installation
- kubectl access for scanning live cluster resources
- CI/CD pipeline access for automated scanning integration
Core Concepts
Security Scoring System
Kubesec assigns a score to each Kubernetes resource based on security checks:
- Positive scores: Awarded for security-enhancing configurations (readOnlyRootFilesystem, runAsNonRoot)
- Zero or negative scores: Indicate missing security controls or dangerous configurations
- Critical advisories: Flagged configurations that represent immediate security risks
Check Categories
- Privilege Controls: Checks for privileged containers, host PID/network access, root execution
- Capabilities: Identifies excessive Linux capabilities (SYS_ADMIN, NET_RAW)
- Volume Mounts: Detects dangerous host path mounts and writable sensitive paths
- Resource Limits: Validates presence of CPU/memory resource constraints
- Security Context: Verifies seccomp profiles, AppArmor annotations, SELinux contexts
Installation
Binary Installation
# Linux/macOS
curl -sSL https://github.com/controlplaneio/kubesec/releases/latest/download/kubesec_linux_amd64.tar.gz | \
tar xz -C /usr/local/bin/ kubesec
# Verify installation
kubesec versionDocker Installation
docker pull kubesec/kubesec:v2
# Scan a manifest file
docker run -i kubesec/kubesec:v2 scan /dev/stdin < deployment.yamlkubectl Plugin
kubectl krew install kubesec-scan
kubectl kubesec-scan pod mypod -n defaultPractical Scanning
Scanning a Single Manifest
# Scan a deployment manifest
kubesec scan deployment.yaml
# Scan with JSON output
kubesec scan -o json deployment.yaml
# Scan from stdin
cat pod.yaml | kubesec scan -Sample Output
[
{
"object": "Pod/web-app.default",
"valid": true,
"fileName": "pod.yaml",
"message": "Passed with a score of 3 points",
"score": 3,
"scoring": {
"passed": [
{
"id": "ReadOnlyRootFilesystem",
"selector": "containers[] .securityContext .readOnlyRootFilesystem == true",
"reason": "An immutable root filesystem prevents applications from writing to their local disk",
"points": 1
},
{
"id": "RunAsNonRoot",
"selector": "containers[] .securityContext .runAsNonRoot == true",
"reason": "Force the running image to run as a non-root user",
"points": 1
},
{
"id": "LimitsCPU",
"selector": "containers[] .resources .limits .cpu",
"reason": "Enforcing CPU limits prevents DOS via resource exhaustion",
"points": 1
}
],
"advise": [
{
"id": "ApparmorAny",
"selector": "metadata .annotations .\"container.apparmor.security.beta.kubernetes.io/nginx\"",
"reason": "Well defined AppArmor policies reduce the attack surface of the container",
"points": 3
},
{
"id": "ServiceAccountName",
"selector": ".spec .serviceAccountName",
"reason": "Service accounts restrict Kubernetes API access and should be configured",
"points": 3
}
]
}
}
]Scanning Multiple Resources
# Scan all YAML files in a directory
for file in manifests/*.yaml; do
echo "=== Scanning $file ==="
kubesec scan "$file"
done
# Scan multi-document YAML
kubesec scan multi-resource.yamlUsing the HTTP API
# Scan via the public API
curl -sSX POST --data-binary @deployment.yaml \
https://v2.kubesec.io/scan
# Run a local API server
kubesec http --port 8080 &
# Scan against local server
curl -sSX POST --data-binary @deployment.yaml \
http://localhost:8080/scanCI/CD Integration
GitHub Actions
name: Kubesec Scan
on: [pull_request]
jobs:
kubesec:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Install Kubesec
run: |
curl -sSL https://github.com/controlplaneio/kubesec/releases/latest/download/kubesec_linux_amd64.tar.gz | \
tar xz -C /usr/local/bin/ kubesec
- name: Scan Manifests
run: |
FAIL=0
for file in k8s/*.yaml; do
SCORE=$(kubesec scan "$file" | jq '.[0].score')
echo "$file: score=$SCORE"
if [ "$SCORE" -lt 0 ]; then
echo "FAIL: $file has critical issues (score: $SCORE)"
FAIL=1
fi
done
exit $FAILGitLab CI
kubesec-scan:
stage: security
image: kubesec/kubesec:v2
script:
- |
for file in k8s/*.yaml; do
kubesec scan "$file" > /tmp/result.json
SCORE=$(cat /tmp/result.json | jq '.[0].score')
if [ "$SCORE" -lt 0 ]; then
echo "CRITICAL: $file scored $SCORE"
cat /tmp/result.json | jq '.[0].scoring.critical'
exit 1
fi
done
artifacts:
paths:
- kubesec-results/Admission Webhook
Deploy Kubesec as a ValidatingWebhookConfiguration to reject insecure manifests at deploy time:
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingWebhookConfiguration
metadata:
name: kubesec-webhook
webhooks:
- name: kubesec.controlplane.io
rules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments", "daemonsets", "statefulsets"]
clientConfig:
service:
name: kubesec-webhook
namespace: kube-system
path: /scan
failurePolicy: Fail
sideEffects: None
admissionReviewVersions: ["v1"]Security Checks Reference
Critical Checks (Negative Score)
| Check | Selector | Risk |
|---|---|---|
| Privileged | securityContext.privileged == true |
Full host access |
| HostPID | spec.hostPID == true |
Process namespace escape |
| HostNetwork | spec.hostNetwork == true |
Network namespace escape |
| SYS_ADMIN | capabilities.add contains SYS_ADMIN |
Near-root capability |
Best Practice Checks (Positive Score)
| Check | Points | Description |
|---|---|---|
| ReadOnlyRootFilesystem | +1 | Prevents filesystem writes |
| RunAsNonRoot | +1 | Non-root process execution |
| RunAsUser > 10000 | +1 | High UID reduces collision risk |
| LimitsCPU | +1 | Prevents CPU resource exhaustion |
| LimitsMemory | +1 | Prevents memory resource exhaustion |
| RequestsCPU | +1 | Ensures scheduler resource awareness |
| ServiceAccountName | +3 | Explicit service account |
| AppArmor annotation | +3 | Kernel-level MAC enforcement |
| Seccomp profile | +4 | Syscall filtering |
References
References and resources
Everything below is rendered for inspection. Script files are read-only and never run.
References 3
api-reference.md1.8 KB
API Reference: Scanning Kubernetes Manifests with Kubesec
Kubesec CLI Commands
| Command | Description |
|---|---|
kubesec scan <file> |
Scan a manifest file |
kubesec scan -o json <file> |
JSON output |
kubesec http --port 8080 |
Start local API server |
kubesec version |
Show version info |
Kubesec HTTP API
| Method | Endpoint | Description |
|---|---|---|
| POST | https://v2.kubesec.io/scan |
Public scan API |
| POST | http://localhost:8080/scan |
Local scan API |
Critical Checks (Negative Score)
| Check | Selector | Risk |
|---|---|---|
| Privileged | securityContext.privileged == true |
Full host access |
| HostPID | spec.hostPID == true |
Process namespace escape |
| HostNetwork | spec.hostNetwork == true |
Network namespace escape |
| SYS_ADMIN | capabilities.add contains SYS_ADMIN |
Near-root capability |
Best Practice Checks (Positive Score)
| Check | Points | Description |
|---|---|---|
| ReadOnlyRootFilesystem | +1 | Prevents filesystem writes |
| RunAsNonRoot | +1 | Non-root execution |
| RunAsUser > 10000 | +1 | High UID |
| LimitsCPU | +1 | CPU limits set |
| LimitsMemory | +1 | Memory limits set |
| ServiceAccountName | +3 | Explicit service account |
| AppArmor annotation | +3 | MAC enforcement |
| Seccomp profile | +4 | Syscall filtering |
Python Libraries
| Library | Version | Purpose |
|---|---|---|
subprocess |
stdlib | Execute kubesec CLI |
requests |
>=2.28 | HTTP API fallback |
json |
stdlib | Parse scan results |
References
- Kubesec GitHub: https://github.com/controlplaneio/kubesec
- Kubesec Online: https://kubesec.io/
- CIS Kubernetes Benchmark: https://www.cisecurity.org/benchmark/kubernetes
standards.md1.8 KB
Standards and References - Kubesec Manifest Scanning
Industry Standards
CIS Kubernetes Benchmark v1.9
- Section 5.2: Pod Security Standards -- Kubesec validates privileged mode, host namespaces
- Section 5.7: General Policies -- Service account configuration, resource limits
- Maps directly to kubesec scoring checks for container security contexts
NIST SP 800-190: Application Container Security Guide
- Section 3.1: Image vulnerabilities and configuration defects
- Section 3.4: Orchestrator security -- manifest validation before deployment
- Section 4.1: Countermeasures for image vulnerabilities
Kubernetes Pod Security Standards (PSS)
- Privileged: No restrictions (kubesec score = lowest)
- Baseline: Prevents known privilege escalation (kubesec validates hostPID, hostNetwork, privileged)
- Restricted: Best practices enforcement (kubesec validates all recommended controls)
Compliance Mapping
| Kubesec Check | CIS Control | NIST 800-190 | PCI DSS |
|---|---|---|---|
| Privileged containers | 5.2.1 | 3.4.4 | 2.2 |
| Host PID namespace | 5.2.2 | 3.4.2 | 2.2 |
| Host network | 5.2.4 | 3.4.3 | 1.3 |
| Root execution | 5.2.6 | 3.4.1 | 7.1 |
| ReadOnlyRootFilesystem | 5.2.8 | 4.1.2 | 2.2 |
| Resource limits | 5.4.1 | 4.3.1 | 2.2 |
| Service accounts | 5.1.5 | 3.4.5 | 7.2 |
Tool Ecosystem
Complementary Scanning Tools
- Kubescape: NSA/CISA framework compliance scanning
- Checkov: Infrastructure-as-code security scanning (covers Kubernetes)
- Datree: Policy enforcement with custom rules
- OPA/Gatekeeper: Runtime policy enforcement as admission controller
Integration Points
- Pre-commit hooks for developer feedback
- CI/CD pipeline gates to prevent insecure deployments
- Admission webhooks for runtime enforcement
- IDE plugins for shift-left security
workflows.md2.0 KB
Workflows - Kubesec Manifest Scanning
Scanning Workflow
Pre-Commit Scanning
- Developer writes Kubernetes manifest locally
- Pre-commit hook runs
kubesec scanon changed YAML files - If score < 0 (critical issues), commit is blocked with remediation guidance
- Developer fixes issues and retries commit
CI/CD Pipeline Integration
- Pull request created with manifest changes
- CI job runs kubesec scan on all manifests in PR
- Results posted as PR comment with score breakdown
- Gate: PR blocked if any manifest scores below threshold
- Merge allowed only after all manifests pass minimum score
Admission Control
- Developer applies manifest via kubectl or GitOps
- ValidatingWebhook intercepts the API request
- Kubesec webhook scans the manifest in real-time
- If critical issues found, admission is denied with explanation
- Clean manifests are admitted to the cluster
Remediation Workflow
Scoring Improvement Process
1. Run kubesec scan on target manifest
2. Review "advise" section for point-earning improvements
3. Review "critical" section for must-fix issues
4. Apply fixes in priority order:
a. Remove critical issues (privileged, hostPID, hostNetwork)
b. Add seccomp profile (+4 points)
c. Add AppArmor annotation (+3 points)
d. Set readOnlyRootFilesystem (+1 point)
e. Set runAsNonRoot (+1 point)
f. Add resource limits (+1 point each)
5. Re-scan to verify improved score
6. Commit and push hardened manifestContinuous Monitoring Workflow
Scheduled Cluster Scanning
- CronJob runs daily scan of all deployed resources
- Extracts manifests from live cluster:
kubectl get deploy -o yaml - Runs kubesec scan on each resource
- Compares scores against previous scan results
- Alerts on score regressions or new critical findings
- Generates weekly security posture report
Score Trending
Week 1: Average score 2.3 (baseline)
Week 2: Average score 3.1 (+0.8 improvement)
Week 3: Average score 4.5 (+1.4 improvement)
Week 4: Average score 5.2 (+0.7 improvement -- target: 6.0)Scripts 2
agent.py5.7 KB
#!/usr/bin/env python3
"""Agent for scanning Kubernetes manifests with Kubesec.
Runs Kubesec security risk analysis on K8s manifests, evaluates
security scores, identifies critical misconfigurations, and
enforces security baselines in CI/CD pipelines.
"""
import json
import subprocess
import sys
from pathlib import Path
from datetime import datetime
class KubesecScanAgent:
"""Scans Kubernetes manifests using Kubesec for security risks."""
def __init__(self, output_dir="./kubesec_scan"):
self.output_dir = Path(output_dir)
self.output_dir.mkdir(parents=True, exist_ok=True)
self.scan_results = []
def scan_manifest(self, manifest_path):
"""Scan a single Kubernetes manifest file."""
cmd = ["kubesec", "scan", manifest_path]
try:
result = subprocess.run(cmd, capture_output=True, text=True, timeout=30)
parsed = json.loads(result.stdout) if result.stdout.strip() else []
except FileNotFoundError:
return self._scan_via_api(manifest_path)
except (subprocess.TimeoutExpired, json.JSONDecodeError):
return {"error": "Kubesec scan failed", "file": manifest_path}
findings = []
for item in parsed:
findings.append({
"object": item.get("object", ""),
"score": item.get("score", 0),
"message": item.get("message", ""),
"passed": [p.get("id") for p in item.get("scoring", {}).get("passed", [])],
"advise": [
{"id": a.get("id"), "reason": a.get("reason"), "points": a.get("points")}
for a in item.get("scoring", {}).get("advise", [])
],
"critical": [
{"id": c.get("id"), "reason": c.get("reason")}
for c in item.get("scoring", {}).get("critical", [])
],
})
scan = {
"file": manifest_path,
"scan_date": datetime.utcnow().isoformat(),
"findings": findings,
}
self.scan_results.append(scan)
return scan
def _scan_via_api(self, manifest_path):
"""Fallback: scan via Kubesec public HTTP API."""
try:
import requests
with open(manifest_path, "rb") as f:
resp = requests.post("https://v2.kubesec.io/scan",
data=f.read(), timeout=30)
parsed = resp.json()
findings = []
for item in parsed:
findings.append({
"object": item.get("object", ""),
"score": item.get("score", 0),
"message": item.get("message", ""),
"passed": [p.get("id") for p in item.get("scoring", {}).get("passed", [])],
"advise": [
{"id": a.get("id"), "reason": a.get("reason"), "points": a.get("points")}
for a in item.get("scoring", {}).get("advise", [])
],
"critical": [
{"id": c.get("id"), "reason": c.get("reason")}
for c in item.get("scoring", {}).get("critical", [])
],
})
scan = {"file": manifest_path, "scan_date": datetime.utcnow().isoformat(), "findings": findings}
self.scan_results.append(scan)
return scan
except Exception as e:
return {"error": f"API scan failed: {e}", "file": manifest_path}
def scan_directory(self, dir_path):
"""Scan all YAML files in a directory."""
p = Path(dir_path)
results = []
for f in sorted(p.glob("*.yaml")) + sorted(p.glob("*.yml")):
results.append(self.scan_manifest(str(f)))
return results
def enforce_score_threshold(self, min_score=0):
"""Check if any manifests fail the minimum score threshold."""
failures = []
for scan in self.scan_results:
for finding in scan.get("findings", []):
if finding.get("score", 0) < min_score:
failures.append({
"file": scan["file"],
"object": finding["object"],
"score": finding["score"],
})
if finding.get("critical"):
failures.append({
"file": scan["file"],
"object": finding["object"],
"critical_issues": finding["critical"],
})
return {"gate": "FAILED" if failures else "PASSED", "failures": failures}
def generate_report(self):
gate = self.enforce_score_threshold(min_score=0)
report = {
"report_date": datetime.utcnow().isoformat(),
"manifests_scanned": len(self.scan_results),
"scans": self.scan_results,
"quality_gate": gate,
}
out = self.output_dir / "kubesec_report.json"
with open(out, "w") as f:
json.dump(report, f, indent=2)
print(json.dumps(report, indent=2))
return report
def main():
if len(sys.argv) < 2:
print("Usage: agent.py <manifest.yaml|directory> [--min-score 0]")
sys.exit(1)
target = sys.argv[1]
min_score = 0
if "--min-score" in sys.argv:
min_score = int(sys.argv[sys.argv.index("--min-score") + 1])
agent = KubesecScanAgent()
if Path(target).is_dir():
agent.scan_directory(target)
else:
agent.scan_manifest(target)
report = agent.generate_report()
gate = agent.enforce_score_threshold(min_score)
if gate["gate"] == "FAILED":
sys.exit(1)
if __name__ == "__main__":
main()
process.py8.3 KB
#!/usr/bin/env python3
"""
Kubesec Manifest Scanner Automation
Scans Kubernetes manifests using Kubesec, aggregates results,
and generates security posture reports with remediation guidance.
"""
import json
import subprocess
import sys
import argparse
from pathlib import Path
from collections import defaultdict
MINIMUM_SCORE_THRESHOLD = 0
RECOMMENDED_SCORE_THRESHOLD = 5
def scan_manifest_with_kubesec(file_path: str, kubesec_url: str = "") -> list[dict]:
"""Scan a single manifest file using kubesec."""
path = Path(file_path)
if not path.exists():
print(f"[ERROR] File not found: {file_path}")
return []
try:
if kubesec_url:
result = subprocess.run(
["curl", "-sSX", "POST", "--data-binary", f"@{file_path}", f"{kubesec_url}/scan"],
capture_output=True, text=True, timeout=30
)
else:
result = subprocess.run(
["kubesec", "scan", file_path],
capture_output=True, text=True, timeout=30
)
if result.returncode != 0:
print(f"[ERROR] Scan failed for {file_path}: {result.stderr.strip()}")
return []
return json.loads(result.stdout)
except FileNotFoundError:
print("[ERROR] kubesec binary not found. Install from https://github.com/controlplaneio/kubesec")
return []
except json.JSONDecodeError:
print(f"[ERROR] Invalid JSON output from kubesec for {file_path}")
return []
except subprocess.TimeoutExpired:
print(f"[ERROR] Timeout scanning {file_path}")
return []
def scan_directory(directory: str, kubesec_url: str = "") -> list[dict]:
"""Scan all YAML/JSON files in a directory."""
dir_path = Path(directory)
if not dir_path.is_dir():
print(f"[ERROR] Directory not found: {directory}")
return []
all_results = []
yaml_files = list(dir_path.glob("**/*.yaml")) + list(dir_path.glob("**/*.yml")) + list(dir_path.glob("**/*.json"))
for manifest_file in sorted(yaml_files):
print(f"[INFO] Scanning {manifest_file}...")
results = scan_manifest_with_kubesec(str(manifest_file), kubesec_url)
for r in results:
r["source_file"] = str(manifest_file)
all_results.extend(results)
return all_results
def categorize_findings(results: list[dict]) -> dict:
"""Categorize scan findings by severity."""
categories = {
"critical": [],
"warning": [],
"info": [],
"passed": []
}
for result in results:
score = result.get("score", 0)
obj = result.get("object", "unknown")
source = result.get("source_file", "unknown")
scoring = result.get("scoring", {})
entry = {
"object": obj,
"source_file": source,
"score": score,
"critical": scoring.get("critical", []),
"advise": scoring.get("advise", []),
"passed": scoring.get("passed", [])
}
if score < 0:
categories["critical"].append(entry)
elif score < RECOMMENDED_SCORE_THRESHOLD:
categories["warning"].append(entry)
else:
categories["passed"].append(entry)
return categories
def generate_remediation(result: dict) -> list[str]:
"""Generate remediation recommendations for a scan result."""
remediations = []
scoring = result.get("scoring", {})
for critical in scoring.get("critical", []):
check_id = critical.get("id", "")
selector = critical.get("selector", "")
reason = critical.get("reason", "")
remediations.append(f"[CRITICAL] {check_id}: {reason} (fix: {selector})")
for advise in scoring.get("advise", []):
check_id = advise.get("id", "")
reason = advise.get("reason", "")
points = advise.get("points", 0)
remediations.append(f"[ADVISE +{points}pts] {check_id}: {reason}")
return remediations
def generate_report(results: list[dict], output_format: str = "text") -> str:
"""Generate a comprehensive scanning report."""
categories = categorize_findings(results)
if output_format == "json":
report = {
"total_resources_scanned": len(results),
"critical_count": len(categories["critical"]),
"warning_count": len(categories["warning"]),
"passed_count": len(categories["passed"]),
"results": results,
"categories": categories
}
return json.dumps(report, indent=2)
lines = []
lines.append("=" * 70)
lines.append("KUBESEC MANIFEST SECURITY SCAN REPORT")
lines.append("=" * 70)
lines.append(f"\nTotal Resources Scanned: {len(results)}")
lines.append(f" Critical (score < 0): {len(categories['critical'])}")
lines.append(f" Warning (score < {RECOMMENDED_SCORE_THRESHOLD}): {len(categories['warning'])}")
lines.append(f" Passed (score >= {RECOMMENDED_SCORE_THRESHOLD}): {len(categories['passed'])}")
if results:
scores = [r.get("score", 0) for r in results]
lines.append(f"\nScore Statistics:")
lines.append(f" Average: {sum(scores) / len(scores):.1f}")
lines.append(f" Min: {min(scores)}")
lines.append(f" Max: {max(scores)}")
if categories["critical"]:
lines.append(f"\n{'=' * 50}")
lines.append("CRITICAL FINDINGS")
lines.append(f"{'=' * 50}")
for item in categories["critical"]:
lines.append(f"\n Resource: {item['object']}")
lines.append(f" File: {item['source_file']}")
lines.append(f" Score: {item['score']}")
for crit in item["critical"]:
lines.append(f" [CRITICAL] {crit.get('id', '')}: {crit.get('reason', '')}")
if categories["warning"]:
lines.append(f"\n{'=' * 50}")
lines.append("WARNINGS")
lines.append(f"{'=' * 50}")
for item in categories["warning"]:
lines.append(f"\n Resource: {item['object']}")
lines.append(f" File: {item['source_file']}")
lines.append(f" Score: {item['score']}")
for adv in item["advise"][:5]:
lines.append(f" [ADVISE +{adv.get('points', 0)}] {adv.get('id', '')}: {adv.get('reason', '')}")
lines.append(f"\n{'=' * 50}")
lines.append("REMEDIATION SUMMARY")
lines.append(f"{'=' * 50}")
advise_counts = defaultdict(int)
for result in results:
for adv in result.get("scoring", {}).get("advise", []):
advise_counts[adv.get("id", "")] += 1
lines.append("\nMost Common Missing Controls:")
for check, count in sorted(advise_counts.items(), key=lambda x: -x[1])[:10]:
lines.append(f" {check}: missing in {count}/{len(results)} resources")
gate_pass = all(r.get("score", 0) >= MINIMUM_SCORE_THRESHOLD for r in results)
lines.append(f"\n{'=' * 50}")
lines.append(f"GATE RESULT: {'PASS' if gate_pass else 'FAIL'}")
lines.append(f"{'=' * 50}")
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(description="Kubesec Manifest Scanner Automation")
parser.add_argument("--file", help="Single manifest file to scan")
parser.add_argument("--directory", help="Directory containing manifests to scan")
parser.add_argument("--url", default="", help="Kubesec API URL (optional, uses local binary if not set)")
parser.add_argument("--format", choices=["text", "json"], default="text", help="Output format")
parser.add_argument("--threshold", type=int, default=0, help="Minimum passing score (default: 0)")
args = parser.parse_args()
global MINIMUM_SCORE_THRESHOLD
MINIMUM_SCORE_THRESHOLD = args.threshold
if not args.file and not args.directory:
print("[ERROR] Specify --file or --directory")
sys.exit(1)
results = []
if args.file:
results = scan_manifest_with_kubesec(args.file, args.url)
for r in results:
r["source_file"] = args.file
elif args.directory:
results = scan_directory(args.directory, args.url)
if not results:
print("[WARN] No scan results generated")
sys.exit(1)
report = generate_report(results, args.format)
print(report)
has_critical = any(r.get("score", 0) < MINIMUM_SCORE_THRESHOLD for r in results)
sys.exit(1 if has_critical else 0)
if __name__ == "__main__":
main()