Every Kubernetes cluster we are handed has the same gap in it. The platform team has read the hardening guides, the compliance questionnaire has been answered, and somewhere in the cluster bootstrap there is a namespace label that enforces the restricted Pod Security Standard. Then the first real workload arrives as a Helm chart, admission control rejects it, and somebody adds an exemption “just for now”. Three years later the exemption list is longer than the policy.
We wanted to know how much of that is the charts’ fault. So we pulled the 600 most-starred Helm charts on Artifact Hub, rendered every one of them with nothing but its own default values, and checked every resulting pod against the restricted profile that Kubernetes has shipped as a stable feature since v1.25 in August 2022.
389 charts rendered into at least one workload. 66 of them can be installed, unmodified, into a namespace that enforces restricted. That is 17.0%.
TL;DR
- We rendered 389 of the 600 most-starred Helm charts with default values and tested 1,512 containers across 1,048 workloads against the Kubernetes
restrictedPod Security Standard. - 66 charts pass (17.0%). 227 charts fail all four of the controls that fail by omission rather than by misconfiguration.
- Seccomp is the single most-skipped field: 311 charts (79.9%) set no
seccompProfileanywhere. 28 charts are that one field away from passing. - 81.7% of charts clear the weaker
baselineprofile. The gap between 81.7% and 17.0% is the gap between “not obviously dangerous” and what your compliance framework actually asked for. - 87 of 266 failing application charts (32.7%) expose no way to fix it from
values.yaml. For seccomp specifically, only 14.0% of the charts that fail on it give you a values key to set. - Unrelated to policy but visible in the same render: 78.4% of containers ship with no CPU or memory limit at all.
What we actually ran
Artifact Hub exposes a free, unauthenticated search API. We took the top 600 Helm charts by stars, excluding deprecated packages, and for each one ran helm pull against the chart’s own repository at the version Artifact Hub lists as current, then helm template with Helm 3.19.0 and no value overrides whatsoever. No --set, no values file, no secrets. Whatever the chart author decided you get when you type helm install is what we measured.
131 charts could not be pulled: dead repository URLs, index files that have moved to OCI registries, and a handful of timeouts. 62 more pulled but refused to render, almost all of them legitimately, because they require a password, an encryption key or an access key before they will produce a manifest. Those are not failures of the chart; they are charts doing the right thing. 407 rendered cleanly, and 389 of those contained at least one pod-bearing workload.
From each rendered manifest we extracted every Deployment, StatefulSet, DaemonSet, Job, CronJob and bare Pod, then walked every container and init container inside them. We checked the six restricted-profile controls that apply to Linux pods: allowPrivilegeEscalation: false on every container, runAsNonRoot: true at pod or container level, runAsUser not set to 0, capabilities.drop including ALL, a seccompProfile.type of RuntimeDefault or Localhost, and the restricted volume type allowlist. We also recorded the baseline violations: privileged: true, host namespaces, hostPath volumes, host ports and capability additions beyond NET_BIND_SERVICE.
One correction we had to make mid-run, because it changes the numbers: our first pass counted Helm test hooks as workloads. It does not. A pod annotated helm.sh/hook: test is created by helm test, not by helm install, and 129 of them, across 96 charts, were quietly dragging charts into the failure column. Jenkins is the clean example. Its StatefulSet sets runAsNonRoot: true at pod level and allowPrivilegeEscalation: false on all four containers. Its UI test pod sets nothing. Excluding test hooks moved the pass count from 57 to 66 and moved Jenkins from “fails everything” to “fails on two fields”. Any audit of this kind that does not filter hooks is overstating its case.
The findings
| Measurement | Charts | Share |
|---|---|---|
| Rendered with at least one workload | 389 | 100% |
Pass restricted unmodified |
66 | 17.0% |
Pass baseline unmodified |
318 | 81.7% |
No seccompProfile on at least one container |
311 | 79.9% |
No capabilities.drop: [ALL] |
273 | 70.2% |
No allowPrivilegeEscalation: false |
267 | 68.6% |
No runAsNonRoot: true |
253 | 65.0% |
| Fail all four of the above | 227 | 58.4% |
| Mount a hostPath volume by default | 51 | 13.1% |
Set runAsUser: 0 explicitly |
36 | 9.3% |
Run a privileged: true container by default |
34 | 8.7% |
Finding 1: the pass list is short, and it is not random
The 66 charts that pass include cert-manager, Argo CD, ingress-nginx, Traefik, metrics-server, Harbor, External Secrets, Kyverno, CloudNativePG, KEDA, kube-state-metrics, oauth2-proxy, Kong and JupyterHub. Five of the eight highest-starred charts in the entire sample are in that list, which is the encouraging part of this data: at the very top of the ecosystem, hardened defaults are now normal.
Below the top tier it falls away fast. Among charts from unverified publishers the pass rate is 8.3%. Among verified publishers it is 20.9%, and among CNCF-hosted repositories 26.5%. Better, but a CNCF badge still means roughly a one-in-four chance that the chart installs cleanly under the policy that CNCF’s own documentation recommends.
Finding 2: baseline is not the bar anyone is being held to
318 of 389 charts (81.7%) pass the baseline profile. If you have ever seen a cluster security review conclude that “the workloads look fine”, this number is usually why. Baseline only fails on things somebody had to actively write: a privileged container, a host namespace, a hostPath mount.
Restricted fails on things nobody wrote at all. All four of its headline controls are fail-by-omission. A chart that has simply never thought about securityContext will pass baseline and fail restricted, and that describes 252 charts in this sample. ISO 27001 auditors, the CIS Kubernetes Benchmark’s Pod Security section and every cloud provider’s hardening guide point at restricted, not baseline. The ecosystem’s defaults are tuned for the profile nobody is being audited against.
Finding 3: seccomp is the whole story, and 28 charts are one line away
Of the four fail-by-omission controls, seccompProfile is missed most often, by 311 charts. More interesting is the tail. 33 charts fail on exactly one control, and for 29 of them that control is seccomp. 28 of those are otherwise completely clean: non-root, capabilities dropped, no privilege escalation, no host access, no disallowed volumes.
That list includes gitlab-runner, istiod, the Istio gateway chart, Argo Workflows, the Elastic ECK operator, descheduler, the Prometheus blackbox exporter, step-certificates and the VictoriaMetrics operator. Every one of them would pass restricted if a chart author added four lines:
securityContext:
seccompProfile:
type: RuntimeDefault
Seccomp has been in the restricted profile since Kubernetes v1.19. RuntimeDefault is the container runtime’s own profile and breaks almost nothing. This is the cheapest available improvement in the entire Helm ecosystem and it is sitting there unclaimed.
Finding 4: the node agents are a fair exception, and we separated them
Some charts genuinely need what restricted forbids. A CNI plugin, a CSI driver, a log shipper and a node exporter all have legitimate business touching the host. Of the 389 charts, 59 are node-level infrastructure by our definition: they ship a DaemonSet, or they use host networking, host PID or a hostPath mount. Only 2 of those 59 pass restricted, and that is largely correct behaviour. Cilium, Longhorn, Falco, the Ceph CSI drivers and node-problem-detector are supposed to be privileged.
Strip them out and you are left with 330 ordinary application charts: web apps, databases, queues, controllers, operators, dashboards. Things that have no business reading the host filesystem. 64 of them pass. 19.4%.
Removing the charts with a real excuse moves the number by two and a half percentage points. The excuse is not where the problem is.
Finding 5: a third of the time you cannot fix it from values.yaml
This is the finding that changes what you do on Monday. A permissive default is an annoyance if the chart lets you override it, and a fork if it does not.
For each of the 266 ordinary application charts that fail on at least one of the four controls, we searched the chart’s own values.yaml for any key naming seccompProfile, allowPrivilegeEscalation, runAsNonRoot or capabilities.
- 87 charts (32.7%) name none of the four. There is no documented value you can set.
- 34 charts (12.8%) do not contain the string
securityContextanywhere invalues.yaml. That list includes Rancher, Pi-hole, NATS, HashiCorp’s Terraform chart, Portainer, Milvus, StackGres and Telegraf. - Of the 258 charts failing on seccomp, only 36 (14.0%) expose a
seccompProfilekey at all. runAsNonRootis the best covered, named in 58.3% of failing charts’ values, which is its own indictment: the knob exists in the majority of cases and the default is still off.
Where there is no key, your options are a post-renderer such as Kustomize in the Helm pipeline, a mutating policy engine such as Kyverno or Gatekeeper patching pods on admission, or maintaining a fork. All three are real operational commitments that nobody costed into the sprint where the chart was chosen.
Finding 6: the same render shows almost nothing is bounded
While we had the manifests open we counted resource declarations. 1,185 of 1,512 containers (78.4%) ship with no CPU or memory limit, and 973 (64.4%) with no request either. Scheduling quality, noisy-neighbour behaviour and most of the surprise in a cloud bill start here. It is a different problem from Pod Security, but it has the same cause: the chart author cannot know your cluster, so they ship nothing, and nothing is what most people deploy.
What to do with this
- Render before you install.
helm templatewith your real values, piped into a policy check, costs seconds and tells you what admission control is going to say before it says it. Put it in CI, not in a runbook. - Enforce in a new namespace first, warn everywhere else. Pod Security Admission supports
warnandauditlabels alongsideenforce. Turn on warn across the cluster today and read the logs for a fortnight. You will find out which of your charts are in the 83% without breaking anything. - Treat “no securityContext key” as a procurement signal. When you are choosing between two charts that do the same job, the one whose
values.yamllets you set a seccomp profile is telling you something about how it will be maintained. - Use a mutating policy engine for the gap, deliberately. Kyverno can add
seccompProfileand drop capabilities on admission across the whole cluster. That is a legitimate answer to the 87 charts with no knob, as long as it is a documented control and not an undocumented surprise for the next engineer. - Open the pull request. 28 charts are four lines from passing. Chart maintainers merge these; the reason they have not is that nobody has sent them.
- Set limits while you are in there. If you are already overlaying a securityContext onto a chart, the resources block is in the same file.
Method and caveats
Charts were rendered at the version Artifact Hub listed as current on 16 October 2026, with Helm 3.19.0 and --kube-version 1.33.0, retrying at 1.35.0 for charts that required a newer API surface, which recovered seven. Defaults change: a chart that fails today may pass next release, and the pass list is a snapshot, not a certification. Our sample is the most-starred 600 charts, which is deliberately biased towards well-maintained software, so a random chart from the long tail is likely to be worse than these numbers, not better.
We checked the Linux restricted controls only, and did not evaluate Windows hostProcess fields or AppArmor. Charts that render a pod only when an optional subchart is enabled were measured with that subchart in its default state. The values.yaml search is a string search against the parent chart’s values file: a chart that allows an arbitrary pod spec patch through a generic override mechanism, as NATS does, is counted as having no named key even though a determined operator could still get there. That makes our 32.7% a floor on fixability rather than a ceiling.
The 131 charts we could not pull are a genuine coverage gap. Dead repository URLs in a package index are their own small story, and one we will come back to.
The pattern underneath
This is the same shape we keep measuring. The safe default lived in the dashboard, not in the code path anyone used. 196 shipped agent configurations and 10 of them set any guard at all. 153 of 250 MCP servers handed over their tool list with no key asked. 7,802 action references pointing at a tag that moves.
In every case the platform shipped the control, the documentation recommended it, and the distribution layer defaulted to off. The control is never the hard part. Getting it to be what happens when somebody types the install command is the hard part, and it is not a technology problem.
REPTILEHAUS builds and runs Kubernetes platforms for teams who need them to pass an audit as well as a load test. If you are carrying an exemption list you cannot justify, or you want the render-and-check step wired into CI before the next compliance cycle, get in touch.
📷 Photo by Fabian Kleiser on Unsplash
