GM.Secrets.Vault
2.0.0
dotnet add package GM.Secrets.Vault --version 2.0.0
NuGet\Install-Package GM.Secrets.Vault -Version 2.0.0
<PackageReference Include="GM.Secrets.Vault" Version="2.0.0" />
<PackageVersion Include="GM.Secrets.Vault" Version="2.0.0" />
<PackageReference Include="GM.Secrets.Vault" />
paket add GM.Secrets.Vault --version 2.0.0
#r "nuget: GM.Secrets.Vault, 2.0.0"
#:package GM.Secrets.Vault@2.0.0
#addin nuget:?package=GM.Secrets.Vault&version=2.0.0
#tool nuget:?package=GM.Secrets.Vault&version=2.0.0
GM.Secrets
Layers an app's secrets into IConfiguration, so code reads them like any other setting —
IConfiguration["ApiKey"], IOptions<T> / IOptionsMonitor<T>, connection strings — without
knowing whether a value came from appsettings, a local file, or a secret store. Which store (Doppler,
AWS Secrets Manager, Azure Key Vault, HashiCorp Vault) is picked per environment with one environment
variable, SECRETS_PROVIDER.
GM.Secrets—builder.AddGMSecrets(...): the localappsecrets.jsonlayer, store selection, and loading the store into configuration (with optional reload for rotation).GM.Secrets.Doppler— a Doppler config, via its REST API and a service token.GM.Secrets.AwsSecretsManager— AWS Secrets Manager, viaAWSSDK.SecretsManager.GM.Secrets.AzureKeyVault— Azure Key Vault, viaAzure.Security.KeyVault.Secrets+DefaultAzureCredential.GM.Secrets.Vault— HashiCorp Vault KV v2, via its HTTP API (token or AppRole).
Install
dotnet add package GM.Secrets
dotnet add package GM.Secrets.Doppler # and/or the store(s) you deploy with
dotnet add package GM.Secrets.AwsSecretsManager
dotnet add package GM.Secrets.AzureKeyVault
dotnet add package GM.Secrets.Vault
Register
builder.AddGMSecrets();
That's all the code. The store is chosen by environment variables: SECRETS_PROVIDER=AzureKeyVault
loads the referenced GM.Secrets.AzureKeyVault package and configures it from its own variables
(AZURE_KEYVAULT_URI, ...). Only that package is loaded; the others are never touched. Reference
the store packages the app can be deployed with — an app that only runs on Azure needs just
GM.Secrets.AzureKeyVault.
To set a store's options in code instead, register it; that registration wins over the package's defaults:
builder.AddGMSecrets(secrets => secrets.AddVault(o => o.MountPoint = "kv"));
Then read secrets the usual way:
var apiKey = builder.Configuration["Payments:ApiKey"];
builder.Services.Configure<DbCredentials>(builder.Configuration.GetSection("DbCredentials"));
public sealed class PaymentClient(IOptionsMonitor<DbCredentials> db) { /* db.CurrentValue */ }
Never log a secret value; error messages from the stores carry names and status codes only.
Configuration layers
Lowest to highest precedence:
appsettings.json → appsettings.{Environment}.json
→ appsecrets.json → appsecrets.{Environment}.json (AddGMSecrets — any environment, when present)
→ the store SECRETS_PROVIDER selects (AddGMSecrets — none when unset)
→ environment variables → command line
appsecrets.jsonholds local secret values. Keep it gitignored; the package also keepsappsecrets*.jsonout ofdotnet publish. A JSON object there ("Db": { "User": "..." }) is a section like any other.SECRETS_FILEpoints the layer at a file elsewhere; turn it off withsecrets.AppSecretsFile.Enabled = false.- The store overrides the files: a key it has wins, a key it lacks falls back to appsecrets and
appsettings. It is loaded at startup (a failure stops startup) and, with
SECRETS_RELOAD_INTERVAL, re-fetched on that interval —IConfigurationandIOptionsMonitor<T>see the new values without a restart (IOptions<T>doesn't; it is read once). A failed refresh keeps the last values. Stores that aren't selected are never created.
Environment variables
The settings that decide where secrets come from are read from process environment variables
only — never appsettings, appsecrets.json, the command line or a store — so a store's credentials
never sit next to the secrets they unlock.
| Variable | Default | Does |
|---|---|---|
SECRETS_PROVIDER |
none | the store to load: Doppler, AwsSecretsManager, AzureKeyVault or Vault; unset loads none |
SECRETS_RELOAD_INTERVAL |
off | re-fetch the store this often, e.g. 00:05:00 |
SECRETS_FILE |
appsecrets.json in the content root |
the local secrets file, anywhere: absolute or relative to the content root, e.g. D:\secrets\myapp.json (must exist; myapp.{Environment}.json beside it is loaded too) |
A SECRETS_PROVIDER whose package isn't referenced, a malformed value, or a store missing its
settings fails at startup with the variable's name (never its value).
Security
What the library does:
- Credentials only from the environment.
SECRETS_PROVIDERand every store's address and credentials are read from process environment variables — never from appsettings,appsecrets.json, the command line or a store — so the place secrets come from is never set by those secrets. - No secret values in errors. Failures name the variable, the path or the HTTP status; a malformed variable's value is left out of its error, and a store's token never appears in one.
- Tokens only over TLS. Vault and Doppler refuse a plain
httpaddress unless it is on this machine (a dev server), since their tokens travel in request headers. Vault mounts and paths are checked so a value can't step to another endpoint (..,?,#). - Only the selected store is loaded.
SECRETS_PROVIDERmust be a plain name; onlyGM.Secrets.{name}is loaded, and unselected stores are never created. - Local secrets stay local.
appsecrets*.jsonin the project is excluded fromdotnet publish.
What you do:
- Keep credentials (
DOPPLER_TOKEN,VAULT_TOKEN,AWS_SECRET_ACCESS_KEY,AZURE_CLIENT_SECRET) in environment variables — user variables locally, the platform's secret settings when deployed — and out of committed files such aslaunchSettings.json. In Azure and AWS prefer a managed identity / IAM role, which needs no credential at all. - Gitignore
appsecrets*.json, and never put a real secret inappsettings*.json. - Give each app's identity read access to its own secrets only (Key Vault Secrets User,
secretsmanager:GetSecretValueon one ARN, a Vault policy on one path, a Doppler service token per config). Rotate anything that was committed or shared. - Never log configuration values; secrets are ordinary configuration once loaded.
Secret stores
| Store | SECRETS_PROVIDER |
Variables | Secret names → configuration keys |
|---|---|---|---|
| Doppler | Doppler |
DOPPLER_TOKEN, optional DOPPLER_PROJECT, DOPPLER_CONFIG (with a personal or CLI token it defaults from the app environment: Development → dev, Staging → stg, Production → prd), DOPPLER_API_URL |
SAMPLE__USER → SAMPLE:USER (keys are case-insensitive) |
| AWS Secrets Manager | AwsSecretsManager |
AWS_SECRETS_MANAGER_SECRET_IDS (comma-separated), AWS_REGION, AWS_ENDPOINT_URL_SECRETS_MANAGER (emulators); credentials from the AWS default chain (IAM role, AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY, AWS_PROFILE) |
a JSON secret is flattened (__ and nesting → sections); a plain secret is keyed by its name (/ → :) |
| Azure Key Vault | AzureKeyVault |
AZURE_KEYVAULT_URI, optional AZURE_KEYVAULT_PREFIX; credentials from DefaultAzureCredential (managed identity, az login, Visual Studio, or an app registration: AZURE_TENANT_ID, AZURE_CLIENT_ID, AZURE_CLIENT_SECRET) |
Sample--User → Sample:User; enabled, unexpired secrets only |
| HashiCorp Vault | Vault |
VAULT_ADDR (https; plain http only for this machine); VAULT_TOKEN, or VAULT_ROLE_ID + VAULT_SECRET_ID (AppRole), else the vault login token in ~/.vault-token; VAULT_PATHS (comma-separated), VAULT_MOUNT (secret), VAULT_NAMESPACE |
KV v2 data flattened (nesting and __ → sections) |
Lists are comma-separated: VAULT_PATHS=myapp/shared,myapp/production (later ones win). Every store's
options can also be set in code: .AddVault(o => ...). On a developer machine the stores' own logins
work as-is: vault login, aws sso login / AWS_PROFILE, az login, a Doppler token — the samples
repo shows each.
Add a store
Implement ISecretsStore — one method, LoadAllAsync, returning every secret keyed in configuration
form (ConfigurationSecrets.FlattenJson / NormalizeKey help). To make SECRETS_PROVIDER=MyStore
find it, ship it in a package named GM.Secrets.MyStore that declares a factory reading its settings
from environment variables:
[assembly: SecretsStore("MyStore", typeof(MyStoreFactory))]
internal sealed class MyStoreFactory : ISecretsStoreFactory
{
public ISecretsStore Create(SecretsVariables variables, IHostEnvironment environment) =>
new MyStore(new MyStoreOptions { Url = variables.GetUri("MYSTORE_URL") });
}
A store in any other assembly can be registered in code instead:
builder.AddGMSecrets(s => s.AddProvider("MyStore", variables => new MyStore(...))).
Samples
GM.Secrets.Samples — a runnable API that reads its secrets through configuration, locally and from each store.
License
MIT — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- GM.Secrets (>= 2.0.0)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on GM.Secrets.Vault:
| Package | Downloads |
|---|---|
|
GM.API
An opinionated ASP.NET Core Web API starter for .NET. A single AddGMAPI/UseGMAPI pair wires up controllers, CORS, API versioning, Swagger docs, FluentValidation, Serilog, and middlewares for exception handling, request logging (with sensitive-data masking) and request localization. Loads secrets into configuration from a local appsecrets.json or the store the SECRETS_PROVIDER environment variable names (Doppler, AWS Secrets Manager, Azure Key Vault, HashiCorp Vault). Bundles the GM.API.* companion packages. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.0 | 469 | 10/30/2026 |