Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Perform security risk analysis on Kubernetes resource manifests using Kubesec to identify misconfigurations, privilege escalation risks, and deviations from security best practices.
.claude/skills/scanning-kubernetes-manifests-with-kubesec/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-07 | ✗→✓ | ▲ Improved | — | — |
| case-18 | ✗→✓ | ▲ Improved | — | — |
| case-09 | ✗→✓ | ▲ Improved | — | — |
| case-05 | ✗→✓ | ▲ Improved | — | — |
| case-10 | ✗→✓ | ▲ Improved | — | — |
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.
Kubesec assigns a score to each Kubernetes resource based on security checks:
bash# 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 version
bashdocker pull kubesec/kubesec:v2 # Scan a manifest file docker run -i kubesec/kubesec:v2 scan /dev/stdin < deployment.yaml
bashkubectl krew install kubesec-scan kubectl kubesec-scan pod mypod -n default
bash# 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 -
json[ { "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 } ] } } ]
bash# 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.yaml
bash# 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/scan
yamlname: 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 $FAIL
yamlkubesec-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/
Deploy Kubesec as a ValidatingWebhookConfiguration to reject insecure manifests at deploy time:
yamlapiVersion: 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"]
| 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 |
| 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 |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted, and 21 counted toward the lift figure. The other 1 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +45 percentage points is the difference between those two pass rates over the 21 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.