skip to content

Hardcoding a credential is usually taught as "don't put the password in the source file". Give the definition of the defect that also explains why the same value sitting in a container image layer, a build log, or a compiled binary is just as bad — and why deleting the line and force-pushing is the wrong frame for the fix.

level: middleimportance: must knowfreq 70%

answer

  1. bearer value: bounded distribution + revocability + attribution
  2. store test: replicated? append-only? wider principals?
  3. layer union: later delete keeps earlier layer
  4. rotate first, purge second — purge is best-effort
  5. separation > transformation > scope/lifetime > detection

basics

~20 s

A secret is a bearer authenticator whose worth depends on controlled distribution and revocability. The defect is copying it into any store that is replicated, append-only, or readable by more principals than the holder. Rotate first; deletion is best-effort.

solid answer

~50 s

A credential is a **bearer** value: presenting it is being the principal. Its security comes from three operational properties — you can bound who holds it, you can revoke it, and you can attribute its use. The defect is putting it in a store that destroys those properties, and source code is only the most visible such store. Version control is replicated, append-only and forked; image layers are immutable, so deleting the file in a later layer does not remove it; string constants survive in class files, native binaries and minified bundles; CI logs, backups and crash reports capture environment values. So "is it gone from HEAD?" is the wrong question. The exposure already happened, and the only action that restores revocability is issuing a new credential and invalidating the old one. Purging copies is hygiene, not remediation: forks, clones, mirrors and caches are outside your reach.

go deeper

for a junior

Be able to say a credential is a bearer value, that committing it means treating it as disclosed, and that the fix is to rotate rather than to delete the line.

for a middle

State the defining test — a store that is replicated, append-only, or readable by more principals than the holder — and apply it to VCS, image layers and compiled artifacts to show the equivalence.

for a senior

Lead with the incident order (rotate, hunt for use across the whole exposure window, purge, then add the structural control) and connect rotation cost to whether the organisation can honestly claim it responds to exposure.

for a principal

Frame it as a property loss rather than a file-content problem, and argue the ladder: an architecture where the artifact never carries credentials removes the class, whereas encryption relocates it and scanning only samples the aftermath.

## What a secret actually is Almost every secret an application handles is a **bearer authenticator**: a value such that presenting it is sufficient to be treated as the principal it belongs to. Passwords, API keys, database credentials, signing keys, webhook shared secrets and refresh tokens are all bearer values. Nothing in the bytes distinguishes the rightful holder from anyone who obtained a copy. Its security therefore rests on three *operational* properties, not on the value itself: 1. **Bounded distribution** — you can enumerate, at least approximately, which principals and which stores hold a copy. 2. **Revocability** — you can make every copy worthless quickly, without rebuilding and redeploying the world. 3. **Attribution** — when it is used, you can tell which holder used it. ## The defect, defined Hardcoding is one instance of a general defect: **placing a bearer authenticator into a store whose distribution you do not control and whose contents you cannot retract.** Source code is the textbook case only because it is the most visible one. What matters is the store's properties: is it replicated? is it append-only or immutable? is it readable by a broader set of principals than the credential's intended holder? Apply that test and the equivalences fall out: - **Version control.** History is content-addressed and append-only; every clone, fork, mirror, pull-request ref and CI cache is a full copy. A rewrite changes your branch, not other people's objects. - **Container image layers.** A layered filesystem unions layers; a later layer that deletes a config file leaves the earlier layer intact, and anyone who can pull the image can extract it. Registries are content-addressed and often retain digests after a tag moves. - **Compiled and packaged artifacts.** Literal strings are recoverable from a class file, a native binary, a WebAssembly module, a mobile package or a minified browser bundle. No language exempts you; obfuscation raises the cost of extraction, it does not change the security property. - **Build logs, backups, snapshots, error reporters.** These routinely capture full environment or configuration dumps, and they are usually readable by a wider audience than production itself. - **Public package registries**, which frequently forbid unpublishing entirely. Anything client-distributed is the extreme case: a secret shipped to a browser, a phone or a customer's server is public by construction, because the trust boundary runs between you and the device. ## Why "delete and force-push" is the wrong frame It answers the question "can the value still be read in the current tip?" but the property that was lost is *bounded distribution*, and that loss is irreversible the moment the artifact left your control. The only action that restores security is issuing a new credential and invalidating the old one. The correct incident order is: 1. **Rotate/revoke** the exposed credential — assume disclosure, do not investigate first. 2. **Look for use** of the old credential in access logs across the whole exposure window, which begins when the value was written, not when it was noticed. 3. **Purge** copies where cheap, as hygiene against casual reuse. 4. **Add the control** that makes recurrence structurally impossible, rather than relying on the reviewer who missed it last time. A useful consequence: if you cannot rotate a credential cheaply, you do not really have an incident response for it. Rotation cost is what turns "assume disclosure" from a slogan into a decision. ## Where the defences rank Secrets handling has the same ladder shape as injection defence — a guarantee at the top degrading into a heuristic at the bottom, in this order: - **Structural separation** — the artifact never contains the credential at all; a runtime identity the platform vouches for obtains it. The guarantee is absolute for the class: there is no value in the artifact to leak. - **Transformation** (encrypted config, sealed values) — better than plaintext, but it terminates in another secret that must itself be delivered. It relocates the problem one level down rather than eliminating it. That is exactly why it loses to structural separation. - **Validation/policy** (narrow scope, short lifetime, one credential per principal) — does not prevent disclosure, bounds what disclosure costs. It beats detection because it holds even when nobody is watching. - **Detection** (secret scanning, anomaly detection) — post-hoc and probabilistic; it finds some of what already escaped. ## What people misclassify Values that are secrets but get treated as configuration: token-signing keys, webhook validation secrets, internal service-to-service credentials, and above all any credential that unlocks the rotation path itself. Values that are *not* secrets and should not be managed as such: public keys, client identifiers, bucket names, and hostnames — treating them as secret adds ceremony without adding a property. And "it's a private repository" is not a boundary you can lean on: repository access changes with hiring, contracting, forking and misconfiguration, while the committed value does not.

  • The value was committed to a private repository that only five people can read. Does that change your response?
    No. Private access control is a policy over a replicated, append-only store, and the copies already sit in five local clones, CI caches and any forks or mirrors. Membership also changes over time while the committed object does not. Treat it as disclosed and rotate; the private setting only lowers the probability that someone external already has it, which affects urgency, not remediation.
  • How would you make this class of mistake structurally impossible rather than relying on reviewers?
    Remove the destination and the source of temptation: make the runtime obtain credentials from a broker or a platform identity so there is no field in the config file for a literal, and fail the build if configuration for a credential-shaped key resolves to a literal. Add server-side scanning where the code lands, not only in a bypassable local hook, and make rotation cheap enough that hits are handled in minutes.
  • Is obfuscating or encoding a key inside a mobile app or browser bundle ever acceptable?
    Not as a security control. Anything shipped to a device the attacker owns is extractable, so obfuscation only raises the cost of an operation the attacker performs once. The correct control is architectural: the client authenticates as its own user, and a server-side component holds the privileged credential and enforces per-user authorization on each call.

Rewriting git history to remove a key is like recalling a printed newspaper: you can pull the copies still on your own shelf, but the edition is out. The only real remedy is publishing a correction that invalidates what was printed.

saying these in an interview costs you the question

  • "We removed it in the next commit and force-pushed, so it's fixed."
  • "It's a private repo, so it isn't really exposed."
  • "It's compiled, so nobody can read the string."
  • "We deleted the file in a later image layer."
  • Investigating whether anyone actually used it before rotating.

context