kubernetes-security — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited kubernetes-security (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
A pragmatic baseline for a single Kubernetes cluster running a small-team workload. Skews toward "I have a cluster and need it to not be the cause of an incident" — not a full CIS Benchmark for regulated environments. Most managed-K8s providers ship sensible defaults at the control-plane layer; the data-plane (your workloads) is where the work is.
Before workload hardening, the cluster itself.
# What version? Is it supported?
kubectl version --short
# K8s has a ~14-month support window. Unsupported = unpatched CVEs.
# Who can reach the API?
kubectl cluster-info
# For managed clusters, check the provider's "API endpoint" access setting:
# - Private endpoint, restricted CIDRs, or behind a bastion is correct
# - "Public, open to 0.0.0.0/0" is a finding
# Audit log enabled?
# For EKS/GKE/AKS: check the cluster's "audit logging" / "control-plane logging" setting
# Self-hosted: --audit-log-path on the API serverPatterns:
--encryption-provider-config.Since K8s 1.25, Pod Security Admission is built-in. There are three policy levels:
| Level | What it allows | When to use |
|---|---|---|
| Privileged | Everything | System namespaces only (kube-system, ingress controllers that need it) |
| Baseline | No privileged escalation, no host networking, no hostPath | Default for most workloads |
| Restricted | The above plus: non-root user, read-only root filesystem, drop all capabilities, seccomp profile | Production application workloads |
Apply per namespace:
apiVersion: v1
kind: Namespace
metadata:
name: production
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: latest
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/audit: restrictedenforce rejects non-compliant pods. warn returns a warning to the user. audit logs to the audit log.
For workloads that cannot meet restricted (legacy containers needing root), use baseline and document why. Anything in privileged mode is a finding unless it's a known infrastructure component.
Common adjustments to make existing workloads restricted-compliant:
# In the pod spec
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
# Per container
containers:
- name: app
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: [ALL]
runAsNonRoot: trueAnything writable (cache, temp) goes to an emptyDir volume; the root filesystem stays read-only.
The single most common K8s misconfiguration: every workload running as the default ServiceAccount of its namespace, which often has more rights than the workload needs.
# What can the default ServiceAccount in 'production' do?
kubectl auth can-i --list --as=system:serviceaccount:production:default -n production
# What ServiceAccounts have cluster-admin or near-admin rights?
kubectl get clusterrolebindings -o json \
| jq -r '.items[] | select(.roleRef.name == "cluster-admin") | "\(.metadata.name) -> \(.subjects | map(.name) | join(","))"'Patterns:
default.ClusterRoleBinding should be rare and justified. verbs and ` resources* in Role rules.Example tight Role:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: app-reader
namespace: production
rules:
- apiGroups: [""]
resources: ["configmaps"]
resourceNames: ["app-config"]
verbs: ["get"]The resource name pin matters — verbs: [get] on all configmaps is too broad if the workload only reads one.
By default, every pod can talk to every other pod, in any namespace. That's a flat network — one compromised workload pivots freely.
# Default-deny ingress AND egress in a namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- EgressThen explicitly allow what's needed:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: app-allow
namespace: production
spec:
podSelector:
matchLabels:
app: web
policyTypes: [Ingress, Egress]
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- protocol: TCP
port: 5432
- to: # DNS
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53NetworkPolicy requires a compatible CNI (Calico, Cilium, Weave, Antrea, AWS VPC CNI with policy enabled). If the CNI ignores NetworkPolicy, the manifests are decoration.
K8s Secret objects are base64-encoded, not encrypted at the application level. At rest in etcd they are encrypted only if you configured encryption. They are still accessible to anyone with get secrets in the namespace.
Patterns, from worst to best:
kubectl describe pod, env-dumping logs, child processesStandard tooling:
For new clusters, prefer External Secrets + a cloud secret manager. SOPS is a good fit for GitOps-heavy teams.
Policy-as-code that blocks non-compliant resources at API time. Two common choices:
Baseline policies worth enforcing:
:latest (only digests or version tags allowed)runAsNonRoot: true (catches workloads that slipped past PSS)hostNetwork: true, hostPID: true, hostIPC: true outside of audited exceptions# Kyverno example — require resource limits
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-resources
spec:
validationFailureAction: enforce
rules:
- name: validate-resources
match:
any:
- resources:
kinds: [Pod]
validate:
message: "Resource limits are required."
pattern:
spec:
containers:
- resources:
limits:
memory: "?*"
cpu: "?*"docker-container-security.myapp@sha256:abc123... is immutable; myapp:v1.2.3 can be repushed.If multiple teams or customers share a cluster, hard isolation is hard. Options:
A single-cluster multi-tenant setup with shared nodes is soft isolation. Container escape and kernel CVEs can cross tenants. For security-sensitive multi-tenancy, run separate node pools per tenant or move to per-tenant clusters.
Walk this list against any cluster you're auditing:
0.0.0.0/0audit, not enforce)default ServiceAccountcluster-admin for human accounts or generic ServiceAccounts:latestrunAsUser: 0 or unspecified)privileged: true on application workloadshostNetwork: true or hostPath: volumes outside system podscluster-admin shared on multiple machines#!/usr/bin/env bash
echo "=== Cluster version ==="
kubectl version --short
echo "=== PSS labels per namespace ==="
kubectl get ns -o json | jq -r '.items[] | "\(.metadata.name)\t\(.metadata.labels // {} | to_entries | map(select(.key | startswith("pod-security.kubernetes.io"))) | from_entries)"'
echo "=== ServiceAccounts with cluster-admin ==="
kubectl get clusterrolebindings -o json | jq -r '.items[] | select(.roleRef.name == "cluster-admin") | "\(.metadata.name)\t\(.subjects // [] | map("\(.kind):\(.namespace // "-"):\(.name)") | join(", "))"'
echo "=== Namespaces without NetworkPolicy ==="
for ns in $(kubectl get ns -o jsonpath='{.items[*].metadata.name}'); do
count=$(kubectl get networkpolicy -n "$ns" 2>/dev/null | grep -c -v NAME)
[ "$count" -eq 0 ] && echo "$ns"
done
echo "=== Pods running as root ==="
kubectl get pods -A -o json | jq -r '.items[] | select(.spec.securityContext.runAsNonRoot != true and (.spec.containers[]?.securityContext.runAsNonRoot // false) != true) | "\(.metadata.namespace)/\(.metadata.name)"' | head -30
echo "=== Pods using :latest ==="
kubectl get pods -A -o json | jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name)\t\(.spec.containers[].image)"' | grep ':latest' | head -30restricted enforced in app namespacesautomountServiceAccountToken: false for workloads not calling K8s APIkind: Secret in env:latestprivileged, hostNetwork, hostPID, hostIPC in app workloadsprivileged: true or hostNetwork: true for application workloads~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.