fix(sql): withhold the JSON parser's message from the probe's red result (Driver.Sql-009)

SqlDriverProbe surfaced 'Config JSON is invalid: ' + ex.Message, where
SqlDriverBrowser.ParseDocument deliberately withholds the identical message
because the malformed text may itself carry a connection string. The two read
the same blob shape - the AdminUI Sql picker composes a document with a
top-level connectionString literal - so a malformed paste reaches both. The
probe now reports the failure class only, matching the browser.
This commit is contained in:
Joseph Doherty
2026-08-10 07:56:58 -04:00
parent 94b8633e68
commit 073930e023
2 changed files with 46 additions and 2 deletions
@@ -19,7 +19,10 @@ namespace ZB.MOM.WW.OtOpcUa.Driver.Sql;
/// timeout, a cancellation — becomes a red <see cref="DriverProbeResult"/>, so the AdminUI has nothing to
/// catch.</para>
/// <para><b>The resolved connection string never reaches the result message.</b> A red result names the
/// failure class, never the string — the same credential discipline the factory and the driver keep.</para>
/// failure class, never the string — the same credential discipline the factory and the driver keep.
/// That includes the JSON parser: a malformed config is reported as a class, with the parser's own
/// message withheld exactly as <c>SqlDriverBrowser.ParseDocument</c> withholds it, because the malformed
/// text may itself be a pasted connection string.</para>
/// </summary>
public sealed class SqlDriverProbe : IDriverProbe
{
@@ -80,7 +83,16 @@ public sealed class SqlDriverProbe : IDriverProbe
}
catch (Exception ex) when (ex is System.Text.Json.JsonException or ArgumentNullException)
{
return new DriverProbeResult(false, $"Config JSON is invalid: {ex.Message}", null);
// The parser's message is WITHHELD, matching SqlDriverBrowser.ParseDocument. Nothing has been
// extracted at this point, so there is no known secret to redact the message against — and the
// blob both types read is the one the AdminUI Sql picker composes, which carries a top-level
// `connectionString` literal. A malformed paste is therefore exactly the text that reaches here,
// and System.Text.Json quotes the offending token and states the JSON path and byte position.
return new DriverProbeResult(
false,
"Config JSON is not valid JSON. (The parser's message is withheld because the malformed "
+ "text may carry a connection string.)",
null);
}
if (dto is null)
@@ -79,6 +79,38 @@ public sealed class SqlDriverProbeTests
result.Message.ShouldNotBeNullOrWhiteSpace();
}
/// <summary>
/// A malformed config is reported as a failure CLASS, never as the parser's own message — the same
/// discipline <c>SqlDriverBrowser.ParseDocument</c> keeps, and for the same reason: the blob the two
/// read is the one the AdminUI Sql picker composes, which carries a top-level <c>connectionString</c>
/// literal, so a malformed paste is exactly the text that reaches here.
/// <para><see cref="System.Text.Json"/>'s message quotes the offending token and states the JSON path
/// and the byte position. None of it may leave the probe: at this point nothing has been extracted,
/// so there is no known secret to redact against.</para>
/// </summary>
[Fact]
public async Task Probe_malformedConfig_withholdsTheParserMessage()
{
using var fixture = new SqlitePollFixture();
var probe = SqlDriverProbe.ForTest(fixture.Factory, new SqliteDialect());
// A pasted literal whose password carries an unquoted ';' — mis-quoted exactly the way an operator's
// paste is, so the parse fails partway THROUGH the credential.
const string malformed =
"""{"provider":"SqlServer","connectionString":Password=Sup3r;SecretTail}""";
var result = await probe.ProbeAsync(malformed, TimeSpan.FromSeconds(5), CancellationToken.None);
result.Ok.ShouldBeFalse();
result.Message.ShouldNotBeNull();
result.Message!.ShouldNotContain("LineNumber");
result.Message.ShouldNotContain("BytePositionInLine");
result.Message.ShouldNotContain("Sup3r");
result.Message.ShouldNotContain("SecretTail");
// Still actionable: it names what is wrong and why the detail is missing.
result.Message.ShouldContain("not valid JSON");
}
[Fact]
public async Task Probe_providerNumberSpelling_parses_soTestConnectMatchesDeploy()
{