fix(sql): close the residual credential leak in HandlePollError
The C1 credential-hygiene fix converted InitializeAsync and ReadAsync to
surface only ex.GetType().Name, but left HandlePollError building its
operator-facing Degrade() message from raw ex.Message, gated only by
'if (ex is DbException) return;'. That guard exists for the I1
double-classification concern, not for message safety, and it is incomplete
for the leak vector: a malformed connection string throws ArgumentException
from the keyword parser (the same unquoted-';'-in-password shape the sibling
SqlDriverBrowser.Sanitize special-cases), which is not a DbException and so
reaches Degrade(ex.Message) unredacted.
It is unreachable in today's control flow only because the connection string
is static and validated identically at Initialize first — an emergent
property, not an enforced invariant, and a live per-poll leak the moment a
future edit (refresh-on-reinit, a different provider) breaks that assumption.
Make the fallback type-only like the other two sites; the full exception still
reaches the log sink via the exception parameter. The I1 defer + control tests
are unaffected (they assert Degraded, not message text). Pinned by a new test
driving a credential-bearing ArgumentException through HandlePollError;
verified load-bearing (red against Degrade(ex.Message), green after).
Closes the C1 review finding on commit 37f444b9.
Claude-Session: https://claude.ai/code/session_01GASWkNEi68FSCtvr6rLoEW
This commit is contained in:
@@ -424,6 +424,27 @@ public sealed class SqlDriverTests
|
||||
driver.GetHealth().State.ShouldBe(DriverState.Degraded);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HandlePollError_forANonDbExceptionCarryingCredentials_degradesWithoutLeakingThem()
|
||||
{
|
||||
// The residual C1 leg HandlePollError owns: a NON-DbException raised from provider interaction —
|
||||
// canonically the ArgumentException a malformed connection string's keyword parser throws, echoing
|
||||
// credential fragments (the exact unquoted-';'-in-password vector this whole fix exists for). It is
|
||||
// not the DbException class ReadAsync owns, so it reaches Degrade — and must arrive TYPE-only, never
|
||||
// ex.Message. Pre-fix this degraded with the raw message and leaked the token.
|
||||
using var fixture = new SqlitePollFixture();
|
||||
var driver = NewDriver(fixture, KvEntry("Speed", SqlitePollFixture.PresentKey));
|
||||
|
||||
driver.HandlePollError(new ArgumentException(
|
||||
"Keyword not supported: 'secrettail;connect timeout'. Password=" + SecretToken));
|
||||
|
||||
var health = driver.GetHealth();
|
||||
health.State.ShouldBe(DriverState.Degraded);
|
||||
health.LastError.ShouldNotBeNull();
|
||||
health.LastError.ShouldNotContain(SecretToken); // never the message text
|
||||
health.LastError.ShouldContain(nameof(ArgumentException)); // but still actionable (the type)
|
||||
}
|
||||
|
||||
// ---- I2: a late poll cannot un-fault a Faulted driver ----
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user