Why does a "no secrets in plain environment variables" standard need rules at two decision points?
answer
- one standard, two documents
- a green source scan proves less than it looks
- the value need never appear in the repo
- references, pipelines, init containers, sidecars
- coverage is disjoint in both directions
basics
~20 sBecause the two rules read different documents. A source-text rule catches a secret typed literally into a file; only a check over the workload's resolved environment catches one that a reference, a pipeline or an injector puts there at start-up. Neither sees the other's case.
solid answer
~40 sOne standard, two inputs. Early, over source text, the rule finds the case where someone wrote a password directly into a manifest or a script — cheap to catch, and the feedback lands on the author immediately. But most plaintext environment variables never appear literally in a repository: a secret reference in the manifest is expanded by the platform into a plaintext variable, the deployment pipeline supplies one, or an init container, sidecar or operator adds entries as the workload starts. None of that is in the source, so no source-text rule can decide it. A check over the resolved environment of the workload sees all of those, whatever their origin. The duplication is real but correct: two rules, two documents, one standard, and dropping either leaves a whole class of violation invisible.
code
yaml · 15 lines# in the repository - a source-text secret scan reports clean
containers:
- name: api
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-credentials
key: password
- name: LOG_LEVEL
value: "info"
# ...
# inside the running container, the resolved environment reads:
# DB_PASSWORD=<the actual password>
# LOG_LEVEL=infogo deeper
Know that a secret can end up in a container's environment without ever being typed into a file, and that a scan of the repository would not see it.
Explain the concrete paths — a reference the platform expands, a value the deploy job supplies, an init container or sidecar adding entries at start-up — and say which document each check reads.
Defend the duplication under pressure: disjoint coverage in both directions, one standard with one wording, and a clear answer for which rule you would build first if forced to pick.
Be ready to say what evidence you would accept that the standard actually holds across an estate, and who pays for the late check that produces it rather than for the cheap early one that feels sufficient.
## One standard is not one rule A policy standard is a sentence in English: *secret material must not reach a workload as a plain environment variable.* A policy **rule** is an expression evaluated against a specific document. The moment you try to write the rule, you discover the standard does not map onto a single document. ## What the source-text rule can decide Run early — in the editor, in a pre-commit hook, on a pull request — and the document you are handed is the file as written. That rule finds: - a password or token typed literally as the value of an environment entry - the same thing in a script, a compose file or a deployment definition - credentials embedded in a command line This catch is worth having. It is close to free, it fires within seconds, and it lands on the person who typed the line while the intent is still in their head. It also stops the secret from entering version-control history, which is a separate and expensive problem. ## What it structurally cannot decide The rule's blindness is not a gap in its expression; it is a property of its input. A container can end up with a plaintext environment variable holding a secret through paths that leave no literal in the repository at all: - **A reference expanded by the platform.** The manifest names a secret and a key; the platform reads it and sets a plain environment variable in the container. The source shows a reference; the process shows the value. - **Pipeline injection.** The deploy job supplies the variable from its own secret store. That configuration lives in the pipeline, not in the application repository. - **Start-up injection.** An init container, a sidecar or an operator adds environment entries as the workload comes up, based on an annotation or a controller's own logic. - **A templating layer.** The value is substituted during rendering, so it exists only downstream of the file a scanner reads. Each of these produces exactly the condition the standard forbids, and a source-text rule reports clean on all of them. This is the single most important thing to be able to say out loud: **a green source scan does not prove no secret reaches the environment.** It proves no secret was written down in the place that was scanned. ## The second home So the standard gets a second rule, over a different document: the workload's resolved environment — the object as finally rendered, or the container as it actually runs. That rule does not care where the value came from. It asks a simpler question: does this workload's environment hold something that looks like secret material, or is it fed by a mechanism the standard forbids? Because its input is the end state, it catches all four paths above plus ones you have not thought of. Because its input is the end state, it is also **later** — the feedback is slower and reaches a different audience than the pre-commit rule does. ## Why this is not sloppy duplication An interviewer will push on the duplication, because keeping two rules in step is real maintenance. The defence has three parts. First, the two rules are not copies of each other. They are different expressions over different documents and they will never share an implementation — one matches string patterns in files, the other inspects a structured environment list. What you keep single is the **standard**: one sentence, one owner, one wording that both rules cite, so a developer blocked by either reads the same requirement. Second, the coverage is genuinely disjoint in both directions. The early rule catches a literal secret in a file that never gets deployed at all — the late rule never sees that workload. The late rule catches everything injected downstream. Neither is a subset of the other. Third, deleting the early rule to "avoid duplication" trades a second of feedback for days of it, and deleting the late rule trades completeness for tidiness. Both are bad trades, and both are made regularly. ## If you can only build one Build the late one. It observes the actual condition the standard is about, so it cannot be fooled by a path you did not anticipate. The early rule is an optimisation on top of it — an enormously valuable one for developer experience, but an optimisation. Say that in an interview and you will have answered the question behind the question.
- Isn't maintaining the same standard as two rules a drift risk?It is, and you manage it by keeping the standard single even though the rules are not. One sentence, one owner, and both rules cite the same requirement text and the same identifier in their messages. The expressions themselves cannot be shared anyway — one matches text in files, the other inspects a structured environment list — so the thing to keep in step is the wording, not the code.
- If you could only ship one of the two rules, which one and why?The one over the resolved environment. It observes the condition the standard is actually about, so it holds regardless of how the value got there, including paths nobody anticipated. The source-text rule is a developer-experience optimisation on top: much faster feedback, much narrower coverage. Shipping only the early rule leaves you confident and wrong.
- A team says the source scan is green so the standard is met. What is wrong with that claim?It confuses the document with the outcome. The scan proves nothing secret was written into the files it read; it says nothing about values expanded from a reference, supplied by the deploy job, or added by an injector at start-up. The evidence that supports the claim has to come from the workload's resolved environment, not from the repository.
saying these in an interview costs you the question
- Claims a clean source scan proves no secret reaches the environment
- Calls the second rule redundant duplication and deletes it
- Thinks a secret reference in source means no plaintext variable
- Assumes anything absent from the repository cannot be in the container
- Treats the two rules as copies that should share one implementation