Where does a password passed to helm upgrade --set end up besides the rendered Secret?
answer
- The apply is not the only write
- Helm needs it to roll back
- One object per revision, in the namespace
- helm get values prints it back
- History retention keeps the old one too
basics
~20 sIn the release record. Helm stores each revision as a Secret named sh.helm.release.v1.<release>.v<revision> holding both the supplied values and the rendered manifest, so helm get values and helm get manifest print the password back, for every retained revision.
solid answer
~50 sHelm files a copy of everything you passed it. Each revision is stored as a Secret named `sh.helm.release.v1.<release>.v<revision>` in the release namespace, and that record contains the values the caller supplied *and* the rendered manifest — so the password is in there twice, in the clear, and `helm get values` or `helm get manifest` reads it straight back. Reading it needs nothing beyond permission to read Secrets in that namespace, which means anyone with that permission can recover every credential ever passed to any release there. History makes it durable: `helm upgrade` adds a revision rather than replacing one, and Helm retains `--history-max` revisions (10 by default), so a rotated password survives in the previous records — which is exactly why `helm rollback` can restore it. `--set-file` keeps the value out of shell history but stores it identically.
code
bash · 5 lineshelm get values pdfsign -n payments
helm get manifest pdfsign -n payments | grep -A3 'kind: Secret'
kubectl get secret sh.helm.release.v1.pdfsign.v47 -n payments \
-o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | head -c 400go deeper
Know that Helm records every install and upgrade somewhere in the cluster, and that helm get values will print back whatever you passed on the command line. Do not assume a --set argument disappears once the command finishes.
Name the release record object and its naming pattern, and explain that it holds both the supplied values and the rendered manifest. Be able to say what permission is needed to read it and why history keeps old copies around.
Reason about blast radius: anyone able to read Secrets in the namespace can recover every credential ever passed to any release there, across the retained revisions. Show the mitigations that actually work and be honest that --set-file is not one of them.
Decide the rule for the estate — whether credential material may reach a chart's values at all, how you would audit existing release records for it, and what has to change in the delivery path once you conclude it may not.
### Helm does not forget what you passed it `helm install` and `helm upgrade` do two things: they apply the rendered objects to the cluster, and they write a **release record** describing what they just did. That record is a Kubernetes Secret in the release namespace, named `sh.helm.release.v1.<release>.v<revision>` — one per revision. Inside it, Helm stores a JSON document that has been gzipped and base64-encoded, and that document contains, among other things: * **the values the caller supplied** — exactly the merge of `values.yaml` overrides that produced this revision, however they arrived: `-f`, `--set`, `--set-string`, `--set-file`; * **the fully rendered manifest**, which is every object the chart produced, including the `Secret` object with the credential in it; * the chart itself, its metadata and the revision's status. So a credential passed on the command line is not written once. It is written into the live Secret object, and it is written twice more into the release record — once as an input value and once inside the rendered manifest. Helm hands both back without ceremony: ```bash helm get values pdfsign -n payments # the values the release was installed with helm get manifest pdfsign -n payments # every rendered object, Secret included ``` Neither command needs a special privilege beyond being able to read Secrets in that namespace, because that is where the record lives. The practical consequence is worth saying out loud in an interview: **anyone who can read Secrets in a namespace can read every credential ever passed to any release in it**, not just the ones currently live. That is a wider blast radius than people expect, and it is a Helm property rather than a Kubernetes one. ### History makes it durable Rotation does not erase the old value. `helm upgrade` writes a *new* revision record and leaves the previous ones in place; Helm keeps the last `--history-max` revisions, which defaults to 10. If you rotate a PDF-signing service's passphrase at revision 47, revisions 38 through 46 still carry the previous passphrase, in the clear, in the same namespace — and they stay there until enough further upgrades push them past the retention window, or the release is uninstalled. That is also precisely why `helm rollback` can restore the old credential: the material is still there, by design. ### Reading it directly You do not need the Helm CLI at all. The record is an ordinary Secret, so: ```bash kubectl get secret sh.helm.release.v1.pdfsign.v47 -n payments \ -o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip ``` The double decode is not a typo: `kubectl` returns the Secret's own base64 field, and the value inside is itself base64 wrapping the gzipped JSON. Being able to describe that pipeline is a good signal that a candidate has actually looked at a release record rather than repeating a rule. ### Which mitigations actually move the needle * **`--set-file signing.passphrase=./passphrase.txt`** reads the value from a file rather than an argv string. That genuinely helps with shell history and with a process list on a shared host, and it is the right way to pass multi-line material such as a key. It changes nothing at all about the release record: the file's *content* becomes the value, and the value is stored. * **`.Files.Get`** is worse, not better. It reads a file that is packaged *inside the chart*, so putting a credential there ships it to everyone who pulls the chart, plus into the release record when the chart is stored with the release. (A `.helmignore` entry would keep the file out of the package — and then `.Files.Get` cannot read it either.) * **A values file in a repository** is not an improvement over `--set`; it moves the plaintext from shell history into Git history, where deleting the line later does not remove it. * **Referencing an existing Secret by name** is the one that works. If the chart takes `existingSecret: pdfsign-signing` and a key name, the credential never enters `.Values`, so the release record contains a name and nothing else, and `helm get values` is safe to share. ### What this is not Whether your CI platform masks the value in a job log, and how the cluster stores Secret objects on disk, are separate questions with separate owners. This one is specifically about Helm's own retention: the tool keeps a copy of everything you passed it, for ten revisions by default, in a namespace-readable object, and it will happily print it back to anyone who asks. The one-line version to say in an interview: *a secret passed to Helm is not a secret you handed to a running pod, it is a secret you filed.*
- You rotate the credential with an upgrade. Is the old one gone?No. The upgrade writes a new revision record and leaves the previous ones in the namespace, each still carrying the old value in its stored values and manifest. They age out only when further upgrades push them past `--history-max`, which defaults to 10, or when the release is uninstalled. That retention is deliberate — it is what makes `helm rollback` able to restore the previous state.
- Does --set-file solve this?Only the argv half of it. `--set-file` reads the value from a file, so it stays out of shell history and out of the process list on a shared host, and it is the correct way to pass multi-line material. The file's content still becomes the value, and the value is still stored in the release record and printed by `helm get values`.
- What about putting the credential in a file inside the chart and reading it with .Files.Get?That is worse. `.Files.Get` reads a file packaged inside the chart, so the credential ships in the chart tarball to everyone who pulls it, as well as landing in the release record. Adding the file to `.helmignore` keeps it out of the package, but then `.Files.Get` cannot read it at render time either.
- How do you make helm get values safe to share?Stop passing material through values. Have the chart accept the name of an existing Secret plus the key to read, so the release record contains a reference and nothing sensitive. Something outside the release — an operator, a bootstrap step, or a platform process — creates the object. The chart still consumes it normally through the pod spec.
saying these in an interview costs you the question
- Thinking only the rendered Secret object holds the value
- Believing --set is safer than a values file for the stored release
- Assuming an upgrade erases the previous credential
- Claiming reading a release record needs cluster-admin
- Suggesting .Files.Get on an in-chart file as the safe option
- Saying helm get values only shows defaults from values.yaml