[Secrets] ${secret:} expansion is undone on config reload (raw placeholders reach connection strings) #32
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
From the scadaproj#15 shared-library review (
code-reviews/Secrets/findings.md, Secrets-003), verified at91fb2b4. Update the finding's Resolution row when fixed.ZB.MOM.WW.Secrets/src/ZB.MOM.WW.Secrets/Configuration/SecretReferenceExpander.cs:115Description
ExpandConfigurationAsyncwrites the expanded value throughconfig[key] = …. That only sets the value in the providers' in-memoryData. A file provider'sLoad()replacesDataon reload, so every expanded key reverts to its literal${secret:…}placeholder. Reload happens onIConfigurationRoot.Reload(), or on anyappsettings*.jsonchange whenreloadOnChangeis on, which is the default forWebApplication.CreateBuilder/Host.CreateDefaultBuilder. The XML docs and the README do not mention this.Failure scenario
The probe used a JSON file
{"Cs":"pw=${secret:x}"}, expanded it topw=hunter2, and then calledcfg.Reload().cfg["Cs"]becamepw=${secret:x}. In production, an operator edits a log level inappsettings.jsonon a running service. From then on,IConfigurationreaders andIOptionsMonitor-rebound options see the placeholder text as the credential. For example, a connection string read per use containsPassword=${secret:sql/…}, and logins fail until the process restarts.Recommendation
Expand through a dedicated configuration provider or source that wraps the others and re-expands on the change token. Alternatively, add an in-memory overlay provider last and re-run expansion on
GetReloadToken(). At minimum, document that reload must be disabled for expanded keys.Resolution
Unresolved.
Fixed on main 25607b7 (Secrets-003 → Resolved): with a ConfigurationManager, expanded values live in an extra last-added non-reloading configuration source; any other IConfigurationRoot gets the old in-place write re-applied after each reload. All references resolve before anything is written (a missing secret leaves config untouched, not half-expanded). Behaviour: a key holding a ${secret:} reference keeps its startup value until restart. Exposed today: OtOpcUa, HistorianGateway, mxaccessgw (ConfigurationManager + reload-on-change); ScadaBridge and the ignitionoee router were not. Evidence: Secrets solution (5 test projects) all pass — Core 246 (1 DPAPI skip), SqlServer 60 (16 live-SQL skips), Akka 44, Grpc 196, Ui 20; each regression test fails with its fix removed. Ships in Secrets 0.6.6 (unpublished).