← All articles

Secrets in Helm Charts: How Kubernetes Deployments Silently Expose Credentials

July 18, 2026

Helm Is Convenient. That Convenience Has a Credential Problem.

Helm is the de-facto package manager for Kubernetes. It lets teams template, version, and deploy complex applications with a single command. But the same flexibility that makes Helm powerful also makes it a surprisingly common source of leaked credentials — in public chart repositories, shared values.yaml files, tarballs in artifact registries, and rendered manifests cached in CI logs.

This article walks through every major place secrets end up inside Helm workflows, and gives you concrete steps to close each gap.

Where Credentials Actually Hide in Helm

1. Hardcoded Values in values.yaml

The most common mistake. A developer sets a sensible default so the chart "just works" out of the box, and that default is a real credential:

# values.yaml
database:
  password: "Sup3rS3cr3t!"
externalApi:
  key: "sk-live-abc123xyz"

values.yaml is almost always committed to the repository. If that repository is ever made public — even briefly — those credentials are now exposed. GitHub's event timeline means even a 30-second public window can be indexed by automated scanners.

2. Rendered Manifests Stored in Git (helm template Output)

Many teams render their Helm charts into raw Kubernetes YAML using helm template and commit the output for GitOps workflows (Argo CD, Flux). If a secret value was passed at render time via --set or a local overrides file, it will appear in plaintext in the rendered Secret resource — base64-encoded, not encrypted:

apiVersion: v1
kind: Secret
metadata:
  name: api-credentials
data:
  apiKey: c2stbGl2ZS1hYmMxMjN4eXo=   # base64 of "sk-live-abc123xyz"

Base64 is not encryption. Anyone with read access to the repo has your secret.

3. Secrets Passed via --set in CI/CD Pipelines

Passing secrets at deploy time feels safer than committing them. But the full helm upgrade command — including --set arguments — often appears verbatim in CI logs:

helm upgrade myapp ./chart \
  --set database.password=$DB_PASSWORD \
  --set api.key=$API_KEY

If the CI platform doesn't mask the variable correctly, or if the variable is passed as a literal string instead of a reference, it prints to stdout. GitHub Actions, Jenkins, and GitLab CI have all had incidents where secrets surfaced in job logs. Logs are frequently retained for weeks and accessible to everyone with repo read access.

4. Chart Tarballs Published to Artifact Registries

When you package and push a chart with helm package and helm push, the entire chart directory — including values.yaml and any override files you forgot to exclude — is bundled into the tarball. If you publish to a public registry like Artifact Hub, or to a self-hosted registry with overly broad access, those files are downloadable by anyone.

5. Helm Secrets Plugin Misuse

The popular helm-secrets plugin (backed by SOPS or Vault) is a legitimate solution — but it's easy to misconfigure. Common mistakes include:

  • Committing the decrypted secrets.yaml file alongside the encrypted one.
  • Leaving the SOPS age or PGP private key in the repository or in a broadly-shared CI environment variable.
  • Using symmetric encryption keys that are shared across teams without rotation.

6. Helm Release History in the Kubernetes API

Helm stores its release history as Kubernetes Secrets (or ConfigMaps in Helm 2). Each release record contains the full rendered manifest — including any Secret resources from that deployment — encoded in the cluster. Anyone with kubectl get secret -n kube-system access and the right labels can retrieve and decode the entire release history, including previous values of rotated credentials.

kubectl get secret sh.helm.release.v1.myapp.v3 -o jsonpath='{.data.release}' \
  | base64 -d | base64 -d | gunzip | jq .

This one command surfaces the complete rendered chart for every historical revision.

A Practical Remediation Checklist

  1. Audit values.yaml for real credentials. Replace any hardcoded secrets with placeholder strings like "" or CHANGEME, and document that these must be supplied at deploy time via a secrets manager.
  2. Never commit rendered manifests containing kind: Secret to Git. If you need GitOps, use Sealed Secrets, External Secrets Operator, or Vault Agent Injector to fetch secrets at runtime rather than storing them in Git.
  3. Use --set-string and environment variable references carefully in CI. Prefer mounting secrets as files and referencing them with --values /path/to/secret-values.yaml, ensuring that file is sourced from a secrets manager (Vault, AWS Secrets Manager, GCP Secret Manager) and never written to disk in the build workspace.
  4. Add secrets*.yaml and *-values-override.yaml to your .helmignore so they are excluded from packaged tarballs.
  5. Restrict Helm release Secret access in the cluster. Apply RBAC so only the CI service account and cluster admins can get or list Helm release Secrets in each namespace. Limit history with --history-max 3 during upgrades.
  6. Run secret scanning on your chart repository. Your pre-commit hooks and CI pipeline should scan values.yaml, rendered outputs, and all YAML templates for credential patterns before any push or package step.

The Right Architecture: Secrets Out of Helm, Into a Secrets Manager

The cleanest pattern eliminates secrets from Helm entirely. Instead of embedding credentials anywhere in chart files, use the External Secrets Operator to declare a reference:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: api-credentials
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: ClusterSecretStore
  target:
    name: api-credentials
  data:
    - secretKey: apiKey
      remoteRef:
        key: prod/myapp/api-key

Now your Helm chart contains zero secrets. The credentials live in AWS Secrets Manager (or Vault, GCP, Azure Key Vault), are fetched by the operator at runtime, and are never committed anywhere. Rotation happens in the secrets manager and propagates automatically.

How to Know If You're Already Exposed

The hard part is knowing whether credentials have already leaked — in a values file that was briefly public, a tarball that was pushed to the wrong registry, or a CI log that was retained longer than expected. Manual grep and ad-hoc scanning miss patterns, especially across git history and archived artifacts.

Before you assume your Helm repos are clean, run a free GhostCred scan — it checks your repositories and surfaces exposed API keys, tokens, and IAM misconfigurations in around 60 seconds, with findings mapped to SOC 2 and HIPAA controls so you know exactly what to prioritize.

Key Takeaways

  • Helm's values.yaml, rendered manifests, CI --set arguments, packaged tarballs, and cluster-stored release history are all viable paths for credential exposure.
  • Base64 encoding in Kubernetes Secrets provides zero confidentiality — treat them as plaintext.
  • The right fix is architectural: use External Secrets Operator or Vault Agent to keep credentials out of Helm charts entirely.
  • Add secret scanning to your chart CI pipeline and pre-commit hooks so leaks are caught before they reach a registry or cluster.

See what's exposed in your own code.

Run a free scan