Hardening Kubernetes · Part 1

5 tips to improve the security of your Kubernetes infrastructure

In the light of the Hugging Face hack, AI agents have gotten all the limelight and attention — but the attack was easily preventable if the basic Kubernetes hardenings had been in place. The uncomfortable truth about most cluster compromises is that they don't hinge on exotic zero-days; they hinge on defaults that were never tightened.

Here are five hardening steps we consider table stakes. Each one closes a door that attackers reach for early, and every one of them is applied by default on ModStack-managed clusters. This post kicks off our Hardening Kubernetes series with the overview — each part that follows digs into one of these tips in production-level depth.

1. Disable automounting of service account tokens

By default, every pod gets a service account token mounted at a well-known path. If an attacker lands in any container — through a dependency exploit, a leaked credential, a compromised model file — that token is their first pivot: it's a ready-made credential for talking to the Kubernetes API. Most workloads never need it.

yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
automountServiceAccountToken: false

Set it on the service account (or per pod spec) and grant the token back explicitly only to the rare workload that genuinely talks to the API server.

2. Use an admission controller — we recommend Kyverno

Everything above relies on people remembering to write the right YAML. An admission controller makes the cluster enforce it instead: every object is validated against policy before it's admitted, so an insecure manifest can't even be created. We recommend Kyverno — policies are plain Kubernetes resources, no new language to learn.

yaml
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: deny-privileged-containers
spec:
  validationFailureAction: Enforce
  rules:
    - name: no-privileged
      match:
        any:
        - resources:
            kinds: [Pod]
      validate:
        message: "Privileged containers are not allowed."
        pattern:
          spec:
            containers:
            - securityContext:
                privileged: "false"

Start with the Pod Security Standards policies, then layer on rules that encode your own conventions — required labels, banned registries, mandatory resource limits.

3. Configure system call profiles

Containers share the host kernel, and the kernel's syscall surface is where container escapes live. A seccomp profile restricts which syscalls a container may make. We restrict every workload to RuntimeDefault — the container runtime's curated profile, which blocks the obscure syscalls that most kernel exploits depend on while remaining compatible with virtually all application workloads.

yaml
securityContext:
  seccompProfile:
    type: RuntimeDefault
  allowPrivilegeEscalation: false

Pair it with your admission controller from tip 2 so a pod without a seccomp profile simply never schedules.

4. Use a network provider like Cilium

Flat pod networks are an attacker's best friend: land anywhere, reach everything — including the Kubernetes API server. A CNI like Cilium gives you identity-aware network policies, which lets you restrict which workloads can reach the K8s API server at all, on top of standard pod-to-pod segmentation.

yaml
# Only pods labelled api-client may talk to the API server
apiVersion: cilium.io/v2
kind: CiliumClusterwideNetworkPolicy
metadata:
  name: restrict-kube-apiserver
spec:
  endpointSelector: {}
  egressDeny:
    - toEntities: [kube-apiserver]
  enableDefaultDeny:
    egress: false

Combined with tip 1, this defuses the classic escalation chain: even a pod that somehow holds a token can't open a connection to spend it.

5. Scan for vulnerabilities continuously

The four tips above shrink the blast radius of a compromise; scanning shrinks the odds of one happening. Every image you run carries a dependency tree you didn't write, and new CVEs land in it weekly. We use AWS's vulnerability scanning (Amazon Inspector, wired into ECR) so every pushed image is scanned automatically and re-evaluated as new CVEs are published — findings surface per service, worst-severity first, before and after the image ships.

Scanning only pays off if someone looks at the results. Put the worst finding on the same dashboard people already use daily — a CVE report nobody opens is indistinguishable from no scanner at all.

The takeaway

None of these five require heroics — they're configuration, not architecture. That's precisely the point of the Hugging Face lesson: the gap between "compromised" and "contained" was a handful of YAML defaults.

If you'd rather not maintain that YAML yourself: ModStack applies every one of these hardenings — token automount off, Kyverno policies, RuntimeDefault seccomp, Cilium networking, and continuous image scanning — to every environment it provisions, out of the box.

Hardened by default

Every ModStack environment ships with these five protections already on. Spend your time on your product, not your YAML.

Get Started