GM.Secrets
2.0.0
dotnet add package GM.Secrets --version 2.0.0
NuGet\Install-Package GM.Secrets -Version 2.0.0
<PackageReference Include="GM.Secrets" Version="2.0.0" />
<PackageVersion Include="GM.Secrets" Version="2.0.0" />
<PackageReference Include="GM.Secrets" />
paket add GM.Secrets --version 2.0.0
#r "nuget: GM.Secrets, 2.0.0"
#:package GM.Secrets@2.0.0
#addin nuget:?package=GM.Secrets&version=2.0.0
#tool nuget:?package=GM.Secrets&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
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.FileExtensions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Json (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
NuGet packages (8)
Showing the top 5 NuGet packages that depend on GM.Secrets:
| 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. |
|
|
GM.Secrets.AzureKeyVault
Azure Key Vault secrets provider for GM.Secrets — loads a vault's enabled secrets into IConfiguration at startup with DefaultAzureCredential (managed identity in Azure, az login / Visual Studio locally), with optional periodic reload. Db--Password maps to Db:Password. Register with builder.AddGMSecrets(s => s.AddAzureKeyVault()) and select via Secrets:Provider = "AzureKeyVault". |
|
|
GM.Secrets.AwsSecretsManager
AWS Secrets Manager provider for GM.Secrets — reads the configured secrets (a JSON secret is flattened into keys, a plain one is keyed by its name) with the AWS SDK's default credential chain, and loads them into IConfiguration at startup, with optional periodic reload. Works with LocalStack/moto via ServiceUrl. Register with builder.AddGMSecrets(s => s.AddAwsSecretsManager()) and select via Secrets:Provider = "AwsSecretsManager". |
|
|
GM.Secrets.Doppler
Doppler store for GM.Secrets — downloads a Doppler config's secrets (REST API, no SDK dependency) and loads them into IConfiguration at startup, with optional periodic reload. DB__PASSWORD maps to Db:Password. With a personal or CLI token the app environment picks the config (Development → dev, Staging → stg, Production → prd). Register with builder.AddGMSecrets(s => s.AddDoppler()) and select with the SECRETS_PROVIDER=Doppler environment variable. |
|
|
GM.Secrets.Vault
HashiCorp Vault secrets provider for GM.Secrets — reads KV v2 paths over Vault's HTTP API (token or AppRole auth, no SDK dependency) and loads them into IConfiguration at startup, with optional periodic reload. Nested values become sections. Register with builder.AddGMSecrets(s => s.AddVault()) and select via Secrets:Provider = "Vault". |
GitHub repositories
This package is not used by any popular GitHub repositories.