Helm is the closest thing Kubernetes has to a package manager, and that is exactly why it is a security problem. A single helm install can create Deployments, ServiceAccounts, ClusterRoleBindings, NetworkPolicies, and Secrets in one atomic operation — and most engineers running that command have never read the templates that generate them. When you pull a chart from a public repository, you are trusting whoever wrote it to not bind your workload to cluster-admin, not run privileged containers, and not bake a default password into a Secret. That trust is frequently misplaced. The Argo CD path-traversal flaw (CVE-2022-24348) showed that even mature GitOps tooling could be tricked through a malicious chart's values.yaml, and the broader lesson holds: a chart is executable infrastructure, not a config file. This guide covers how to treat it that way.
Why a chart is more dangerous than a manifest
A raw Kubernetes manifest says exactly what it does. A Helm chart hides its behavior behind Go templating, conditionals, and hundreds of overridable values, so the resource that actually lands in the cluster depends on inputs you may not have inspected. The gap between "what the README shows" and "what the rendered YAML contains" is where risk lives.
The fix is to never install what you have not rendered. helm template expands a chart to plain YAML locally, without touching the cluster:
# Render the chart with your values and inspect what will actually be applied
helm template myapp ./mychart -f values-prod.yaml > rendered.yaml
# Grep for the things that should make you nervous
grep -nE 'ClusterRoleBinding|privileged: true|hostPath|hostNetwork|:latest' rendered.yaml
Make this a required step, not an optional one. In CI, render every chart and pipe the output into a policy scanner before it is ever allowed near a real cluster.
Pin images by digest, not by mutable tag
The default in most charts is a floating tag like image: nginx:1.27 or, worse, nginx:latest. A tag is a pointer that upstream can repoint at any time, which means the image you tested is not guaranteed to be the image you run. Pin to an immutable digest so the content is cryptographically fixed:
# values.yaml — pin the exact content, not a moving label
image:
repository: nginx
# digest wins over tag when both are present
digest: "sha256:9c2b660fcd9d3b1a3a1f2f6d7c8e5b4a3c2d1e0f9a8b7c6d5e4f3a2b1c0d9e8f"
pullPolicy: IfNotPresent
Digest pinning also makes admission-time signature verification meaningful — you cannot verify a signature against a tag that might change underneath you.
Scope RBAC to the workload, not the cluster
The single most common chart footgun is over-broad RBAC. Many charts ship a ClusterRole with wildcards because it is easier than enumerating exact permissions. A workload that only reads ConfigMaps in its own namespace should never receive ["*"] on ["*"].
# A least-privilege Role scoped to a single namespace and resource set
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: {{ include "myapp.fullname" . }}
namespace: {{ .Release.Namespace }}
rules:
- apiGroups: [""]
resources: ["configmaps"]
verbs: ["get", "list", "watch"]
When you audit a third-party chart, grep the rendered output for kind: ClusterRole, kind: ClusterRoleBinding, and verbs: ["*"]. Any of them should trigger a conversation, not an automatic approval.
Keep secrets out of values files
values.yaml files get committed to Git, pasted into tickets, and echoed in CI logs. A plaintext database password in a chart's values is a leaked credential waiting to happen. Do not template raw secrets. Instead, reference secrets created out of band, or use the External Secrets Operator to pull them from a real secret manager at deploy time:
# Reference an existing Secret rather than templating the value
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: myapp-db # created by External Secrets Operator / SealedSecrets
key: password
If you must template secret material during migration, encrypt the values file with SOPS and a KMS key so the committed artifact is ciphertext, never cleartext.
Verify chart provenance before you trust it
Helm has supported provenance since v3 through signed .prov files that hash the chart and are verified with helm install --verify against a GPG keyring. In 2026 the stronger option is to treat charts as OCI artifacts and sign them with Sigstore cosign, the same way you sign container images:
# Push the chart to an OCI registry, then sign it keylessly via Sigstore
helm push myapp-1.4.0.tgz oci://registry.example.com/charts
cosign sign registry.example.com/charts/myapp:1.4.0
# Verify identity and issuer before install
cosign verify registry.example.com/charts/myapp:1.4.0 \
--certificate-identity-regexp '.*@example.com' \
--certificate-oidc-issuer https://token.actions.githubusercontent.com
Keyless signing anchors the signature to a workload identity (a GitHub Actions OIDC token, for example) and records it in the Rekor transparency log, so you can prove which pipeline built and published the chart.
Hardening checklist
| Control | Why it matters | How to enforce |
|---|---|---|
helm template + scan in CI | Rendered YAML is the real attack surface | Fail the pipeline on policy violations |
| Digest-pinned images | Tags are mutable pointers | Reject :latest and tag-only refs |
| Namespaced least-privilege RBAC | Charts default to over-broad roles | Flag ClusterRoleBindings and wildcards |
| No secrets in values | Values land in Git and logs | SOPS encryption or External Secrets |
| Signed chart provenance | Proves origin and integrity | cosign verify at admission |
securityContext defaults | Charts often omit non-root settings | Pod Security Admission restricted |
How Safeguard helps
A chart is infrastructure-as-code, and it should be scanned like it. Safeguard's IaC scanning renders Helm charts and flags the dangerous patterns — wildcard RBAC, privileged containers, hostPath mounts, missing securityContext — before the chart is ever installed, so review happens on the resources that will actually exist rather than the README. Because charts reference container images, container security scanning inspects the images a chart pulls and confirms they are digest-pinned and signed, and the Safeguard CLI runs the same checks locally and in CI so a bad chart fails the build instead of the cluster. If you are comparing lightweight scanners for this workflow, see Safeguard vs Trivy, and review plans on the pricing page.
Charts are convenient precisely because they hide complexity — so verify what they generate. Create a free Safeguard account or read the documentation to start scanning your charts today.