The Credential Inventory_

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

StepWhat you doWhat it produces
1. InventoryRead 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. ClassifyPut each one in one of five boxes: shared secret, API key, OAuth token, minted key, identifierCorrect handling stops being a decision
3. Decide the copyApply 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 radiusFor each one, write down what stops working if it is replacedThe document nobody has and everybody needs at 11pm
1. Inventory read the deployed source, not the docs 2. Classify five kinds — the whole taxonomy 3. Decide the copy will a human type this again? 4. Blast radius what breaks when you replace it The output is one page. Not a policy, not a vault migration — a table anyone on the team can read in ninety seconds and act on without asking whoever set the thing up eighteen months ago. FOUR STEPS, ONE AFTERNOON
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 fileThere is a file, a note or a chat thread holding credential values, and nobody is certain which are current
The duplicate problemThe same credential name appears more than once with different values, and no dates
The rotation fearSomething ought to be rotated but nobody will, because nobody knows what it would break
The bus factorOne person knows which key does what, and it lives in their head
The silent failureA secret has gone missing at some point and nothing obviously broke — which is worse, not better

How to read this

The Five KindsThe taxonomy. The part that transfers to any stack, any cloud, any language
Worked ExampleThe method applied end to end to an illustrative four-service estate
Blast RadiusWhat breaks per kind, including the two cases that surprise everyone
Handling RulesKeep or delete, where the keepers live, and how to generate a secret without ever seeing it
Run It YourselfThe 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.

KindMade byReadable later?Typical namesWhere you meet it
1. Shared secretYou, from randomnessNo — write-only once setToken, admin keyGating your own internal API
2. API keyA vendor, for your accountShown once at creationAPI key, secret keyAny paid or metered third-party service
3. OAuth tokenA service, after a user clicks AllowNever shown to you at allAccess token, refresh token"Connect your account" flows
4. Minted keyYour own app, on demandOnce, at creation, then neverToken, key, inviteGiving a second user scoped access
5. IdentifierAnyone — it names a thingAlways; it is publicID, account id, client idConfig 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.
1. Generated from randomness 2. Stored the platform's secret store 3. Used on every request 4. Replaced go back to step 1 piped, never printed write-only read by the server only the old value is gone the instant the new one lands Notice what is missing: no step displays, copies or saves the value. If a secret appears on screen, the recipe was wrong. THE LIFE OF A SHARED SECRET
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 oneopenssl rand -base64 32 — 32 bytes of randomness, about 256 bits. Unguessable in any practical sense.
Where it goesStraight into the platform's secret store, by pipe, never via the clipboard
Who reads itOnly 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 noteCompare 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.

ShapeUsually prefixed so you can identify them on sight. The prefix is a deliberate feature: it lets scanners spot a leaked key in a repo.
ShownOnce, at creation. Assume every console works this way and that you cannot come back for it.
RevokingFrom the vendor's dashboard. Revoked keys die immediately everywhere they were used — which is the point.
Why it matters mostAn 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 controlWhere 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 existsSo 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.
ScopedGranted 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.
ExpiresBy 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 itEncrypted at rest, always. It is a live credential belonging to a person, not a config value.
RevokingFrom 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 worksGenerate 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 itSo 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.
ScopedEach key opens exactly one account or tenant, and cannot mint further keys. Admin is a separate credential, not a flag on this one.
RevocableIndividually, 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.

ExamplesAccount ids, database ids, application ids, public client ids, audience tags
Where they liveCommitted config. Correct and normal.
The tellAn 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 safeIt 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.

ServiceWhat it doesBindings it reads
Marketing sitePublic pages plus an inquiry form1 secret, 2 config values, 2 resource bindings
Internal ops appThe tool the business actually runs on4 secrets, 3 config values, 2 resource bindings
Private data APIServes a companion tool1 secret, 1 resource binding
Admin surfaceBehind the identity provider0 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 isKindCountKeep a copy?
Admin key for the ops appShared secret1Yes — typed at sign-in
Bearer token for the private APIShared secret1Yes — callers send it
Per-user keys minted by the appMinted keynYes, if handed to a person
Vendor API keysAPI key3No — reissue on demand
Encryption key for stored tokensShared secret1No — never typed by hand
Third-party user tokensOAuth token2No — never visible
Cloud + code-host loginsOAuth token2No — no value exists
Ids, audience tags, client idsIdentifier4Not secret at all
WORTH KEEPING SET AND FORGET NOT SECRET 3 7 4 a human types these password manager, named, one entry each reissuable or invisible secret store only — a copy is pure risk identifiers committed config, look them up any time Of fourteen credential-shaped values, three belong anywhere other than the machine. Before the exercise, all fourteen were in a notes file.
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-openThe 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 pairThe 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.

