Static analysis with Kubesec

Score manifests for risk before they ship.

Advanced10 min · lesson 19 of 24

Image scanning finds vulnerable packages inside the artifact; static analysis finds risky decisions in the manifest that ships it — a privileged container, a missing runAsNonRoot, host namespaces, an added SYS_ADMIN capability. These are configuration mistakes, invisible to a CVE scanner, and Kubesec is the tool that reads a manifest and scores it, telling you exactly which settings helped or hurt.

terminal
$ kubesec scan pod.yaml
[ { "object": "Pod/web.default",
"valid": true,
"score": -30,
"scoring": {
"critical": [
{ "selector": "containers[] .securityContext .privileged == true",
"reason": "Privileged containers can run any command as root" } ],
"advise": [
{ "selector": ".spec .securityContext .runAsNonRoot == true",
"reason": "Force the container to run as a non-root user" } ] } } ]

Read the score, then raise it

The report splits findings into critical (settings that actively endanger the node — privileged, hostNetwork, extra capabilities) and advise (hardening you have not added yet — runAsNonRoot, readOnlyRootFilesystem, dropped capabilities, resource limits). Removing the privileged flag and adding the security-context basics from the microservice-security lessons walks the score from negative into positive; the tool essentially grades that same checklist.

terminal
# after dropping privileged and adding runAsNonRoot + drop ALL + readOnlyRootFilesystem
$ kubesec scan pod.yaml | jq '.[].score'
7 # was -30; each hardening setting adds points

Gate the pipeline on it

Run Kubesec in CI and fail the merge below a threshold, the same way you gate image scans — it catches the privileged pod in review, before it is ever applied. It complements the other supply-chain controls rather than replacing them: image scanning reads the artifact, Kubesec reads how you ask to run it, and admission policy enforces at the cluster. Real coverage uses all three.

.gitlab-ci.yml
manifest_scan:
image: kubesec/kubesec:v2
script:
- |
score=$(kubesec scan k8s/pod.yaml | jq '.[0].score')
echo "kubesec score: $score"
[ "$score" -ge 5 ] || { echo "below threshold"; exit 1; }
A good score is not runtime proof
Static analysis reasons about the YAML, not the running workload — it cannot see what the image does at runtime, whether the setting is actually enforced by admission, or what the app writes once started. Treat the score as one gate among scanning, signing, and admission, not as a certificate of safety.