A method for getting every key, token and secret in a small software estate under control — what each kind is, whether a copy is worth keeping, and what actually breaks when you replace one. Built to grow: ask Claude to add a section and this page updates in place.
Why credentials become a mess
Nobody sets out to keep a text file of unlabelled random strings. It happens because credentials arrive one at a time, over months, each one urgent, and none of them announce what kind of thing they are. By the time there are a dozen, no one can say which are live, which are dead, and which were never secret to begin with.
The fix is not more discipline. It is a five-minute classification exercise, done once, that turns every future credential decision into a lookup instead of a judgement call.
The rule that unties most of the knot: a secret is stored by name, and there is only ever one live value per name. Setting it again does not add a second credential — it overwrites the first, instantly and irreversibly. So a "new token" and a "replacement token" are the same event. A column of six values in a notes file is almost always one credential saved six times, five of which died the moment the next one landed.
The method, in four steps
Step
What you do
What it produces
1. Inventory
Read each service's deployed source for every environment binding it reads. Not the docs, not memory — the source.
A complete list, including the ones you forgot existed
2. Classify
Put each one in one of five boxes: shared secret, API key, OAuth token, minted key, identifier
Correct handling stops being a decision
3. Decide the copy
Apply one test: will a human ever have to type this in again?
Usually two or three keepers out of a dozen or more
4. Map the blast radius
For each one, write down what stops working if it is replaced
The document nobody has and everybody needs at 11pm
The method. Step 2 is the one that does the work — everything after it follows mechanically.
You need this if any of these are true
The notes file
There is a file, a note or a chat thread holding credential values, and nobody is certain which are current
The duplicate problem
The same credential name appears more than once with different values, and no dates
The rotation fear
Something ought to be rotated but nobody will, because nobody knows what it would break
The bus factor
One person knows which key does what, and it lives in their head
The silent failure
A secret has gone missing at some point and nothing obviously broke — which is worse, not better
How to read this
The Five Kinds
The taxonomy. The part that transfers to any stack, any cloud, any language
Worked Example
The method applied end to end to an illustrative four-service estate
Blast Radius
What breaks per kind, including the two cases that surprise everyone
Handling Rules
Keep or delete, where the keepers live, and how to generate a secret without ever seeing it
Run It Yourself
The audit procedure, with the commands
The Five Kinds
why one word means five different things
"Token" gets used for all five of these, which is exactly why they blur together in a notes file. They differ on three questions: who made it, who can read it back, and what happens when it changes. Place a value in one of these boxes and you always know what to do with it.
Kind
Made by
Readable later?
Typical names
Where you meet it
1. Shared secret
You, from randomness
No — write-only once set
Token, admin key
Gating your own internal API
2. API key
A vendor, for your account
Shown once at creation
API key, secret key
Any paid or metered third-party service
3. OAuth token
A service, after a user clicks Allow
Never shown to you at all
Access token, refresh token
"Connect your account" flows
4. Minted key
Your own app, on demand
Once, at creation, then never
Token, key, invite
Giving a second user scoped access
5. Identifier
Anyone — it names a thing
Always; it is public
ID, account id, client id
Config files, committed to the repo
Identifiers are not secrets. An id names something; a secret proves you are allowed to use it. Database ids and client ids belong in your committed config, and that is correct — knowing the id of a database gets an attacker precisely nowhere without a credential. Half the anxiety in a typical notes file is spent guarding values that were never sensitive.
A secret's whole life. Every stray value in a notes file came from a recipe that skipped the pipe and printed to the screen instead.
Kind 1 — Shared secret
A long random string acting as a password with no username. Whoever holds it is trusted completely, so it is only ever as safe as the place you keep it.
How to make one
openssl rand -base64 32 — 32 bytes of randomness, about 256 bits. Unguessable in any practical sense.
Where it goes
Straight into the platform's secret store, by pipe, never via the clipboard
Who reads it
Only your service, at runtime. A well-built secret store shows the name and never the value — including to you.
Lost it?
Generate a new one and set it. There is no recovery, and no need for one.
Design note
Compare it in constant time. A plain string comparison leaks length and prefix information through timing — a small nit over a network, free to avoid.
Kind 2 — API key
Issued by a vendor so their servers know which account to bill and rate-limit. You did not create it and cannot recreate the same one — you can only ask for another.
Shape
Usually prefixed so you can identify them on sight. The prefix is a deliberate feature: it lets scanners spot a leaked key in a repo.
Shown
Once, at creation. Assume every console works this way and that you cannot come back for it.
Revoking
From the vendor's dashboard. Revoked keys die immediately everywhere they were used — which is the point.
Why it matters most
An API key is usually attached to billing. A leaked one spends your money, which makes it the practical priority over almost anything else on this page.
Cost control
Where the vendor supports it, issue a separate key per service. One leak then has a blast radius of one service, and per-key spend tells you which service is expensive.
Kind 3 — OAuth token
The kind you never see. A user clicks "Allow" on a screen they recognise, and the service hands your application a token directly, behind the scenes. OAuth = Open Authorization, the standard behind every "Sign in with…" button.
Why it exists
So a user never types their password into someone else's application. The password stays with the service; your app gets a limited, revocable token instead.
Scoped
Granted for named permissions only. A read-only integration should request read-only scopes — asking for more is both a security and a conversion problem, because the consent screen lists them.
Expires
By design. Long-lived tokens typically last weeks and must be refreshed before they lapse — which means a scheduled job, and a plan for what happens when that job fails.
Storing it
Encrypted at rest, always. It is a live credential belonging to a person, not a config value.
Revoking
From the service's own settings, or by disconnecting in your app. Neither needs the token's value.
Why a platform login gives you nothing to write down. Most cloud command-line tools authenticate by OAuth: a browser round-trip stores a token in a local config file you never see. That is why "where is my deploy token?" often has no answer — there isn't one, and you don't need one.
Kind 4 — Minted key
A credential your own application creates on request, so a second person can use the system without being handed the admin secret. The kind most small builds skip, and then regret skipping.
How it works
Generate a prefixed random string, show it once, and store only its cryptographic hash. A hash is one-way: the database can check a token it is shown, but cannot reproduce one.
Why hash it
So a copy of your database is not a set of working keys. Even with full read access, nobody can sign in. This is the single highest-value hour in most small builds.
Scoped
Each key opens exactly one account or tenant, and cannot mint further keys. Admin is a separate credential, not a flag on this one.
Revocable
Individually, and immediately, without touching anyone else's access. Compare with a single shared password, where removing one person's access means changing everyone's.
Lost it?
Unrecoverable by design. Revoke and mint another — the intended path, not a failure.
Kind 5 — Identifier
Names a resource. Carries no permission at all. These are the values it is safe to paste anywhere, and the ones you can genuinely look up again whenever you want.
An identifier usually has a matching secret somewhere with a near-identical name. "Client id" is public; "client secret" is not. Same application, two halves, opposite handling.
Why the public half is safe
It has to travel in the open — it is in the URL of the consent screen the user just clicked. Security comes from the secret half, which never leaves your server.
Watch out: being long and random-looking does not make a value a secret — most identifiers look exactly like that · "id" and "secret" pairs are treated completely differently despite arriving together · a value you can look up again is not worth storing · anything printed to a terminal has entered shell history, which is a file on disk.
Worked Example
the method applied to a four-service estate
A small operator running four serverless services on one cloud account — a marketing site, an internal ops app, a private data API, and a scheduled integration. This is the shape a solo builder or a two-person shop actually ends up with after a year.
Illustrative example. The services below are invented for this walkthrough. They are modelled on the pattern such estates converge on rather than describing any particular deployment, and the numbers are there to show the shape of the result.
Step 1 — Inventory, from the deployed source
Read what each service actually reads at runtime. Documentation drifts; a config file may list bindings that no longer exist; memory is worst of all.
Service
What it does
Bindings it reads
Marketing site
Public pages plus an inquiry form
1 secret, 2 config values, 2 resource bindings
Internal ops app
The tool the business actually runs on
4 secrets, 3 config values, 2 resource bindings
Private data API
Serves a companion tool
1 secret, 1 resource binding
Admin surface
Behind the identity provider
0 secrets, 3 identifiers, 3 resource bindings
The first finding is usually the count. Fourteen credential-shaped things across four services — and the owner expected about five. The gap is never carelessness; it is that six of them were set once, months apart, and never thought about again.
Step 2 & 3 — Classify, then apply the copy test
What it is
Kind
Count
Keep a copy?
Admin key for the ops app
Shared secret
1
Yes — typed at sign-in
Bearer token for the private API
Shared secret
1
Yes — callers send it
Per-user keys minted by the app
Minted key
n
Yes, if handed to a person
Vendor API keys
API key
3
No — reissue on demand
Encryption key for stored tokens
Shared secret
1
No — never typed by hand
Third-party user tokens
OAuth token
2
No — never visible
Cloud + code-host logins
OAuth token
2
No — no value exists
Ids, audience tags, client ids
Identifier
4
Not secret at all
The typical result. The ratio matters more than the absolute numbers: most of what people guard does not need guarding, and the few things that do get lost in the noise.
Two findings the inventory produced
Both are the kind that only surface when you read the source rather than the config.
A silent fail-open
The marketing site's anti-bot check was written to accept submissions if its verification secret was missing, so a real customer is never blocked by a configuration mistake. That is the right call — and it means a missing secret produces no visible symptom at all. The fix is not to change the behaviour; it is to know, and to check the secret is present as part of the audit.
A coupled pair
The encryption key and the stored third-party tokens are joined: rotating the key makes every stored token unreadable. Nothing is lost but the connections, which must be re-established. Nobody would guess this from the credential's name, which is exactly why it belongs in a written blast-radius table.
What made both findings possible: reading the deployed source, not the configuration file and not the person's recollection. A config file lists what was intended. The source shows what is read. In an estate that has been iterated on, those two drift apart within months.
Blast Radius
what actually stops working
Replacing a credential is one command. Knowing what breaks is the part that decides whether you do it on a Tuesday morning or at midnight during an outage. This table is the deliverable most teams have never written down.
Kind
Replace it and…
Recovery
Safe to do casually?
Shared secret — user-facing
Every device signed in with it is locked out at the next request
Distribute the new value to each holder
Only if you can reach every holder
Shared secret — service-to-service
Every calling service fails until updated
Update caller and callee together, callee last
No — sequence it
API key
That vendor's features stop until the new key lands
None needed if the app degrades gracefully
Yes — the safest kind to rotate
Encryption key
Everything encrypted with it becomes permanently unreadable
Re-establish whatever the ciphertext represented
No — this is the dangerous one
OAuth client secret
New connections and refreshes fail; existing sessions run until their next refresh
Set the new one before the refresh window closes
Yes, with a deadline
OAuth access token
Not yours to rotate — the service manages it
Reconnect if it errors
n/a
Minted key
Exactly one user
Mint them another
Yes — that is the point of them
Identifier
Nothing. It is a name.
n/a
n/a
The one everyone gets wrong. An encryption key is not a password — it is the key to a lock box. Changing a password locks people out until they learn the new one. Changing an encryption key means the box never opens again: the data encrypted under the old key is gone, not merely inaccessible. Rotate it deliberately, with a plan for re-establishing whatever it protected, and never "just to be safe".
When rotation is actually warranted
Definitely
The value appeared somewhere it could be read — a screen share, a chat, a screenshot, a ticket, a log, a shared file
Definitely
Someone who had it should no longer have it
Yes, calmly
You are unsure whether it leaked. Rotation costs minutes; a live leak on a billed API key costs money continuously.
No
On a calendar, for its own sake. Scheduled rotation without a triggering event mostly generates outages and false confidence.
No
Because you found an old value and cannot tell if it is live. Test it instead — that is a lookup, not a decision.
Watch out: rotating a service-to-service secret without sequencing takes both sides down · a secret that is cleared rather than replaced can leave a fail-open path silently unguarded · "we rotate quarterly" is a policy, not a control, unless someone verifies afterwards that everything still works.
Handling Rules
keep, delete, and how to never see a secret
The keep rule, in one sentence: keep a copy only if a human will have to type it in later. In a typical small estate that is two or three items out of a dozen or more. Everything else is set once by pipe, reissued on demand from a dashboard, or never visible to you in the first place.
Keep
What
Why it qualifies
Sign-in credentials for your own tools
Typed on every new device
Service-to-service bearer tokens
The caller has to be configured with it
Keys you issued to another person
They may lose it, and a hashed key cannot be looked up
Delete
What
Why it does not qualify
Vendor API keys
The console issues another in seconds
Encryption keys
Never typed by hand; keeping a copy is the only way it can leak
OAuth client secrets
The provider reissues on request
Anything unlabelled
An unidentifiable secret is already useless
Duplicates of one name
Only the newest can be live; the rest died on the day the next was set
Ids of any kind
Not secret, and always look-up-able
Generating without ever seeing it
Every recipe here ends in a pipe, so the value is never printed, never enters shell history, and never touches the clipboard. The examples use a serverless CLI, but the shape is the same for any secret store.
Generate and set, in one breath
openssl rand -base64 32 | tr -d '\n' \
| your-cli secret put ENCRYPTION_KEY
Nothing displayed. Nothing to copy.
Nothing to paste anywhere by accident.
When you DO need to keep it
T=$(openssl rand -base64 32 | tr -d '=+/' \
| cut -c1-40)
printf '%s' "$T" | your-cli secret put ADMIN_TOKEN
printf '%s' "$T" | pbcopy
Now it is on the clipboard and nowhere
else. Paste into the password manager,
then copy something else to clear it.
A key copied from a vendor console
K=$(pbpaste | tr -d '[:space:]')
printf %s "$K" | your-cli secret put VENDOR_KEY
The tr strips any stray newline the website
appended. An invisible trailing character is
the single most common reason a freshly-set
key comes back "unauthorized".
Confirm what is set
your-cli secret list
Prints names only, never values. A store
that can show you a secret back is a store
that can leak it — the absence of that
command is a feature.
Is an old value alive? — test, don't guess
Ask the server rather than reasoning about it. Any endpoint that requires authentication and changes nothing will do.
curl -sS -o /dev/null -w '%{http_code}\n' \
-H "Authorization: Bearer PASTE_THE_VALUE" \
https://your-service.example/api/me
200 → alive. Now: whose is it, and should it still be?
401 → dead. Delete the note; it cannot be revived.
Use a read-only endpoint, and run it in a terminal rather than any tool that logs requests. A value that returns 200 and cannot be attributed to a known holder is itself a reason to rotate.
Where the keepers live
Password manager
Encrypted at rest, syncs to phone, survives a laptop failure. A notes app is none of those things and is usually open during screen shares.
Named, not pasted
"Ops app — admin sign-in" beats a bare string. An unlabelled secret is one you will regenerate rather than trust.
One entry per name
On rotation, edit the existing entry. Never add a second — that is how the pile starts.
Not in shell history
Anything typed or printed in a terminal is in a history file on disk. The pipe recipes above avoid this entirely.
Not in a repo
Even a private one. Keep a committed .example file carrying the names with empty values — it documents what must be set without carrying anything.
Watch out: a secret pasted into a chat, ticket or screenshot is leaked even in a private thread — rotate rather than reason about who saw it · clearing a shell history file does not clear the current session's buffer · "I'll tidy this later" is how five dead values end up indistinguishable from one live one.
Run It Yourself
the audit, start to finish
An afternoon's work for a small estate. The output is one table, and the value is almost entirely in step 1 — everything else is mechanical once the list is honest.
The procedure
#
Do this
Why this way
1
List every deployed service on the account, not just the ones you remember
The forgotten ones are where the stale credentials are
2
For each, read the deployed source for environment reads — env.SOMETHING, destructuring, config objects
Config files list intent; source shows what is actually read
3
Classify each into one of the five kinds by how it is used, not what it is named
Names lie. A variable called …_KEY is often an identifier.
4
Ask the copy question for each; mark the two or three keepers
This is the answer people actually wanted
5
Write the blast-radius line for each. If you cannot, that is the finding.
An unknown blast radius is why nothing ever gets rotated
6
Confirm every secret the source reads is actually set, in every environment
Fail-open paths are silent by definition
7
Delete the notes file, having moved the keepers to a password manager
The exercise is not finished until the pile is gone
Commands that do the reading
Every environment read, per service
grep -rhoE "env\.[A-Z][A-Z0-9_]+" src/ | sort -u
Catches the ones no config file mentions.
Also check for destructuring and bracket
access if the codebase uses them:
grep -rn "} = env" src/
grep -rn 'env\[' src/
What is actually set
your-cli secret list --name SERVICE
Does the source read anything not set?
Compare the two lists. A name in the source
and missing from the store is either dead
code or a silent fail-open. Both are worth
knowing; only one is urgent.
What the finished artefact looks like
Five columns. Anyone on the team should be able to act from it without asking whoever built the thing.
Name
Kind
Service
Keep a copy?
Replace it and…
…
one of five
which service
yes / no
one sentence
Keep it where the work is. This table belongs beside the code or in the runbook, not in a wiki nobody opens. It changes when a service changes, and a document that lives away from the thing it describes is wrong within a quarter.
What the exercise is actually worth
Immediately
The notes file goes away, and with it most of the surface area a leak would come from
Next incident
Rotation becomes a five-minute task instead of a risk assessment nobody wants to run
Next hire
Credential handling stops living in one person's head
Quietly
You find the two or three things that were silently misconfigured, because you looked at the source instead of asking
Topic Index
Every concept and command in this codebook. Type to filter; click to jump.