After a stored database password is replaced, what makes the previous value still fetchable to the same callers?
answer
- a write appends, it does not overwrite
- the name carries a history
- a read can name a version
- same read right reaches both values
- retiring is its own step
basics
~20 sMost stores append a version rather than overwriting the old one, so the earlier value stays in the name's history and any caller with read rights on that name can ask for it by version. Replacing is not retiring.
solid answer
~50 sMost stores model a name as an ordered list of versions: a write appends a new one, and the earlier values go on sitting there, enabled and servable. A read that asks for the name alone gets the current value; a read that asks for the name *and* an earlier version number gets last quarter's password — with the same read right, not a second one. That is why a replacement on its own is not a rotation. Two further things are still true afterwards: the old value is fetchable from the store until its version is retired, and it is still accepted by the database account until someone changes it there. Those are separate operations against separate systems, and stores differ in what they give you for the first, so both belong in the written procedure rather than being assumed.
code
pseudocode · 16 lines# last quarter: version 6 written. this quarter: version 7 written.
current = store.read(name = "adminToolDbPassword")
# -> { value: "<this quarter>", version: 7, state: ENABLED }
old = store.read(name = "adminToolDbPassword", version = 6)
# -> { value: "<last quarter>", version: 6, state: ENABLED }
# the same read right served both: the write appended 7, it did not remove 6
store.retireVersion(name = "adminToolDbPassword", version = 6)
old = store.read(name = "adminToolDbPassword", version = 6)
# -> refused: version retired
# note what did NOT change: the database account still accepts <last quarter>
# until it is changed there.go deeper
Recall that a secret store keeps a name's earlier values, not only the latest one, and that a read can ask for a specific version.
Explain that a write appends a version and that the same read right reaches the earlier ones, so replacing a value and retiring its history are two different operations.
Show the procedure: replace, then retire the earlier versions inside the store, then withdraw the old credential at the account that accepts it — and verify each one rather than assuming the write did it.
Weigh what history buys the estate — a cheap undo for a mistaken write — against what it costs: a readable copy of every credential the estate has used, sitting behind an ordinary read right.
## A name in the store is a list, not a box It is tempting to picture a stored credential as a box: you put a value in, and putting a new value in pushes the old one out. Most stores do not work that way. A name holds an ordered list of **versions**, and a write **appends** one. The password written last quarter became version *n*; the replacement became version *n+1*; nothing removed version *n*. "Current" is a pointer to the newest version, not a container that only ever holds one thing. One separation is worth making immediately, because the word *version* is doing two jobs in this area. This is a version of the **stored value**. It is not a version of the **key that encrypts the store's contents** — that is a different object on a different clock, and none of what follows is about it. ## Why the earlier value is still fetchable Three properties combine, and each one is defensible on its own: - **A read can name a version.** The read interface takes a name and, optionally, a version. That is the feature: it is what makes history useful at all. - **Rights are expressed over names, not over points in time.** The read right that returns the current value of a name normally returns its earlier values too. There is usually no separate permission to withhold, so "only the operators can see the old password" is not true by default. - **Nothing ages out by itself.** An earlier version stays servable until something retires it: a per-name limit on how many versions are kept, a scheduled destruction, or an explicit operation someone performs. | A read that asks for... | What comes back | |---|---| | the name alone | the current value | | the name and an earlier version | the older value, on the same read right | | a version that has been retired | a refusal — the version exists but is not served | | a version that has been destroyed | nothing at all; there is no value left to serve | ## Replacing, retiring, withdrawing — three steps, not one 1. **Replace.** Write the new value. From this moment a plain read of the name returns the new password. Nothing else has happened: no permission changed, no earlier value moved, and the database still accepts the old password. 2. **Retire.** Make the earlier versions unservable, so a read that names one is refused. This is an operation inside the store, and it is the step that closes the "anyone with read can fetch last quarter's password" hole. 3. **Withdraw.** Change or remove the old credential at the system that accepts it — the database account. This is the step that makes a surviving copy worthless, and it happens outside the store entirely. A quarterly exercise that performs only step 1 has changed which value the store hands out by default and nothing else. That is the specific finding a review three months later turns up: the previous value is still readable, and it still works. Steps 2 and 3 are independent of each other — retiring the version does not touch the database account, and changing the database account does not remove the value from history. ## Where designs differ This is the part a procedure most often gets wrong by assuming. Designs genuinely differ, and it is worth checking rather than remembering: - Some managers let a read refuse anything below a chosen version, so one setting retires a whole span of history at once. Others only enable and disable individual versions, which means retirement is per-version work. - Some names can be configured to keep no history at all; others keep a fixed number of versions and drop the oldest as new writes arrive; others keep everything until told otherwise. - Some stores treat removing a version as reversible for a period, others as immediate and final. None of that is a property of "secret stores" in general, and a runbook written against one shape silently does nothing on another. ## What this means in practice - Put retirement in the rotation procedure as a named step with its own verification, not as an implied consequence of the write. - Set the per-name history limit deliberately, especially on names whose single value reaches a lot. - When the replacement was prompted by an exposure, retire and destroy the exposed version explicitly rather than letting it age out — the point of the replacement was to end that value. - Verify the finding the same way a review will: try to read the previous version with an ordinary consumer's rights, and try the old password against the account. Two checks, two different answers.
- Does retiring the earlier version in the store stop the old password working?No. Retiring is an action inside the store: it stops the store handing that value out. The database account goes on accepting the old password until it is changed or removed there. Withdrawal happens at the system that accepts the credential, and a copy someone already fetched is unaffected by anything you do in the store.
- The name is configured to keep only the current value — what changes?Reads by version stop being an exposure, because there is no history to serve. You also lose the cheap undo for a mistaken write: a wrong value has to be corrected from wherever the right one is authoritatively held, or by changing the credential at the downstream system and writing the new one in.
- Why is a per-name history limit worth setting rather than leaving at whatever the store defaults to?Because the limit decides how much of the estate's credential history sits behind one read right. Platforms set a default so the feature works out of the box, not because that number suits a name whose single value reaches a whole environment. Set it against what the name reaches, not against storage cost.
saying these in an interview costs you the question
- Thinks writing a new value overwrites and removes the old one
- Says reading an earlier version needs a second, separate permission
- Calls the replacement a rotation when nothing was retired or withdrawn
- Assumes retiring the version stops the database accepting the old password
- Confuses a version of the stored value with a version of the encryption key