fix(secrets): run the store-path guard in the pre-host container (0.6.1)
0.6.0 put the store-path rules in the shared library, but the guard was not
running at the moment that matters here.
CreateBuilder resolves ${secret:} references before the host exists, using a
throwaway ServiceCollection that contains no IHostEnvironment — and it runs the
store migrator, which creates the database. The library resolved the content
root from IHostEnvironment alone, so it could not distinguish "no content root"
from "no host registered" and skipped the under-content-root rule entirely. The
store was created at the rejected path; the boot then failed a moment later when
the real host validated. The leftover empty database with its -wal/-shm siblings
is exactly the artifact that made the 2026-08-09 credential loss read as "the
database is there, it's just empty".
The pin alone does not close this. An app with a correctly configured path shows
no symptom and is still unprotected, because the guard simply is not running when
the store is created. 0.6.1 adds a 4-argument AddZbSecrets overload taking the
content root explicitly, and the call site has to use it. The in-host
registration below needs nothing.
Verified by removing the fix rather than by observing a clean boot — which is how
this survived its first release. With the 3-argument overload the new test fails
by finding a created database at
src/ZB.MOM.WW.MxGateway.Server/probe-secrets-*.db: inside the source tree, since
that is what the content root resolves to under test.
Two things about the test itself, both of which it would have been easy to get
subtly wrong:
It asserts no-file-created before asserting that startup threw. "It threw" is the
weaker claim, and asserting it first masks the stronger one — the run that proved
this defect would have reported "no exception was thrown" and said nothing about
the database sitting in the source tree.
The accepting case asserts the database *is* created, not merely that nothing
threw. A not-null builder is close to a tautology once no exception escaped, and
it would still pass if the pre-host container stopped opening the store at all —
which would also quietly void the rejecting case, since that one can only observe
a file the migration would otherwise have written. The two assertions hold each
other up.
Found by HistorianGateway's adoption, which probed the rejected paths instead of
observing a successful boot.
This commit is contained in:
@@ -78,9 +78,19 @@ public static class GatewayApplication
|
||||
// here (SecretNotFoundException); config with no tokens is untouched (no-op), so this is safe
|
||||
// to always run. CreateBuilder is synchronous and single-shot at bootstrap, so the two awaits
|
||||
// are driven via GetAwaiter().GetResult() (no sync-context deadlock risk during host startup).
|
||||
// The content root is passed explicitly because this container is a throwaway
|
||||
// ServiceCollection with no IHostEnvironment in it. Without it the library cannot tell
|
||||
// "no content root exists" from "no host is registered", so it skips the
|
||||
// under-content-root rule — and the migrator below CREATES the store before the real host
|
||||
// ever validates. The boot then fails a moment later, having already left an empty
|
||||
// database with its -wal/-shm siblings at the very path the rule rejects. That artifact is
|
||||
// what made the 2026-08-09 outage read as "the database is there, it's just empty".
|
||||
// DO NOT simplify this to the 3-argument overload: it still compiles, the app still boots
|
||||
// when the path is correct, and the guard silently stops running at the one moment that
|
||||
// matters.
|
||||
#pragma warning disable ASP0000 // deliberate throwaway container, disposed here, shares no singletons
|
||||
using (var secretsProvider = new ServiceCollection()
|
||||
.AddZbSecrets(builder.Configuration, "Secrets")
|
||||
.AddZbSecrets(builder.Configuration, "Secrets", builder.Environment.ContentRootPath)
|
||||
.BuildServiceProvider())
|
||||
#pragma warning restore ASP0000
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user