Files
lmxopcua/code-reviews/Driver.Historian.Wonderware.Client.Contracts/findings.md
T
Joseph Doherty 78542ab2d2 review(Driver.Historian.Wonderware.Client.Contracts): redact SharedSecret in ToString (High)
First review at 7286d320. -001 (High): record ToString() leaked SharedSecret into logs ->
override ToString() that omits it. -003 (Medium): [Range(1,65535)] on Port. -004 thumbprint
doc. -002 (SHA-1 pin -> SHA-256, lives in FrameChannel) deferred cross-module.
2026-06-19 12:22:53 -04:00

140 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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 165535) 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 `<param name="Port">` 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 `<remarks>` 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.