KindReplace it and…RecoverySafe to do casually?
Shared secret — user-facingEvery device signed in with it is locked out at the next requestDistribute the new value to each holderOnly if you can reach every holder
Shared secret — service-to-serviceEvery calling service fails until updatedUpdate caller and callee together, callee lastNo — sequence it
API keyThat vendor's features stop until the new key landsNone needed if the app degrades gracefullyYes — the safest kind to rotate
Encryption keyEverything encrypted with it becomes permanently unreadableRe-establish whatever the ciphertext representedNo — this is the dangerous one
OAuth client secretNew connections and refreshes fail; existing sessions run until their next refreshSet the new one before the refresh window closesYes, with a deadline
OAuth access tokenNot yours to rotate — the service manages itReconnect if it errorsn/a
Minted keyExactly one userMint them anotherYes — that is the point of them
IdentifierNothing. It is a name.n/an/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

DefinitelyThe value appeared somewhere it could be read — a screen share, a chat, a screenshot, a ticket, a log, a shared file
DefinitelySomeone who had it should no longer have it
Yes, calmlyYou are unsure whether it leaked. Rotation costs minutes; a live leak on a billed API key costs money continuously.
NoOn a calendar, for its own sake. Scheduled rotation without a triggering event mostly generates outages and false confidence.
NoBecause 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

WhatWhy it qualifies
Sign-in credentials for your own toolsTyped on every new device
Service-to-service bearer tokensThe caller has to be configured with it
Keys you issued to another personThey may lose it, and a hashed key cannot be looked up

Delete

WhatWhy it does not qualify
Vendor API keysThe console issues another in seconds
Encryption keysNever typed by hand; keeping a copy is the only way it can leak
OAuth client secretsThe provider reissues on request
Anything unlabelledAn unidentifiable secret is already useless
Duplicates of one nameOnly the newest can be live; the rest died on the day the next was set
Ids of any kindNot 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 managerEncrypted 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 nameOn rotation, edit the existing entry. Never add a second — that is how the pile starts.
Not in shell historyAnything typed or printed in a terminal is in a history file on disk. The pipe recipes above avoid this entirely.
Not in a repoEven 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 thisWhy this way
1List every deployed service on the account, not just the ones you rememberThe forgotten ones are where the stale credentials are
2For each, read the deployed source for environment reads — env.SOMETHING, destructuring, config objectsConfig files list intent; source shows what is actually read
3Classify each into one of the five kinds by how it is used, not what it is namedNames lie. A variable called …_KEY is often an identifier.
4Ask the copy question for each; mark the two or three keepersThis is the answer people actually wanted
5Write the blast-radius line for each. If you cannot, that is the finding.An unknown blast radius is why nothing ever gets rotated
6Confirm every secret the source reads is actually set, in every environmentFail-open paths are silent by definition
7Delete the notes file, having moved the keepers to a password managerThe 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.

NameKindServiceKeep a copy?Replace it and…
one of fivewhich serviceyes / noone 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

ImmediatelyThe notes file goes away, and with it most of the surface area a leak would come from
Next incidentRotation becomes a five-minute task instead of a risk assessment nobody wants to run
Next hireCredential handling stops living in one person's head
QuietlyYou 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.

The same four questions, asked of every kind

KindRead it back?Who reissuesKeep a copy?Blast radius
Shared secretNoYouOnly if a human types itEvery holder, at once
API keyOnce, at creationThe vendorNoThat vendor's features
OAuth tokenNeverThe serviceNoManaged for you
Minted keyOnce, at creationYour appIf you gave it awayOne user
IdentifierAlwaysn/a — publicNo, it's not secretNothing