Deploy-time guard: reject a Sql DriverConfig blob carrying a connectionString key (FF-3) #498

Closed
opened 2026-07-24 21:00:10 -04:00 by dohertj2 · 1 comment
Owner

The 'a pasted literal connection string is never persisted' guarantee for the Sql driver is enforced only at the typed-DTO layer (SqlDriverConfigDto has no connectionString property + UnmappedMemberHandling.Skip drops it on read). That protects the read path, not the write path: if the AdminUI ever persists a raw-JSON DriverConfig blob (the fallback pattern unmapped drivers use), an operator pasting {"connectionString":"Server=...;Password=..."} would write a credential into the ConfigDb DriverConfig column even though the runtime factory never reads it back. Add a DraftValidator deploy-time check that rejects any persisted Sql DriverConfig blob carrying a connectionString key (defence-in-depth regardless of how the authoring UI evolves). Surfaced by the Task 14 (SqlDriverBrowser) review.

The 'a pasted literal connection string is never persisted' guarantee for the Sql driver is enforced only at the typed-DTO layer (SqlDriverConfigDto has no connectionString property + UnmappedMemberHandling.Skip drops it on read). That protects the read path, not the write path: if the AdminUI ever persists a raw-JSON DriverConfig blob (the fallback pattern unmapped drivers use), an operator pasting {"connectionString":"Server=...;Password=..."} would write a credential into the ConfigDb DriverConfig column even though the runtime factory never reads it back. Add a DraftValidator deploy-time check that rejects any persisted Sql DriverConfig blob carrying a connectionString key (defence-in-depth regardless of how the authoring UI evolves). Surfaced by the Task 14 (SqlDriverBrowser) review.
Author
Owner

Fixed on fix/sql-driver-followups (1919a8e5).

DraftValidator.ValidateSqlConnectionStringNotPersisted fails the deploy (SqlConnectionStringPersisted) when a Sql-typed driver instance carries a top-level connectionString key.

Beyond the report:

  • Device.DeviceConfig is checked too, not just DriverInstance.DriverConfig. DeviceConfig is merged onto DriverConfig before the DTO sees it, so a credential pasted into the device blob is the identical leak.
  • The key is matched case-insensitively. System.Text.Json binds ConnectionString to a connectionString property by default, so an ordinal match would have left the obvious bypass wide open.
  • The message never echoes the value — validation errors reach the AdminUI, the deploy log and the audit trail. Pinned by a test.
  • Only the top level is scanned; the DTO is flat, so a nested occurrence cannot bind and is not the mistake this rule exists to catch.
  • Malformed/blank/non-object JSON never throws — a parse failure would take down every other rule in the same pass.

16 tests. Verified falsifiable: with the rule disabled, the 6 positive assertions fail and the 10 negatives stay green, so none passes vacuously.

Design §8.2’s “if an inline connectionString is ever permitted (dev convenience only), the AdminUI redacts it” carve-out is withdrawn in the same change — redaction only hides a credential that has already been written.

Not covered, filed separately as #499: ClusterNode.DriverConfigOverridesJson is a third persistence surface for a driver config blob, but it is not part of DraftSnapshot, so this validator cannot see it.

Fixed on `fix/sql-driver-followups` (`1919a8e5`). `DraftValidator.ValidateSqlConnectionStringNotPersisted` fails the deploy (`SqlConnectionStringPersisted`) when a Sql-typed driver instance carries a top-level `connectionString` key. Beyond the report: - **`Device.DeviceConfig` is checked too, not just `DriverInstance.DriverConfig`.** `DeviceConfig` is merged onto `DriverConfig` before the DTO sees it, so a credential pasted into the device blob is the identical leak. - **The key is matched case-insensitively.** `System.Text.Json` binds `ConnectionString` to a `connectionString` property by default, so an ordinal match would have left the obvious bypass wide open. - **The message never echoes the value** — validation errors reach the AdminUI, the deploy log and the audit trail. Pinned by a test. - Only the top level is scanned; the DTO is flat, so a nested occurrence cannot bind and is not the mistake this rule exists to catch. - Malformed/blank/non-object JSON never throws — a parse failure would take down every other rule in the same pass. 16 tests. **Verified falsifiable:** with the rule disabled, the 6 positive assertions fail and the 10 negatives stay green, so none passes vacuously. Design §8.2’s *“if an inline `connectionString` is ever permitted (dev convenience only), the AdminUI redacts it”* carve-out is withdrawn in the same change — redaction only hides a credential that has **already been written**. **Not covered, filed separately as #499:** `ClusterNode.DriverConfigOverridesJson` is a third persistence surface for a driver config blob, but it is not part of `DraftSnapshot`, so this validator cannot see it.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#498