diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs
index a53e4390..3bb727e5 100644
--- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs
+++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql/SqlDriverFactoryExtensions.cs
@@ -194,8 +194,14 @@ public static class SqlDriverFactoryExtensions
}
catch (JsonException ex)
{
+ // The parser's message AND the exception carrying it are withheld (R06:S-8), matching
+ // SqlDriverProbe and SqlDriverBrowser: System.Text.Json quotes the offending token, and a pasted
+ // connection-string literal is exactly the malformed text that lands here. This message becomes
+ // InitializeFailed -> health LastError -> /hosts. Line/byte position carry no content, so they stay.
throw new InvalidOperationException(
- $"Sql driver config for '{driverInstanceId}' is not valid JSON: {ex.Message}", ex);
+ $"Sql driver config for '{driverInstanceId}' is not valid JSON (line {ex.LineNumber + 1}, byte "
+ + $"{ex.BytePositionInLine}). The parser's message is withheld because the malformed text may "
+ + "carry a connection string.");
}
}
diff --git a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqlDriverFactoryTests.cs b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqlDriverFactoryTests.cs
index 65d6a281..9d1265de 100644
--- a/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqlDriverFactoryTests.cs
+++ b/tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Sql.Tests/SqlDriverFactoryTests.cs
@@ -25,6 +25,28 @@ public sealed class SqlDriverFactoryTests
{
// ---- the plan's headline legs ----
+ ///
+ /// R06:S-8. A malformed config used to quote System.Text.Json's message — which echoes the
+ /// offending token — into the exception that becomes InitializeFailed → health
+ /// LastError → /hosts. A pasted connection string is exactly the malformed text that
+ /// reaches here, so the parser text (and the inner exception carrying it) is withheld, as the probe and
+ /// browser already do. The message still names the instance and says what is wrong.
+ ///
+ [Fact]
+ public void CreateInstance_malformedJson_withholdsTheParserText()
+ {
+ const string secret = "hunter2-do-not-log";
+ var ex = Should.Throw(() =>
+ SqlDriverFactoryExtensions.CreateInstance(
+ "d1", $$"""{"provider":"SqlServer","connectionStringRef": Password={{secret}} }""", null));
+
+ ex.Message.ShouldContain("d1");
+ ex.Message.ShouldContain("not valid JSON");
+ ex.Message.ShouldNotContain(secret);
+ ex.Message.ShouldNotContain("Password");
+ ex.InnerException.ShouldBeNull();
+ }
+
[Fact]
public void CreateInstance_missingConnectionStringRef_throwsActionable()
=> Should.Throw(() =>