Files
scadaproj/ZB.MOM.WW.Secrets/Directory.Packages.props
T
Joseph Doherty 944595ad32 feat(secrets): fail-closed store-path validation; default moves outside the content root
Secrets:SqlitePath is now validated at startup by SecretsOptionsValidator using two
rules in the new public SecretsStorePathRules. Both come from production incidents and
neither implies the other.

Must be absolute: a relative value resolves against the process working directory,
which for a Windows service is C:\Windows\System32, so the store lands outside the
installation; in a dev loop it lands in the source tree (a gateway was observed writing
its secrets database into src/, tripping that repo's own tree-hygiene test).

Must not be inside the content root: an absolute path can still point into the
directory every deployment procedure in this family renames on upgrade. On 2026-08-09 a
gateway's credential store sat inside its Server\ directory; the deploy renamed Server
to Server.bak.*, the database went with it, and the process created a fresh empty one
at the same path. Every consumer lost its credential, silently, for two days. The
existing "must be absolute" convention did not help — the path was absolute.

The rules reject rather than relocate. Silently moving a credential store presents later
as an authentication failure pointing nowhere near the configuration that caused it; a
boot error is seen immediately by whoever caused it. Validation is deliberately not
exempted in Development: a guard that never runs in a dev loop or in CI rots, and the
configuration behind the outage looked entirely reasonable.

BREAKING for configuration, not API. SecretsOptions.SqlitePath now defaults to an
absolute per-user path under LocalApplicationData; it was "secrets.db", which the new
rules reject. An application configuring a relative path will fail to boot — remove the
key or set an absolute one outside the content root. All five deployed instances of this
family were checked first and already set an absolute path outside their content root,
so the migration edge (a host relying on the OLD default now finding nothing and
creating an empty store — this bug, re-introduced by its own fix) does not apply to any
of them.

Per-user rather than machine-wide is deliberate: CommonApplicationData is /usr/share on
Linux and macOS, which a normal user cannot write, so a machine-wide library default
would fail every consuming application's boot on a developer machine. An application may
compute its own default; the configured value always wins.

Validator registration is idempotent. AddZbSecrets is legitimately called more than once
in real compositions, and TryAddEnumerable cannot de-duplicate a factory-built
descriptor — it de-duplicates on implementation type, which a factory does not carry, so
it throws on the second call rather than skipping. The existing suite caught this.

Tests cover both rules including the segment-boundary case (/srv/app-data must not read
as inside /srv/app — a false positive on a fail-closed startup rule is an application
that will not boot), that the shipped default satisfies the rules it ships with, that
the default is not machine-wide, idempotent registration, and — most importantly — that
a bad path actually throws when IOptions is resolved, since a correctly implemented
validator that is never invoked is the same "guard that does not fire" failure this
change exists to prevent.

Family bumps to 0.6.0. Adds a dependency on ZB.MOM.WW.Configuration 0.1.0 for the shared
OptionsValidatorBase/ValidationBuilder vocabulary, which every consumer already
references.
2026-08-11 08:18:37 -04:00

91 lines
5.3 KiB
XML

<Project>
<PropertyGroup>
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
<!-- Enable transitive pinning so the security override below (SQLitePCLRaw native lib) takes effect. -->
<CentralPackageTransitivePinningEnabled>true</CentralPackageTransitivePinningEnabled>
</PropertyGroup>
<ItemGroup>
<!-- LDAP -->
<PackageVersion Include="Novell.Directory.Ldap.NETStandard" Version="3.6.0" />
<!-- Data -->
<PackageVersion Include="Microsoft.Data.Sqlite" Version="10.0.7" />
<!-- Transitive security pin: Microsoft.Data.Sqlite 10.0.7 pulls SQLitePCLRaw.lib.e_sqlite3 2.1.11,
which carries advisory GHSA-2m69-gcr7-jv3q (NU1903). Pin the patched 2.1.12 native lib. -->
<PackageVersion Include="SQLitePCLRaw.lib.e_sqlite3" Version="2.1.12" />
<!-- Shared SQL-Server secret store (ZB.MOM.WW.Secrets.Replication.SqlServer).
6.0.2 matches the version ScadaBridge and ZB.MOM.WW.GalaxyRepository already resolve, so a
consumer taking both gets one SqlClient rather than a downgrade warning. -->
<PackageVersion Include="Microsoft.Data.SqlClient" Version="6.0.2" />
<!-- Akka cluster replication (ZB.MOM.WW.Secrets.Replication.Akka).
1.5.62 matches ScadaBridge's pinned Akka.NET line — the clustered consumer must not be
forced into an Akka version bump just to adopt secret replication. -->
<PackageVersion Include="Akka" Version="1.5.62" />
<PackageVersion Include="Akka.Cluster" Version="1.5.62" />
<PackageVersion Include="Akka.Cluster.Tools" Version="1.5.62" />
<PackageVersion Include="Akka.Remote" Version="1.5.62" />
<PackageVersion Include="Akka.TestKit.Xunit2" Version="1.5.62" />
<!-- gRPC secrets hub (ZB.MOM.WW.Secrets.Replicator.Grpc).
2.76.0 / 3.34.1 is the version the whole family already resolves — ScadaBridge and
OtOpcUa pin exactly these, and the sibling ZB.MOM.WW.LocalDb libraries floor them there.
A consumer taking the hub alongside LocalDb replication must not be pushed into a
downgrade warning or an unplanned Grpc bump just to adopt secret replication. -->
<PackageVersion Include="Google.Protobuf" Version="3.34.1" />
<PackageVersion Include="Grpc.AspNetCore" Version="2.76.0" />
<PackageVersion Include="Grpc.Core.Api" Version="2.76.0" />
<PackageVersion Include="Grpc.Net.Client" Version="2.76.0" />
<PackageVersion Include="Grpc.Tools" Version="2.76.0" />
<!-- Cryptography -->
<!-- 9.0.4, not 9.0.0: Microsoft.Data.SqlClient 6.0.2 pulls System.Configuration.ConfigurationManager
9.0.4, which requires ProtectedData >= 9.0.4. With transitive pinning on, the lower central
version is a hard NU1109 downgrade error rather than a silent resolve. -->
<PackageVersion Include="System.Security.Cryptography.ProtectedData" Version="9.0.4" />
<!-- Extensions -->
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.7" />
<PackageVersion Include="Microsoft.Extensions.Logging" Version="10.0.7" />
<!-- ASP.NET Core Authentication / Authorization -->
<PackageVersion Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="10.0.7" />
<PackageVersion Include="Microsoft.AspNetCore.Authorization" Version="10.0.7" />
<!-- ZB.MOM.WW shared libraries (Gitea feed) -->
<PackageVersion Include="ZB.MOM.WW.Audit" Version="0.1.0" />
<PackageVersion Include="ZB.MOM.WW.Configuration" Version="0.1.0" />
<PackageVersion Include="ZB.MOM.WW.Theme" Version="0.2.0" />
<PackageVersion Include="ZB.MOM.WW.Auth.AspNetCore" Version="0.1.4" />
<!-- Interactive console -->
<PackageVersion Include="Spectre.Console" Version="0.50.0" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.50.0" />
<!-- Test -->
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.14.1" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.4" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.61" />
<!-- In-process ASP.NET Core host for the gRPC secrets-hub end-to-end suite. 10.0.7 is the
version the rest of the family already pins (ZB.MOM.WW.LocalDb, OtOpcUa, ScadaBridge) and
matches the Microsoft.Extensions.* line above, so the test host cannot drag a second
ASP.NET Core version into the graph. -->
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="10.0.7" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<PackageVersion Include="bunit" Version="1.40.0" />
</ItemGroup>
</Project>