Secrets at Rest & in Transit
A secret stays secret only as long as its worst-handled copy stays hidden. Secrets usually leak not from the vault but from a config file, a log line, an error message, a chat thread, or a screenshot. Protect every place a secret can rest or pass through, not just the one you designed.
Secrets management has two jobs: keep secrets out of everywhere they should not be (source control, logs, plaintext config, messages), and protect them everywhere they are allowed to be (a managed vault, encrypted transport, memory). Get the first wrong and the second does not matter.
The Finperiti sample held its secrets in raw configuration with no Key Vault. That meant every secret was one repo clone, one misconfigured log, or one leaked config file away from exposure. The fix is structural: a vault as the single source, references instead of values, and disciplined transport.
Keep secrets out
- AlwaysStore every secret in a managed vault (Azure Key Vault) and reference it at runtime. Application code sees a reference, not a value.
- DoRun automated secret scanning in CI and pre-commit, so a committed secret fails the build before it merges.
- DoInject secrets through the platform's secret mechanism (Key Vault references, managed identity) rather than baking them into images or files.
- NeverCommit a secret to source control — keys, connection strings, certificates, client secrets. Not even for a moment, and not even in a private repo.
- NeverStore secrets in plaintext — in config files, env files, IaC state, database columns, or logs.
- NeverHard-code a secret in application code, tests, or pipeline definitions.
- NeverSend a credential, token, key, or password over email, chat, SMS, ticket, or screenshot. Share access only through the approved vault.
"ConnectionStrings": {
"Db": "Server=...;Password=P@ssw0rd!;"
}
It is committed to the repo and readable by anyone with clone access or a leaked config file. The password should be a Key Vault reference resolved at runtime.
"ConnectionStrings": {
"Db": "@Microsoft.KeyVault(SecretUri=https://kv.../db-conn)"
}
No secret in the repo. The app's managed identity reads it from the vault at startup, and rotation happens in one place.
Protect secrets where they live & travel
- DoEncrypt sensitive data at rest (transparent DB encryption, plus column or field encryption for the most sensitive), and all traffic in transit with current TLS.
- DoScope and rotate. Each secret has the narrowest access and shortest life that works, and a tested way to rotate it.
- DoRedact secrets from logs, telemetry, and error responses by default. Assume anything you log may be read by someone who should not see it.
- ConsiderShort-lived, automatically-issued credentials (managed identity tokens) instead of long-lived static secrets wherever the platform allows.
- Do notPass secrets as URL query parameters or store them in browser-accessible storage.
- NeverReuse one secret across environments or services, or leave a known-exposed secret in place. Rotate it at once and treat it as a breach.
Self-review checklist
- AskIs there a secret value — not a reference — anywhere in this diff?
- AskCould any secret end up in a log, an exception message, or telemetry?
- AskIs this data encrypted both at rest and in transit?
- AskIf this secret leaked, could I rotate it without a code change?