# Code Review — Driver.Historian.Wonderware.Client.Contracts | Field | Value | |---|---| | Module | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Contracts` | | Reviewer | Claude Code | | Review date | 2026-06-19 | | Commit reviewed | `a19b0f86` | | Status | Reviewed | | Open findings | 0 | ## Checklist coverage A comprehensive review completes every category, recording "No issues found" where a category produced nothing rather than leaving it blank. | # | Category | Result | |---|---|---| | 1 | Correctness & logic bugs | Driver.Historian.Wonderware.Client.Contracts-003 | | 2 | OtOpcUa conventions | No issues found | | 3 | Concurrency & thread safety | No issues found | | 4 | Error handling & resilience | No issues found | | 5 | Security | Driver.Historian.Wonderware.Client.Contracts-001, Driver.Historian.Wonderware.Client.Contracts-002 | | 6 | Performance & resource management | No issues found | | 7 | Design-document adherence | No issues found | | 8 | Code organization & conventions | No issues found | | 9 | Testing coverage | No test project — per review instructions; verified by build only | | 10 | Documentation & comments | Driver.Historian.Wonderware.Client.Contracts-004 | ## Findings ### Driver.Historian.Wonderware.Client.Contracts-001 | Field | Value | |---|---| | Severity | High | | Category | Security | | Location | `WonderwareHistorianClientOptions.cs:24-30` | | Status | Resolved | **Description:** `WonderwareHistorianClientOptions` is a `sealed record`. The C# compiler auto-generates a `ToString()` for records that includes every positional constructor parameter by name and value — in this case including `SharedSecret`. If the options object is ever passed to structured logging (e.g. `_logger.LogDebug("{opts}", options)`), printed in an exception message, surfaced in a health-check endpoint, or serialized by a framework that calls `ToString()`, the shared secret will appear in plain text in log files or HTTP responses. There is currently no `ToString()` override to redact it. The `FrameChannel.ConnectInternalAsync` log line in the sibling project does not log `_options` directly, so there is no active leak today, but the type is a latent trap for any future logging addition. **Recommendation:** Override `ToString()` to redact the secret, including only the non-sensitive connection fields (Host, Port, PeerName, UseTls, ServerCertThumbprint). **Resolution:** Resolved 2026-06-19 — added `override ToString()` to the record that outputs Host, Port, PeerName, UseTls, and ServerCertThumbprint only, omitting SharedSecret; verified by build (no test project for this module). `dotnet build` on both the Contracts project and the consumer Client project passes with 0 errors/warnings. --- ### Driver.Historian.Wonderware.Client.Contracts-002 | Field | Value | |---|---| | Severity | Medium | | Category | Security | | Location | `WonderwareHistorianClientOptions.cs:50-54` | | Status | Deferred | **Description:** `ServerCertThumbprint` is documented as a "SHA-1 thumbprint". The consumer (`FrameChannel.DefaultTcpConnectFactory`, sibling project) pins via the no-arg `GetCertHashString()` overload, which returns a 40-character SHA-1 hex string. SHA-1 is a deprecated hash algorithm for certificate identification. An operator following current tooling (Windows Certificate Manager, `certutil -sha256`, `openssl x509 -fingerprint -sha256`) would supply a 64-character SHA-256 thumbprint, which will silently never match and cause every TLS handshake to fail with a generic auth error and no indication that the algorithm mismatch is the cause. **Recommendation:** Upgrade the consumer to `cert.GetCertHashString(HashAlgorithmName.SHA256)` and update the doc to require a 64-char SHA-256 thumbprint. This requires a coordinated change to `FrameChannel.DefaultTcpConnectFactory` in the `Client` project (not a wire-contract change). Until then the doc has been updated (finding 004) to explicitly warn operators that only 40-char SHA-1 thumbprints are accepted. **Resolution:** Deferred — the fix requires editing `FrameChannel.DefaultTcpConnectFactory` in the sibling `Client` project, outside this Contracts module's scope. The documentation has been updated (finding 004 fix) to warn operators of the SHA-1-only constraint. Track as a follow-up in the `Client` module's review. --- ### Driver.Historian.Wonderware.Client.Contracts-003 | Field | Value | |---|---| | Severity | Medium | | Category | Correctness & logic bugs | | Location | `WonderwareHistorianClientOptions.cs:26` | | Status | Resolved | **Description:** The `Port` positional constructor parameter has no `[Range]` validation attribute (unlike `ProbeTimeoutSeconds`, which carries `[Range(1, 60)]`). A misconfigured zero, negative, or out-of-range port (valid TCP range 1–65535) passes construction silently. The consumer then fails at `TcpClient.ConnectAsync` time with a `SocketException` whose error gives no hint that the port value is invalid. The sibling `ServerHistorianOptions.Validate()` warns on `Port <= 0` at startup, but that fires only if the read-path historian is wired; write-path-only or probe-only usage has no guard. **Recommendation:** Add `[Range(1, 65535)]` on the `Port` positional parameter. **Resolution:** Resolved 2026-06-19 — added `[Range(1, 65535)]` to the `Port` positional parameter; also updated the `` XML doc to state the valid range. Build passes with 0 errors/warnings on both the Contracts and consumer Client projects. --- ### Driver.Historian.Wonderware.Client.Contracts-004 | Field | Value | |---|---| | Severity | Low | | Category | Documentation & comments | | Location | `WonderwareHistorianClientOptions.cs:50-54` | | Status | Resolved | **Description:** The `ServerCertThumbprint` XML doc said "SHA-1 thumbprint (hex, no spaces)" but did not state the expected length (40 hex characters for SHA-1). An operator supplying a SHA-256 thumbprint (64 hex chars — the format shown by modern tooling) would get a silent TLS handshake failure. The doc also did not explain which hash algorithm the consumer uses or why SHA-256 thumbprints will not work. **Recommendation:** Update the XML doc to state the 40-character SHA-1 constraint explicitly and warn that SHA-256 thumbprints will silently fail. **Resolution:** Resolved 2026-06-19 — updated the `ServerCertThumbprint` XML doc to state "40 hex characters, no spaces, case-insensitive" and added a `` block explaining that the consumer uses `GetCertHashString()` (SHA-1) and that a 64-char SHA-256 thumbprint will silently never match. Build passes with 0 errors/warnings.