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.yamlfile 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
- Audit
values.yamlfor real credentials. Replace any hardcoded secrets with placeholder strings like""orCHANGEME, and document that these must be supplied at deploy time via a secrets manager. - Never commit rendered manifests containing
kind: Secretto 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. - Use
--set-stringand 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. - Add
secrets*.yamland*-values-override.yamlto your.helmignoreso they are excluded from packaged tarballs. - Restrict Helm release Secret access in the cluster. Apply RBAC so only the CI service account and cluster admins can
getorlistHelm release Secrets in each namespace. Limit history with--history-max 3during upgrades. - 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--setarguments, 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