Safeguard
Security Guides

.NET Secrets Management: From User Secrets to Key Vault

How to keep connection strings, API keys, and certificates out of your .NET source and images, using the Secret Manager, environment configuration, managed identities, and a real vault.

Marcus Chen
AppSec Engineer
5 min read

The fastest way to leak credentials in a .NET app is to put them where they're convenient: appsettings.json. It's readable, it's checked into git, and it ends up baked into your container image and your build logs. Secrets management is the discipline of keeping connection strings, API keys, tokens, and certificates out of source and out of images — and making them available to the app only at runtime, from a place designed to guard them. This guide walks the .NET secrets story from local development to production, using the configuration system .NET already gives you.

Why config files are the wrong place for secrets

The .NET configuration system layers providers — JSON files, environment variables, command line, and external stores — into a single IConfiguration. That layering is the feature: you can keep non-secret settings in appsettings.json and inject secrets from a provider that never touches source control. The moment a secret lands in a committed file, three problems appear at once: it's in git history forever (deleting it in a later commit doesn't remove it), it's in every clone and fork, and it's in the built image's layers. Remediation isn't "delete the line" — it's "rotate every exposed credential," which is expensive and error-prone. The goal is to make that mistake structurally impossible.

Local development: the Secret Manager

For development, .NET ships the Secret Manager (user secrets), which stores values outside the project directory so they can't be committed:

dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Db" "Server=...;Password=..."

Values live in your user profile (for example, under %APPDATA%\Microsoft\UserSecrets\<id> on Windows), and the configuration system reads them automatically in the Development environment:

var conn = builder.Configuration.GetConnectionString("Db");

Two caveats keep this honest: user secrets are stored in plaintext — they protect against accidental commits, not against someone with access to your machine — and they are a development-only mechanism. Never use them for production.

Production: a managed vault plus a managed identity

In production, secrets should come from a dedicated store — Azure Key Vault, AWS Secrets Manager, HashiCorp Vault, or your platform's equivalent — bound through a configuration provider. The critical detail is how the app authenticates to the vault. The wrong answer is another secret (a vault password in an environment variable creates the same problem one level up). The right answer is a workload/managed identity, so no bootstrap secret exists:

builder.Configuration.AddAzureKeyVault(
    new Uri("https://my-vault.vault.azure.net/"),
    new DefaultAzureCredential());   // uses the managed identity, no secret

DefaultAzureCredential picks up the managed identity in production and falls back to developer credentials locally, so the same code works in both places. This gives you central rotation, access auditing, and revocation — none of which a config file offers.

When you must use environment variables

Container platforms often inject configuration as environment variables, and .NET reads them natively (with : written as __ for nested keys, e.g. ConnectionStrings__Db). They're acceptable as a transport when the platform's secret store populates them at runtime, but be aware of the pitfalls: environment variables are visible to the whole process and often to diagnostics endpoints, they can end up in crash dumps and logs, and a Dockerfile ENV line bakes them into the image permanently. Prefer platform secret mounts (Kubernetes secrets as files, for example) or the vault provider over ENV in a Dockerfile.

Catch leaks before they're pushed

Prevention beats cleanup. Add a secret scanner to your workflow so a mistyped credential never reaches a remote branch:

  • Run gitleaks or trufflehog as a pre-commit hook and as a CI step.
  • Enable your platform's push protection (secret scanning that blocks the push).
  • Scan the whole history when onboarding a repo — leaks often predate your policy.

Pair this with least-privilege: the credentials an app holds should be scoped to exactly what it needs, and short-lived where possible, so a leak's blast radius is small and its window is short.

A secrets management checklist

EnvironmentStoreAuth to store
Local devSecret Manager (user secrets)N/A (local file)
CICI secret store (masked)Scoped, rotated tokens
ProductionKey Vault / Secrets ManagerManaged / workload identity
  • No secrets in appsettings*.json, web.config, or source
  • Secret Manager for local dev; understood as plaintext + dev-only
  • Vault provider + managed identity in production
  • No secrets in Dockerfile ENV; use platform secret mounts
  • Secret scanning as a pre-commit hook and CI gate; push protection on
  • Credentials least-privilege, scoped, and rotated

Infrastructure carries secrets too. A scan of your Terraform, Helm, and Kubernetes manifests catches secrets committed into infrastructure-as-code and misconfigured vault access policies that application-level scanning misses.

How Safeguard Helps

Safeguard closes the loop between "we have a policy" and "the policy is enforced." It scans your repositories and CI for exposed secrets — connection strings, API keys, tokens, private keys — and flags them with the file, commit, and blast radius identified, so rotation is targeted rather than guesswork. Its infrastructure-as-code scanning extends that to secrets and overly broad access policies embedded in your deployment manifests. You can run the same checks locally and in CI through the Safeguard CLI, and the platform correlates a leaked credential with the dependencies and services it can reach so you understand real impact. Teams consolidating secret and dependency scanning often compare it on the Safeguard vs Mend page.

Start free at app.safeguard.sh/register and read the secret-scanning setup at docs.safeguard.sh.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.