--- status: Active lastUpdated: 2026-09-18 applyTo: - "Fuchs/Program.cs" - "Fuchs/appsettings*.json" - "OCORE/OCORE/Secrets/**" - "OCORE_web/OCORE_web/Secrets/**" relatedDecisions: - "0031-shared-keyvault-dpapi-secret-management.md" --- # Secret management (Key Vault + DPAPI) ## Summary Fuchs loads its secrets (connection strings, API keys, mailer tokens, ...) from the shared Azure Key Vault `pcwkeys` at startup, with a DPAPI-encrypted local cache as an offline fallback. The mechanism is shared, host-type-agnostic infrastructure owned by the `OCORE`/`OCORE_web` submodules ([0031](../Decisions/0031-shared-keyvault-dpapi-secret-management.md)); this doc covers how Fuchs specifically wires into it and where to make changes. The full mechanism reference (naming/encoding rules, DI wiring, cache format) is maintained independently in `OCORE_web/.github_instructions/secretsmanagement.instructions.md`. ## How it works 1. `Fuchs/Program.cs` calls `builder.AddSecretManagement()` (`OCORE_web.Secrets.SecretManagementWebExtensions`) as the first step in `ConfigureServices`, before `FuchsOcmsIntranet.Initialize(configuration)` or anything else reads `IConfiguration`. 2. That call, backed by `OCORE.Secrets.Extensions.SecretManagementExtensions`, registers two configuration sources in order: the local DPAPI cache (`secrets.cache`, offline fallback) first, then the live Azure Key Vault (overrides the cache when reachable) using `PrefixKeyVaultSecretManager` to filter to secrets named `fuchs--...` and map them onto `IConfiguration` keys. 3. `KeyVaultSyncService` (a registered `BackgroundService`) periodically re-pulls every key listed in `Fuchs:SecretManagement:ManagedSecretKeys` from Key Vault, re-writes the DPAPI cache, and calls `IConfigurationRoot.Reload()` so a running process picks up rotated secrets without a restart. 4. A secret whose name is only known at runtime (not a fixed `IConfiguration` key) — e.g. resolved from a DB job record — is instead fetched directly via `OCORE.Secrets.Extensions.SecretsOnDemandLoader`, bypassing `ManagedSecretKeys` and the periodic sync entirely. 5. In `Development`, `appsettings.Development.json` (git-ignored) can override any of these values locally, bypassing Key Vault for that key; `AssembleConnectionStrings` (`Fuchs/Program.cs`) additionally prefers `_Dev`-suffixed credential keys so a reachable Key Vault can never hand a dev machine production DB credentials. ## Key files - `Fuchs/Program.cs` — `builder.AddSecretManagement()` call site and `AssembleConnectionStrings`. - `Fuchs/appsettings.json` — `SecretManagement` block (`VaultUri`, `AppName: "fuchs"`, `CacheFilePath`, `SyncIntervalHours`, `ManagedSecretKeys`) plus `"MANAGED_BY_KEYVAULT"` placeholders for every managed key. - `OCORE/Secrets/Extensions/SecretManagementExtensions.cs` — shared Key Vault + DPAPI wiring and DI registration, used by every host type. - `OCORE/Secrets/Extensions/SecretsOnDemandLoader.cs` — direct, cached, runtime-name secret lookup outside the `ManagedSecretKeys` sync path. - `OCORE/Secrets/Sync/KeyVaultSyncService.cs` — periodic re-sync + `IConfigurationRoot.Reload()`. - `OCORE/Secrets/Cache/DpapiSecretsCache.cs` — DPAPI encrypt/decrypt of the local cache file. - `OCORE_web/Secrets/SecretManagementWebExtensions.cs` — the `WebApplicationBuilder.AddSecretManagement()` entry point Fuchs calls. - `OCORE_web/.github_instructions/secretsmanagement.instructions.md` — canonical mechanism reference (naming/encoding rules, cache format, "adding a new secret" steps), kept current independently of this doc. - `.github/copilot-instructions.md` ("Azure Key Vault — Secret Naming") — the naming rules summarized for this repo, with the `fuchs--` prefix. ## Related decisions - [0031 — Shared Key Vault + DPAPI secret management via OCORE/OCORE_web](../Decisions/0031-shared-keyvault-dpapi-secret-management.md)