Why Helm Security Matters

Helm is the dominant Kubernetes package manager. The majority of production Kubernetes deployments use Helm charts, either from public repositories like Artifact Hub or from internal chart repositories. When a chart has an insecure default, that default propagates to every installation that doesn't explicitly override it β€” and most do not.

Unlike raw Kubernetes YAML, Helm charts add complexity: Go templates, conditional logic, chart dependencies (subcharts), and values overrides at multiple layers. A security misconfiguration can be hidden inside a template conditional that is only triggered by a specific combination of values, or inside a subchart that inherits settings from the parent chart. These are not visible to a human reading the YAML and are missed by scanners that don't perform full rendering before analysis.

Privileged Containers in Chart Defaults

Many Helm charts set securityContext.privileged: true or leave security contexts entirely absent in their default values.yaml. A privileged container has full access to the host's devices and kernel capabilities β€” it can mount host paths, manipulate the host network stack, and escape the container namespace entirely.

# Commonly seen insecure chart default # templates/deployment.yaml securityContext: privileged: true # Full host access β€” almost never needed runAsRoot: true # Runs as UID 0 allowPrivilegeEscalation: true # Can gain higher privileges readOnlyRootFilesystem: false # Writable filesystem # Secure defaults β€” override in your values.yaml securityContext: privileged: false runAsNonRoot: true runAsUser: 1000 allowPrivilegeEscalation: false readOnlyRootFilesystem: true capabilities: drop: ["ALL"]

Beyond privileged, watch for hostPID: true (access to host process tree), hostNetwork: true (access to host network interfaces), and hostPath volume mounts (direct filesystem access). Each of these can enable container breakout or lateral movement when set in a compromised pod.

Secrets in values.yaml

It is common to find database passwords, API keys, and certificates hardcoded in chart default values.yaml files β€” either as example values or as empty strings that operators are expected to fill in before deployment. The problem: values.yaml is typically committed to source control alongside the chart, and values files used for specific environments are routinely committed to GitOps repositories.

Even when secrets are not in the chart itself, helm install -f production-values.yaml causes those values to be stored in a Kubernetes Secret object (Helm's release history is stored in Secrets). By default, these secrets are base64-encoded but not encrypted β€” any actor with access to the default namespace can read all Helm release values including any secrets they contain.

Helm release secrets: Run kubectl get secrets -l owner=helm in your cluster. These contain the full rendered manifests of every Helm release, including all values. If your values include database passwords or API keys, every entity with namespace read access can retrieve them.

The correct pattern is to reference Kubernetes Secret objects or external secret stores (AWS Secrets Manager, HashiCorp Vault, External Secrets Operator) from your chart templates rather than embedding values directly. The chart should accept a secret name as a value and reference it via valueFrom.secretKeyRef.

Network Policy Gaps

The Kubernetes default is to allow all pod-to-pod communication within a cluster. A Helm chart that does not define NetworkPolicy resources deploys workloads that can communicate with any other pod in the cluster β€” including pods in different namespaces. This means a compromised application pod can make requests to the metadata API, database pods, and internal admin services.

Many community charts include NetworkPolicy resources only as optional, disabled by default. The reasoning is simplicity β€” enabling network policies requires a CNI plugin that supports them (Calico, Cilium, Weave), and not all cluster operators have this. The result is that the secure option is opt-in and rarely enabled.

OCI Registry and Helm Repository Supply Chain

Helm charts are distributed through two mechanisms: Helm chart repositories (HTTP servers with an index.yaml) and OCI registries (the same registry format used for container images, now supported natively in Helm 3.8+).

Chart integrity verification is optional and often not enforced. Helm supports chart provenance files (.prov) with cryptographic signatures, but most operators do not verify signatures during helm install. A man-in-the-middle attack against an HTTP chart repository, or a compromised chart repository, can serve a modified chart with additional backdoored templates.

The OCI path is more secure by default β€” OCI registries support content addressable references and can be combined with Cosign for signing. Migrating your chart distribution to an OCI registry with signed charts enforces provenance verification automatically.

Scanning Helm Charts Correctly

The correct scan flow: render the chart to Kubernetes manifests first, then scan the rendered output. helm template my-release ./my-chart -f values.yaml > rendered.yaml produces the YAML that will actually be applied to the cluster. Then scan rendered.yaml with Checkov, Trivy, or Kubescape.

Scanning the raw chart templates (templates/*.yaml) with Go template placeholders produces false positives (on placeholder values) and misses conditional misconfigurations. Always scan the rendered output, and render with the values you actually use in each environment β€” dev and production may have different security postures.

Helm Chart Hardening Checklist

  1. All containers set runAsNonRoot: true, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true
  2. Capabilities are dropped to ALL; only specific capabilities are re-added if required
  3. No hostPID, hostNetwork, or hostPath unless explicitly required and documented
  4. NetworkPolicy resources are provided and enabled by default
  5. Resource limits and requests are set for all containers
  6. No secrets in values.yaml β€” all secrets reference external stores or Kubernetes Secret names
  7. Helm release history is stored with encrypted secrets (use helm-secrets plugin or native encryption)
  8. Charts are signed and signatures verified at install time
  9. Chart scanning (rendered output) is required in CI/CD before any helm install or helm upgrade

For inherited charts: When using a community Helm chart you don't control, create a custom values.yaml that explicitly overrides security-relevant defaults. Document which values you are overriding and why. Review the override set on every chart version upgrade.