docs(code-reviews): regenerate index — 126 Medium findings resolved

All Medium-severity code-review findings across the 29 reviewed modules
are now Resolved. The Pending findings table holds only Low-severity items.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-05-22 11:29:21 -04:00
parent 0f3b74ad87
commit bbe292a4b4

View File

@@ -10,37 +10,37 @@ Each module's `findings.md` is the source of truth; this file is generated from
| Module | Reviewer | Date | Commit | Status | Open | Total |
|---|---|---|---|---|---|---|
| [Admin](Admin/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 7 | 12 |
| [Analyzers](Analyzers/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 7 | 7 |
| [Client.CLI](Client.CLI/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 10 | 10 |
| [Client.Shared](Client.Shared/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 9 | 11 |
| [Client.UI](Client.UI/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 11 | 11 |
| [Configuration](Configuration/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 9 | 11 |
| [Core](Core/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 10 | 12 |
| [Core.Abstractions](Core.Abstractions/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 8 | 8 |
| [Core.AlarmHistorian](Core.AlarmHistorian/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 7 | 11 |
| [Core.ScriptedAlarms](Core.ScriptedAlarms/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 11 | 12 |
| [Core.Scripting](Core.Scripting/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 9 | 11 |
| [Core.VirtualTags](Core.VirtualTags/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 12 | 13 |
| [Driver.AbCip](Driver.AbCip/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 11 | 15 |
| [Driver.AbCip.Cli](Driver.AbCip.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 8 | 8 |
| [Driver.AbLegacy](Driver.AbLegacy/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 11 | 13 |
| [Driver.AbLegacy.Cli](Driver.AbLegacy.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 7 | 7 |
| [Driver.Cli.Common](Driver.Cli.Common/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 6 |
| [Driver.FOCAS](Driver.FOCAS/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 10 | 12 |
| [Admin](Admin/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 3 | 12 |
| [Analyzers](Analyzers/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 7 |
| [Client.CLI](Client.CLI/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 8 | 10 |
| [Client.Shared](Client.Shared/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 11 |
| [Client.UI](Client.UI/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 6 | 11 |
| [Configuration](Configuration/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 11 |
| [Core](Core/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 6 | 12 |
| [Core.Abstractions](Core.Abstractions/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 8 |
| [Core.AlarmHistorian](Core.AlarmHistorian/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 2 | 11 |
| [Core.ScriptedAlarms](Core.ScriptedAlarms/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 6 | 12 |
| [Core.Scripting](Core.Scripting/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 11 |
| [Core.VirtualTags](Core.VirtualTags/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 7 | 13 |
| [Driver.AbCip](Driver.AbCip/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 15 |
| [Driver.AbCip.Cli](Driver.AbCip.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 6 | 8 |
| [Driver.AbLegacy](Driver.AbLegacy/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 3 | 13 |
| [Driver.AbLegacy.Cli](Driver.AbLegacy.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 6 | 7 |
| [Driver.Cli.Common](Driver.Cli.Common/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 2 | 6 |
| [Driver.FOCAS](Driver.FOCAS/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 12 |
| [Driver.FOCAS.Cli](Driver.FOCAS.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 5 |
| [Driver.Galaxy](Driver.Galaxy/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 11 | 14 |
| [Driver.Historian.Wonderware](Driver.Historian.Wonderware/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 11 | 12 |
| [Driver.Historian.Wonderware.Client](Driver.Historian.Wonderware.Client/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 9 | 10 |
| [Driver.Modbus](Driver.Modbus/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 11 | 12 |
| [Driver.Modbus.Addressing](Driver.Modbus.Addressing/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 8 | 9 |
| [Driver.Modbus.Cli](Driver.Modbus.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 8 | 8 |
| [Driver.OpcUaClient](Driver.OpcUaClient/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 10 | 15 |
| [Driver.S7](Driver.S7/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 10 | 14 |
| [Driver.S7.Cli](Driver.S7.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 7 | 7 |
| [Driver.TwinCAT](Driver.TwinCAT/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 11 | 16 |
| [Driver.Galaxy](Driver.Galaxy/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 4 | 14 |
| [Driver.Historian.Wonderware](Driver.Historian.Wonderware/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 7 | 12 |
| [Driver.Historian.Wonderware.Client](Driver.Historian.Wonderware.Client/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 10 |
| [Driver.Modbus](Driver.Modbus/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 7 | 12 |
| [Driver.Modbus.Addressing](Driver.Modbus.Addressing/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 3 | 9 |
| [Driver.Modbus.Cli](Driver.Modbus.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 6 | 8 |
| [Driver.OpcUaClient](Driver.OpcUaClient/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 2 | 15 |
| [Driver.S7](Driver.S7/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 14 |
| [Driver.S7.Cli](Driver.S7.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 4 | 7 |
| [Driver.TwinCAT](Driver.TwinCAT/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 5 | 16 |
| [Driver.TwinCAT.Cli](Driver.TwinCAT.Cli/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 7 | 7 |
| [Server](Server/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 12 | 15 |
| [Server](Server/findings.md) | Claude Code | 2026-05-22 | `76d35d1` | Reviewed | 6 | 15 |
## Pending findings
@@ -48,132 +48,6 @@ Findings with status `Open` or `In Progress`, ordered by severity.
| ID | Severity | Category | Location | Description |
|---|---|---|---|---|
| Admin-006 | Medium | Security | `Components/Layout/MainLayout.razor:47-49`, `Program.cs:129,131-135` | `app.UseAntiforgery()` is enabled, but the Sign-out form (`<form method="post" action="/auth/logout">`) renders no antiforgery token, and the `MapPost("/auth/logout", ...)` endpoint does not call `.DisableAntiforgery()` or otherwise opt ou… |
| Admin-007 | Medium | Design-document adherence | `Components/Pages/Clusters/NewCluster.razor:91,95-96` | `NewCluster.CreateAsync` hardcodes `CreatedBy = "admin-ui"` (both on the `ServerCluster` row and the draft generation) instead of the signed-in operator principal name. `admin-ui.md` section "Audit" requires "the operator principal" be rec… |
| Admin-008 | Medium | Error handling & resilience | `Services/ReservationService.cs:28-37` | `ReservationService.ReleaseAsync` calls `sp_ReleaseExternalIdReservation` with only `@Kind`, `@Value`, `@ReleaseReason`. `admin-ui.md` section "Release an external-ID reservation" specifies the proc sets `ReleasedBy` to the FleetAdmin who… |
| Admin-009 | Medium | Testing coverage | `src/Server/ZB.MOM.WW.OtOpcUa.Admin` (whole module) | The module most security-critical behaviours have no enforced test coverage at the boundary that matters. There is no test that an unauthenticated request to a page or hub is rejected (which would have caught Admin-001/002/003), no test of… |
| Analyzers-001 | Medium | Correctness & logic bugs | `src/Tooling/ZB.MOM.WW.OtOpcUa.Analyzers/UnwrappedCapabilityCallAnalyzer.cs:135-139` | `IsInsideWrapperLambda` treats a guarded call as "wrapped" if it is textually inside ANY lambda that is an argument to ANY invocation whose containing type is `CapabilityInvoker` or `AlarmSurfaceInvoker`. It matches the containing type onl… |
| Analyzers-006 | Medium | Testing coverage | `tests/Tooling/ZB.MOM.WW.OtOpcUa.Analyzers.Tests/UnwrappedCapabilityCallAnalyzerTests.cs` | The test suite exercises only 3 of the 7 guarded interfaces (`IReadable`, `IWritable`, `ITagDiscovery`) and one positive / one negative lambda case. Significant untested behaviour for an analyzer that gates a repo-wide resilience invariant… |
| Client.CLI-001 | Medium | Correctness & logic bugs | `Commands/HistoryReadCommand.cs:73`, `Commands/HistoryReadCommand.cs:76` | The start and end options are parsed with `DateTime.Parse(StartTime)` with no `IFormatProvider` or `DateTimeStyles`. Parsing therefore depends on the current OS culture: the same `--start "03/04/2026"` resolves to March 4 on an en-US box a… |
| Client.CLI-005 | Medium | Concurrency & thread safety | `Commands/SubscribeCommand.cs:66-78`, `Commands/AlarmsCommand.cs:52-64` | The `DataChanged` and `AlarmEvent` handlers write to `console.Output` (a `System.IO.TextWriter`) directly from the OPC UA SDK subscription/notification thread, while the command main flow is awaiting `Task.Delay(Timeout.Infinite, ct)` and… |
| Client.Shared-001 | Medium | Correctness & logic bugs | `OpcUaClientService.cs:552` | `OnAlarmEventNotification` returns early when `eventFields.EventFields` has fewer than 6 entries. The event filter built by `CreateAlarmEventFilter` always registers 13 select clauses, so a conforming server returns 13 fields. The `< 6` th… |
| Client.Shared-002 | Medium | Correctness & logic bugs | `OpcUaClientService.cs:351-355`, `OpcUaClientService.cs:373` | `GetRedundancyInfoAsync` performs unguarded unboxing casts on values read from the server: `(int)redundancySupportValue.Value` and `(byte)serviceLevelValue.Value`. Unlike the `ServerUriArray`/`ServerArray` reads below them, the `Redundancy… |
| Client.Shared-007 | Medium | Concurrency & thread safety | `OpcUaClientService.cs:581-622` | In the alarm fallback path, the `Task.Run` closure mutates the captured locals `activeState`, `ackedState`, `time`, and `capturedMessage`, then reads them when invoking `AlarmEvent`. Because the captured `_session` reference can be replace… |
| Client.Shared-008 | Medium | Error handling & resilience | `OpcUaClientService.cs:170-180`, `Helpers/ValueConverter.cs:15-31` | `WriteValueAsync` coerces a string input to the target type by reading the node's current value and inferring the type from `currentDataValue.Value`. When the node has never been written, or the read returns a `Bad` status with a null `Val… |
| Client.UI-001 | Medium | Correctness & logic bugs | `ViewModels/HistoryViewModel.cs:76`, `ViewModels/HistoryViewModel.cs:77` | `ReadHistoryAsync` runs as a `RelayCommand` body, which is invoked on the UI thread, so the bare `IsLoading = true` at line 76 happens to land on the right thread today. But `Results.Clear()` on the very next line is wrapped in `_dispatche… |
| Client.UI-002 | Medium | Correctness & logic bugs | `ViewModels/MainWindowViewModel.cs:255`, `ViewModels/MainWindowViewModel.cs:333` | `ConnectAsync` calls `await BrowseTree.LoadRootsAsync()` and `ViewHistoryForSelectedNode` calls `History.SelectedNodeId = ...` by dereferencing the nullable child view-model properties (`BrowseTreeViewModel?`, `HistoryViewModel?`) without… |
| Client.UI-005 | Medium | Concurrency & thread safety | `ViewModels/MainWindowViewModel.cs:286-304`, `ViewModels/MainWindowViewModel.cs:155-189` | `SubscriptionsViewModel` and `AlarmsViewModel` attach handlers to the long-lived `_service` events (`DataChanged`, `AlarmEvent`) in their constructors and detach them only via `Teardown()`. `Teardown()` is called from `DisconnectAsync` (op… |
| Client.UI-007 | Medium | Security | `Services/UserSettings.cs:22-23`, `Services/JsonSettingsService.cs:38-50`, `ViewModels/MainWindowViewModel.cs:393-408` | The OPC UA `UserName`-token password is persisted in cleartext. `UserSettings.Password` is a plain `string`, `JsonSettingsService.Save` serializes the whole settings object to `settings.json` under `LocalApplicationData`, and `SaveSettings… |
| Client.UI-008 | Medium | Performance & resource management | `ViewModels/MainWindowViewModel.cs:18`, `ViewModels/MainWindowViewModel.cs:125-148`, `App.axaml.cs:18-32` | `IOpcUaClientService` is declared `IDisposable` (`IOpcUaClientService.cs:10`), and the concrete service owns an OPC UA session plus SDK resources. `MainWindowViewModel` holds `_service` for the lifetime of the app but never calls `_service… |
| Configuration-002 | Medium | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260417215224_StoredProcedures.cs:325` | `sp_RollbackToGeneration` opens its own `BEGIN TRANSACTION`, clones rows into a new Draft, then `EXEC dbo.sp_PublishGeneration`, which itself runs `BEGIN TRANSACTION` (nesting `@@TRANCOUNT` to 2) and on its failure paths executes a bare `R… |
| Configuration-003 | Medium | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs:73` | `ValidatePathLength` computes path length with hard-coded constants — it always charges 64 chars for Enterprise+Site (`32 + 32 + ...`) regardless of the cluster's actual values. This over-rejects: a short Enterprise/Site is penalised by up… |
| Configuration-006 | Medium | Error handling & resilience | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/LocalCache/ResilientConfigReader.cs:79` | The fallback `catch` filters on `ex is not OperationCanceledException`. A SQL command timeout surfaced by ADO.NET as a `TaskCanceledException` (derives from `OperationCanceledException`) is then treated as caller cancellation and propagate… |
| Configuration-009 | Medium | Security | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/DesignTimeDbContextFactory.cs:14` | `DefaultConnectionString` embeds a plaintext `sa` password with `User Id=sa` directly in source, checked into the repository. Although used only at design time (`dotnet ef`), a checked-in `sa` credential normalises committing DB passwords… |
| Core-003 | Medium | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrie.cs:80-98` | `WalkSystemPlatform` records every Galaxy folder-segment grant with `NodeAclScopeKind.Equipment` (see the comment at lines 82-86) because `NodeAclScopeKind` has no `FolderSegment` member. The functional union of permission flags is unaffec… |
| Core-005 | Medium | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrieCache.cs:59-70` | `Prune` mutates the `ConcurrentDictionary` with a plain indexer assignment (`_byCluster[clusterId] = new ClusterEntry(...)`) after a separate `TryGetValue` read. If `Install` runs concurrently for the same cluster, the `AddOrUpdate` in `In… |
| Core-006 | Medium | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core/OpcUa/GenericDriverNodeManager.cs:42-64` | `BuildAddressSpaceAsync` is not guarded against being called more than once. A second call subscribes a second `_alarmForwarder` to `IAlarmSource.OnAlarmEvent` and overwrites the `_alarmForwarder` field, so the first delegate is leaked (st… |
| Core-007 | Medium | Error handling & resilience | `src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/AlarmSurfaceInvoker.cs:75-83` | `UnsubscribeAsync` always routes through `_defaultHost`, even when an `IPerCallHostResolver` is wired and the original `SubscribeAsync` fanned the subscription out to a non-default host. The `IAlarmSubscriptionHandle` is opaque here and ca… |
| Core.Abstractions-001 | Medium | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/PollGroupEngine.cs:112` | `PollOnceAsync` detects a change with `!Equals(lastSeen?.Value, current.Value)`. `object.Equals` falls back to reference equality for reference types that do not override it — including `T[]` array values. The capability interfaces explici… |
| Core.Abstractions-002 | Medium | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/PollGroupEngine.cs:105-109` | `PollOnceAsync` iterates `state.TagReferences` and indexes the reader's result with `snapshots[i]`, assuming the driver-supplied `_reader` delegate returns exactly one snapshot per input reference in input order. The contract is documented… |
| Core.Abstractions-003 | Medium | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/PollGroupEngine.cs:64,121-130` | `Subscribe` starts the poll loop with a fire-and-forget `Task.Run` and keeps no reference to the returned `Task`. Neither `Unsubscribe` nor `DisposeAsync` awaits the loop's completion — they only cancel the `CancellationTokenSource` and di… |
| Core.AlarmHistorian-003 | Medium | OtOpcUa conventions | `src/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian/SqliteStoreAndForwardSink.cs:107-127,218-243,246-253` | `EnqueueAsync` is declared `async`-shaped (`Task EnqueueAsync(...)`) and the `IAlarmHistorianSink` contract explicitly states "the sink MUST NOT block the emitting thread … `EnqueueAsync` returns as soon as the queue row is committed." But… |
| Core.AlarmHistorian-005 | Medium | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian/SqliteStoreAndForwardSink.cs:66-71,141-143,199,386-388` | The mutable status fields `_lastDrainUtc`, `_lastSuccessUtc`, `_lastError`, `_drainState`, and `_backoffIndex` are written by the drain timer thread inside `DrainOnceAsync` and read concurrently by `GetStatus()` / `CurrentBackoff` on Admin… |
| Core.AlarmHistorian-007 | Medium | Error handling & resilience | `src/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian/SqliteStoreAndForwardSink.cs:172-174` | When the writer returns a wrong-cardinality result, the code throws `InvalidOperationException` after `WriteBatchAsync` has already succeeded. The events were potentially delivered to the historian, but no rows are deleted or dead-lettered… |
| Core.AlarmHistorian-009 | Medium | Design-document adherence | `src/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian/SqliteStoreAndForwardSink.cs:317-347` | `docs/AlarmTracking.md` and the `IAlarmHistorianSink` contract present the SQLite queue as the durability guarantee — "Durably enqueue the event", "operator acks never block on the historian being reachable". But `EnforceCapacity` silently… |
| Core.AlarmHistorian-010 | Medium | Testing coverage | `tests/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian.Tests/SqliteStoreAndForwardSinkTests.cs` | The test suite covers the happy paths well (Ack/Retry/PermanentFail, capacity eviction, retention purge, ctor validation) but leaves critical paths untested: (a) no test exercises a corrupt / `null`-deserializing `PayloadJson` row, so the… |
| Core.ScriptedAlarms-002 | Medium | Correctness & logic bugs | `ScriptedAlarmEngine.cs:162`, `ScriptedAlarmEngine.cs:90` | `LoadAsync` is written to be re-callable — it begins by calling `UnsubscribeFromUpstream()`, `_alarms.Clear()`, and `_alarmsReferencing.Clear()` (lines 90-92), which only makes sense if a reload is supported. But at line 162 it uncondition… |
| Core.ScriptedAlarms-004 | Medium | Concurrency & thread safety | `ScriptedAlarmEngine.cs:138-143`, `ScriptedAlarmEngine.cs:227-234` | During `LoadAsync`, `_upstream.SubscribeTag(path, OnUpstreamChange)` is called inside the `_evalGate` critical section (line 142). If an upstream implementation delivers an initial value synchronously from inside `SubscribeTag` (a common p… |
| Core.ScriptedAlarms-005 | Medium | Concurrency & thread safety | `ScriptedAlarmEngine.cs:365-369`, `ScriptedAlarmEngine.cs:416-424` | `Dispose` sets `_disposed = true`, disposes `_shelvingTimer`, and clears `_alarms`. A `RunShelvingCheck` callback already in flight on a thread-pool thread can have passed its `if (_disposed) return;` check (line 367) before `Dispose` ran,… |
| Core.ScriptedAlarms-007 | Medium | Error handling & resilience | `ScriptedAlarmEngine.cs:216`, `ScriptedAlarmEngine.cs:251`, `ScriptedAlarmEngine.cs:154`, `ScriptedAlarmEngine.cs:387` | Every state mutation calls `await _store.SaveAsync(...)` and relies on it succeeding. If the production SQL-backed `IAlarmStateStore` (Stream E) throws — transient SQL outage, deadlock, timeout — the exception propagates: in `ApplyAsync` i… |
| Core.ScriptedAlarms-012 | Medium | Testing coverage | `tests/Core/ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms.Tests/ScriptedAlarmEngineTests.cs` | Several engine behaviours central to the module have no test coverage: (1) the 5-second shelving timer / timed-shelve auto-expiry through the *engine* — only the pure `Part9StateMachine.ApplyShelvingCheck` is tested, never `ScriptedAlarmEn… |
| Core.Scripting-003 | Medium | Security | `TimedScriptEvaluator.cs:9`, `ScriptSandbox.cs:30` | There is no bound on memory a script may allocate or on the number of threads/tasks a script may spawn. The class docs acknowledge unbounded memory as "a budget concern" deferred to v3, but in-process execution means a script doing `new by… |
| Core.Scripting-004 | Medium | Correctness & logic bugs | `DependencyExtractor.cs:73` | The walker matches tag-access calls purely by spelling — any `InvocationExpressionSyntax` whose member name is `GetTag` or `SetVirtualTag` is treated as a `ScriptContext` tag access, regardless of the receiver. A script that defines a loca… |
| Core.Scripting-007 | Medium | Error handling & resilience | `TimedScriptEvaluator.cs:60` | `RunAsync` wraps the inner run in `Task.Run(...)` and then awaits `WaitAsync(Timeout, ct)`. If the caller-supplied `ct` cancels at roughly the same time the timeout elapses, the order in which `WaitAsync` observes the timeout vs. the cance… |
| Core.Scripting-010 | Medium | Testing coverage | `tests/Core/ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests/ScriptSandboxTests.cs:54` | The sandbox-escape test suite covers only the four obvious vectors (File / Http / Process / Reflection) as direct member-access calls. It does not test: `typeof(forbidden)`, generic type arguments (`List<FileInfo>`), cast expressions to fo… |
| Core.VirtualTags-002 | Medium | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags/VirtualTagEngine.cs:237` | The cold-start guard `if (!AreInputsReady(ctxCache)) return;` silently abandons the evaluation when any input is null or Bad-quality. For a chained virtual tag (C depends on B depends on driver tag A), if A is still Bad at startup, B is sk… |
| Core.VirtualTags-003 | Medium | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags/VirtualTagEngine.cs:117-120` | The upstream-subscription loop in `Load` iterates `definitions.SelectMany(d => _tags[d.Path].Reads)`. If `definitions` contains two rows with the same Path, the first registers `_tags[Path]` and the second overwrites it, but `definitions`… |
| Core.VirtualTags-005 | Medium | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags/VirtualTagSource.cs:50-64` | `SubscribeAsync` registers the per-path engine observers first (lines 52-56), then in a second loop reads the current value and fires the initial-data callback (lines 60-64). Between those two loops an upstream change can cascade and the e… |
| Core.VirtualTags-008 | Medium | Performance & resource management | `src/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags/DependencyGraph.cs:81-115` | `TransitiveDependentsInOrder` calls `TopologicalSort()` (a full O(V+E) Kahn pass plus a Dictionary rank build) on every invocation, and it is invoked from `CascadeAsync` on every upstream change event (`OnUpstreamChange`). On a large graph… |
| Core.VirtualTags-012 | Medium | Testing coverage | `tests/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags.Tests/` | Several behaviours of the engine have no test coverage: (1) the cold-start `AreInputsReady` guard -- no test exercises an upstream that is null/Bad at evaluation time and asserts the resulting tag state (see Core.VirtualTags-002); (2) `ctx… |
| Driver.AbCip-004 | Medium | Correctness & logic bugs | `AbCipDataType.cs:51-58`, `LibplctagTagRuntime.cs:47-49,53` | `ToDriverDataType` maps `LInt`/`ULInt` to `DriverDataType.Int32` (a TODO comment notes the gap) and `Dt` to `Int32`. But `LibplctagTagRuntime.DecodeValueAt` returns an actual `long` for `LInt`/`ULInt` (`_tag.GetInt64`, `(long)_tag.GetUInt6… |
| Driver.AbCip-005 | Medium | Correctness & logic bugs | `AbCipDriver.cs:124-141` | In `InitializeAsync`, when a `Structure` tag declares `Members`, the loop registers each fanned-out member into `_tagsByName` but the parent Structure tag itself is also left in `_tagsByName` (added at line 125 before the member check). A… |
| Driver.AbCip-006 | Medium | OtOpcUa conventions | `PlcTagHandle.cs:28-59`, `AbCipDriver.cs:806-807,832-833`, `LibplctagTagRuntime.cs:117` | `driver-specs.md` makes the SafeHandle-wrapped native handle a non-negotiable Tier-B protection ("Wrap every libplctag handle in a SafeHandle with finalizer calling plc_tag_destroy"). The repo ships `PlcTagHandle : SafeHandle` for this, bu… |
| Driver.AbCip-009 | Medium | Concurrency & thread safety | `AbCipDriver.cs:621-648`, `AbCipDriver.cs:591-614` | `EnsureTagRuntimeAsync` and `EnsureParentRuntimeAsync` are check-then-act on a non-thread-safe `Dictionary` (`device.Runtimes` / `device.ParentRuntimes`). `ReadAsync` is `IReadable` and may be invoked concurrently: the server read path, ea… |
| Driver.AbCip-010 | Medium | Error handling & resilience | `AbCipDriver.cs:621-648`, `AbCipDriver.cs:346-391` | Once `EnsureTagRuntimeAsync` successfully creates and initializes a `LibplctagTagRuntime`, that runtime is cached for the lifetime of the device and never re-created on failure. If the underlying native tag enters a permanently-bad state (… |
| Driver.AbCip-014 | Medium | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/AbCipStatusMapperTests.cs:28-40` | `AbCipStatusMapperTests.MapLibplctagStatus_maps_known_codes` asserts the mapper against the same wrong integer constants (-5, -7, -14, -16, -17) the production code uses (see Driver.AbCip-002). The test locks in the bug rather than catchin… |
| Driver.AbCip.Cli-001 | Medium | Error handling & resilience | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli/Commands/WriteCommand.cs:70-85` | `ParseValue` parses every numeric Logix type with the BCL `*.Parse` methods (`sbyte.Parse`, `short.Parse`, `int.Parse`, `float.Parse`, ...). These throw the raw `FormatException` and `OverflowException` on bad operator input. The module's… |
| Driver.AbCip.Cli-002 | Medium | Correctness & logic bugs | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli/Commands/ProbeCommand.cs:21-23`; `Commands/ReadCommand.cs:24-25`; `Commands/SubscribeCommand.cs:20-22` | `ProbeCommand`, `ReadCommand`, and `SubscribeCommand` expose `--type` as a free `AbCipDataType` enum option with no exclusion of `AbCipDataType.Structure`. Only `WriteCommand` rejects `Structure` (with an explicit `CommandException`). Pass… |
| Driver.AbLegacy-002 | Medium | Correctness & logic bugs | `AbLegacyDriver.cs:368` | In `WriteBitInWordAsync` the parent word is decoded with `Convert.ToInt32(parentRuntime.DecodeValue(AbLegacyDataType.Int, ...))`. `LibplctagLegacyTagRuntime.DecodeValue` for `AbLegacyDataType.Int` returns `(int)_tag.GetInt16(0)` - a sign-e… |
| Driver.AbLegacy-003 | Medium | Correctness & logic bugs | `AbLegacyAddress.cs:62-95` | `TryParse` does not reject several malformed PCCC addresses that the XML docs imply are invalid: - A sub-element and a bit index together (`T4:0.ACC/2`) parse successfully even though no PCCC element supports both. - I/O/S files with a fil… |
| Driver.AbLegacy-004 | Medium | Correctness & logic bugs | `LibplctagLegacyTagRuntime.cs:36-37` | `DecodeValue` for `AbLegacyDataType.Bit` with `bitIndex == null` returns `_tag.GetInt8(0) != 0`. A bit-file element (`B3:0/0`) is a single bit inside a 16-bit word; reading only the low byte (`GetInt8(0)`) means a `Bit` tag whose live bit… |
| Driver.AbLegacy-007 | Medium | Concurrency & thread safety | `AbLegacyDriver.cs:411-438`, `AbLegacyDriver.cs:386-409` | `EnsureTagRuntimeAsync` and `EnsureParentRuntimeAsync` are check-then-act: `device.Runtimes.TryGetValue(...)` then, after `await runtime.InitializeAsync`, `device.Runtimes[def.Name] = runtime`. `Dictionary` is not thread-safe, and two conc… |
| Driver.AbLegacy-008 | Medium | Concurrency & thread safety | `AbLegacyDriver.cs:21`, `AbLegacyDriver.cs:138-146`, `AbLegacyDriver.cs:216-229` | `_health` is a plain non-volatile reference field mutated from `ReadAsync`, `WriteAsync` (both can run on multiple threads / poll loops) and `InitializeAsync`/`ShutdownAsync`, and read by `GetHealth()` from yet another thread. There is no… |
| Driver.AbLegacy-009 | Medium | Error handling & resilience | `AbLegacyDriver.cs:41-74` | `InitializeAsync` starts probe loops with `Task.Run` inside the try block. If `InitializeAsync` fails - or is re-entered - after some probe loops are already started, the catch only sets `_health = Faulted` and rethrows; it does not cancel… |
| Driver.AbLegacy-010 | Medium | Error handling & resilience | `AbLegacyStatusMapper.cs:26-56` | `MapLibplctagStatus` maps the integer codes -5/-7/-14/-16/-17. These do not match the native libplctag PLCTAG_ERR_* constants (PLCTAG_ERR_TIMEOUT = -32, PLCTAG_ERR_NOT_FOUND = -22, PLCTAG_ERR_NOT_ALLOWED = -21, PLCTAG_ERR_OUT_OF_BOUNDS = -… |
| Driver.AbLegacy-012 | Medium | Design-document adherence | `PlcFamilies/AbLegacyPlcFamilyProfile.cs:7-54`, `AbLegacyDriver.cs:48-52` | `AbLegacyPlcFamilyProfile` declares four record properties - `DefaultCipPath`, `MaxTagBytes`, `SupportsStringFile`, `SupportsLongFile` - and only `LibplctagPlcAttribute` is ever consumed. In particular: - `DefaultCipPath` is dead: the per-… |
| Driver.AbLegacy.Cli-001 | Medium | Error handling & resilience | `Commands/WriteCommand.cs:46`, `Commands/WriteCommand.cs:62-72` | `WriteCommand.ExecuteAsync` calls `ParseValue(Value, DataType)` at line 46, *before* the `try` block and outside any catch. `ParseValue` uses `short.Parse` / `int.Parse` / `float.Parse`, which throw `FormatException` on malformed input (`-… |
| Driver.Cli.Common-002 | Medium | Correctness & logic bugs | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common/SnapshotFormatter.cs:101-122` | `FormatStatus` matches the full 32-bit status word for exact equality against the shortlist. OPC UA status codes carry sub-code/flag bits in the low 16 bits (info type, structure-changed, semantics-changed, limit bits, overflow, etc.). A d… |
| Driver.Cli.Common-003 | Medium | Concurrency & thread safety | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common/DriverCommandBase.cs:51-59` | `ConfigureLogging` assigns the process-global `Serilog.Log.Logger` without disposing the previously assigned logger and the library never calls `Log.CloseAndFlush()`. Each call creates a fresh `Logger` via `CreateLogger()` and overwrites `… |
| Driver.Cli.Common-005 | Medium | Testing coverage | `tests/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common.Tests/SnapshotFormatterTests.cs:27-37` | The `FormatStatus_names_well_known_status_codes` `[Theory]` asserts `0x80060000 => "BadTimeout"`, which encodes the wrong spec value (see Driver.Cli.Common-001). The test passes because it validates the formatter against the same incorrect… |
| Driver.FOCAS-003 | Medium | Correctness & logic bugs | `FocasDriver.cs:71-79` | In `InitializeAsync`, capability-matrix validation only runs when `_devices.TryGetValue(tag.DeviceHostAddress, out var device)` succeeds. A tag whose `DeviceHostAddress` does not match any configured device (a common config typo, e.g. a tr… |
| Driver.FOCAS-004 | Medium | OtOpcUa conventions | `FocasDriver.cs:374-379`, `WireFocasClient.cs:48-50` | `DiscoverAsync` emits user tags with `SecurityClass = tag.Writable ? SecurityClassification.Operate : SecurityClassification.ViewOnly`, and `FocasTagDefinition.Writable` defaults to `true` (also defaulted to `true` in the factory - `t.Writ… |
| Driver.FOCAS-005 | Medium | Concurrency & thread safety | `FocasDriver.cs:28`, `FocasDriver.cs:206-215`, `FocasDriver.cs:261`, `FocasDriver.cs:274` | `_health` is a plain (non-volatile) field mutated from multiple concurrent contexts - `ReadAsync`, `WriteAsync`, and the per-device `ProbeLoopAsync` can all run on different threads simultaneously (subscriptions go through `PollGroupEngine… |
| Driver.FOCAS-006 | Medium | Error handling & resilience | `FocasDriver.cs:859-874`, `WireFocasClient.cs:22-31` | `EnsureConnectedAsync` reuses the cached `IFocasClient` instance across a transient disconnect: it only checks `device.Client is { IsConnected: true }` and otherwise calls `ConnectAsync` again on the same object. For a `WireFocasClient` wh… |
| Driver.FOCAS-012 | Medium | Testing coverage | `FocasDriverFactoryExtensions.cs`, `FocasDriver.cs:495-629` (`FixedTreeLoopAsync`) | The unit test project does not exercise `FocasDriverFactoryExtensions.CreateInstance` with `FixedTree` / `AlarmProjection` / `HandleRecycle` config sections - which is why the config-mapping gap in Driver.FOCAS-001 was not caught. There is… |
| Driver.Galaxy-003 | Medium | Correctness & logic bugs | `Runtime/StatusCodeMap.cs:86` | `FromMxStatus` returns `Good` whenever `status.Success != 0`. The intent (per the surrounding comment "Honors the success flag") is that a non-zero `Success` means success. But if `MxStatusProxy.Success` is itself a native HRESULT/return c… |
| Driver.Galaxy-004 | Medium | Correctness & logic bugs | `GalaxyDriver.cs:901` | `OnPumpDataChange` reconstructs a raw OPC DA quality byte from an OPC UA `StatusCode` for the probe watcher: it shifts `StatusCode >> 30` and maps `0->192, 1->64, _->0`. The `StatusCode` was itself produced upstream by `StatusCodeMap.FromQ… |
| Driver.Galaxy-006 | Medium | Concurrency & thread safety | `GalaxyDriver.cs:848-861` | `OnAlarmFeedTransition` picks the "owner" handle with `_alarmSubscriptions.First()` under `_alarmHandlersLock`. `HashSet<T>.First()` enumeration order is unspecified and unstable across mutations — when multiple alarm subscriptions are act… |
| Driver.Galaxy-007 | Medium | Concurrency & thread safety | `GalaxyDriver.cs:937-968` | `Dispose()` is not synchronized against the capability methods. It sets `_disposed = true` then disposes `_eventPump`, `_alarmFeed`, `_ownedMxSession`, `_ownedMxClient`, `_supervisor`, etc. A concurrent `SubscribeAsync`/`ReadAsync`/`WriteA… |
| Driver.Galaxy-009 | Medium | Error handling & resilience | `GalaxyDriver.cs:354-371` | `StartDeployWatcher` launches the watch loop with `_ = _deployWatcher.StartAsync(CancellationToken.None)` — a fire-and-forget with a discarded `Task`. `StartAsync` can throw synchronously (`InvalidOperationException` if already started); t… |
| Driver.Galaxy-011 | Medium | Performance & resource management | `GalaxyDriver.cs:411` | `GetMemoryFootprint()` unconditionally returns `0` with a comment "PR 4.4 sets this from SubscriptionRegistry size" — PR 4.4 has shipped (the registry exists and is used) but the method was never updated. `IHostConnectivityProbe.GetMemoryF… |
| Driver.Galaxy-014 | Medium | Testing coverage | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy` (module-wide) | The reconnect/recovery path is the module's highest-risk surface and is effectively untested at the integration seam. The `ReconnectSupervisor` has a clean test seam (injectable `reopen`/`replay`/`backoffDelay`), but because nothing wires… |
| Driver.Historian.Wonderware-002 | Medium | Correctness and logic bugs | `Ipc/HistorianFrameHandler.cs:162`, `:181` | `HandleWriteAlarmEventsAsync` dereferences `req.Events.Length` in both the `_alarmWriter is null` branch (line 162) and the catch block (line 181). MessagePack deserializes an absent or explicit-nil array field as a `null` reference, not `… |
| Driver.Historian.Wonderware-003 | Medium | Correctness and logic bugs | `Backend/HistorianDataSource.cs:320-323`, `:457-460` | Raw and at-time reads decide whether a sample is a string or a numeric with `if (!string.IsNullOrEmpty(result.StringValue) && result.Value == 0)`. The `result.Value == 0` clause is intended to distinguish a real numeric zero from a string… |
| Driver.Historian.Wonderware-006 | Medium | Error handling and resilience | `Ipc/PipeServer.cs:120-128` | `RunAsync` re-accepts connections in a `while` loop. If `RunOneConnectionAsync` throws synchronously and immediately on every iteration (for example `new NamedPipeServerStream(...)` fails because the pipe name is already in use, or `PipeAc… |
| Driver.Historian.Wonderware-009 | Medium | Performance and resource management | `Backend/HistorianDataSource.cs:382-395`, `Ipc/Contracts.cs:85-99` | `ReadAggregateAsync` drains `query.MoveNext` into `results` with no upper bound, unlike `ReadRawAsync`, which honours `maxValues` / `MaxValuesPerRead` and breaks. `ReadProcessedRequest` carries no max-buckets field. A processed read over a… |
| Driver.Historian.Wonderware.Client-002 | Medium | Correctness & logic bugs | `WonderwareHistorianClient.cs:154-199`, `IAlarmHistorianSink.cs:66-74` | `WriteBatchAsync` can never return `HistorianWriteOutcome.PermanentFail`. `HistorianWriteOutcome` defines three states (`Ack`, `RetryPlease`, `PermanentFail`) and the drain worker is documented to move the event to the dead-letter table on… |
| Driver.Historian.Wonderware.Client-005 | Medium | Error handling & resilience | `Ipc/FrameReader.cs:31-32` | After reading the 4-byte length prefix, `ReadFrameAsync` reads the kind byte with the synchronous, blocking `_stream.ReadByte()` and ignores the `CancellationToken`. On a `NamedPipeClientStream` with `PipeOptions.Asynchronous`, a synchrono… |
| Driver.Historian.Wonderware.Client-007 | Medium | Security | `WonderwareHistorianClient.cs:276` | `ToSnapshots` deserializes peer-supplied bytes with `MessagePackSerializer.Deserialize<object>(dto.ValueBytes)`, typeless MessagePack deserialization. The `object` overload resolves runtime types from the wire payload. The client treats th… |
| Driver.Historian.Wonderware.Client-009 | Medium | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests/WonderwareHistorianClientTests.cs` | The suite covers happy paths, server-error, bad-secret, a single reconnect and health counters, but several critical paths are untested: (1) `ReadAtTimeAsync` with a partial/reordered sidecar reply, the contract-alignment case from finding… |
| Driver.Modbus-002 | Medium | Correctness & logic bugs | `ModbusDriver.cs:127-186` | `ShutdownAsync` never clears `_tagsByName`, and `InitializeAsync` repopulates it with `_tagsByName[t.Name] = t` (`ModbusDriver.cs:134`) without clearing first. `ReinitializeAsync` calls `ShutdownAsync` then `InitializeAsync`. Because `_opt… |
| Driver.Modbus-004 | Medium | Performance & resource management | `ModbusDriver.cs:1468-1473` | `DisposeAsync()` only disposes `_transport`. Unlike `ShutdownAsync`, it does not cancel/dispose `_probeCts` or `_reprobeCts`, nor dispose `_poll` (the `PollGroupEngine`). A caller that uses `await using` or `using` without first calling `S… |
| Driver.Modbus-005 | Medium | Correctness & logic bugs | `ModbusDriver.cs:777-798,323-330` | `ReadRegisterBlockAsync` and `ReadBitBlockAsync` index `resp[1]` and call `Buffer.BlockCopy(resp, 2, ..., resp[1])` with no bounds validation. `ModbusTcpTransport.SendOnceAsync` validates only the MBAP length field and the exception high-b… |
| Driver.Modbus-006 | Medium | Error handling & resilience | `ModbusDriver.cs:514-524,532-550` | `RunReprobeOnceForTestAsync` reads `_transport` once at the top (`var transport = _transport ?? throw ...`). If `ShutdownAsync` runs (setting `_transport = null` and disposing it) while a re-probe pass is mid-iteration, the loop keeps issu… |
| Driver.Modbus.Addressing-002 | Medium | Correctness & logic bugs | `ModbusAddressParser.cs:86-94` | In the 3-field disambiguation, an empty 3rd field (`40001:F:`) reaches `parts[2].All(char.IsDigit)`. `Enumerable.All` returns true for an empty sequence, so the empty string is classified as a valid-shaped array count, assigned to `countPa… |
| Driver.Modbus.Addressing-003 | Medium | Correctness & logic bugs | `ModbusAddressParser.cs:405-406`, `ModbusAddressParser.cs:128` | `LooksLikeByteOrderToken` classifies any 4-letter token as a byte-order token. A 3-field address whose 3rd field is a 4-letter type-like token (e.g. `40001:S:BOOL`) is routed into `TryParseByteOrder`, producing the misleading diagnostic "U… |
| Driver.Modbus.Addressing-004 | Medium | Correctness & logic bugs | `ModbusAddressParser.cs:182-194` | The bit suffix is stripped using `text.IndexOf('.')` — the first dot. An input such as `40001.5.3` produces a bit text of "5.3", rejected by `byte.TryParse` with the generic "Bit index must be 0..15" message. A Modicon-style decimal-point… |
| Driver.Modbus.Addressing-005 | Medium | Error handling & resilience | `ModbusAddressParser.cs:200-213` | `TryParseRegionAndOffset` tries family-native, then mnemonic, then Modicon. When all three fail it returns false with whatever error the Modicon parser last wrote (comment: "the Modicon error is the more specific diagnostic"). For a non-Ge… |
| Driver.Modbus.Addressing-008 | Medium | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.Tests/` | Several edge cases of the address arithmetic are untested or asserted wrong: (a) DL205 system V-memory mapping is tested only with the incorrect expected value (`ModbusFamilyParserTests.cs:20`, see finding -001); (b) there is no test for `… |
| Driver.Modbus.Cli-001 | Medium | Correctness & logic bugs | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli/Commands/SubscribeCommand.cs:43-51` | `SubscribeCommand` synthesises its `ModbusTagDefinition` with only `Name`, `Region`, `Address`, `DataType`, `Writable`, and `ByteOrder` — it never exposes or passes `--bit-index`, `--string-length`, or `--string-byte-order`. A user running… |
| Driver.Modbus.Cli-002 | Medium | Correctness & logic bugs | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli/Commands/WriteCommand.cs:54-89` | `WriteCommand` rejects read-only regions (`DiscreteInputs` / `InputRegisters`) but does not validate that `--type` is meaningful for the `Coils` region. `write -r Coils -a 5 -t UInt16 -v 42` builds a `Coils` tag with `DataType = UInt16`; t… |
| Driver.OpcUaClient-006 | Medium | Concurrency & thread safety | `OpcUaClientDriver.cs:1330-1359` | OnReconnectComplete mutates `Session` (line 1347) directly from the reconnect-handler callback thread with no synchronization against ReadAsync/WriteAsync/DiscoverAsync/ShutdownAsync. Session is a plain auto-property with no memory barrier… |
| Driver.OpcUaClient-007 | Medium | Concurrency & thread safety | `OpcUaClientDriver.cs:1374`, `:1376-1383`, `:508` | Two disposal races. (1) Dispose() does `DisposeAsync().AsTask().GetAwaiter().GetResult()`, synchronous blocking on async work. The Galaxy stability review (driver-stability.md, the 2026-04-13 findings) explicitly calls out sync-over-async… |
| Driver.OpcUaClient-008 | Medium | Error handling & resilience | `OpcUaClientDriver.cs:1092-1099` | AcknowledgeAsync issues the batched CallAsync and then catches all exceptions with a best-effort empty catch; it also never inspects the per-call results in the success path (`_ = await session.CallAsync(...)`). An alarm acknowledgment the… |
| Driver.OpcUaClient-009 | Medium | Error handling & resilience | `OpcUaClientDriver.cs:560-564` | WriteAsync's catch block fans out BadCommunicationError across the whole batch on any exception. Writes are non-idempotent by default (IWritable remarks, decision #44/#45): a timeout exception may fire after the upstream server already app… |
| Driver.OpcUaClient-010 | Medium | Correctness & logic bugs | `OpcUaClientDriver.cs:823-824` | MapUpstreamDataType maps DataTypeIds.Byte (the OPC UA unsigned 8-bit type) to DriverDataType.Int16. Byte should map to an unsigned driver type (UInt16 is the smallest unsigned available, matching how SByte belongs with the signed family).… |
| Driver.OpcUaClient-012 | Medium | Security | `OpcUaClientDriver.cs:210-217` | When AutoAcceptCertificates is true the driver registers a CertificateValidation handler that accepts only StatusCodes.BadCertificateUntrusted. A self-signed or otherwise untrusted server certificate frequently fails validation with a diff… |
| Driver.OpcUaClient-013 | Medium | Performance & resource management | `OpcUaClientDriver.cs:436-437` | GetMemoryFootprint() is hard-coded to return 0 and FlushOptionalCachesAsync is a no-op Task.CompletedTask. docs/v2/driver-stability.md section "In-process only (Tier A/B)" makes per-instance allocation tracking a contract requirement, and… |
| Driver.OpcUaClient-015 | Medium | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/*`, `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcUaClientSmokeTests.cs` | Unit-test coverage is solid for the pure mappers (MapSeverity, MapUpstreamDataType, MapSecurityPolicy, MapAggregateToNodeId, BuildCertificateIdentity, ResolveEndpointCandidates) and for "throws before init" guards, but the highest-risk beh… |
| Driver.S7-002 | Medium | Correctness & logic bugs | `S7Driver.cs:350` | MapDataType collapses S7DataType.UInt32 to DriverDataType.Int32. UInt32 values above int.MaxValue (2^31-1) wrap to negative when surfaced to the OPC UA client, silently corrupting the value. The inline comment only flags Int64/UInt64 as "w… |
| Driver.S7-004 | Medium | OtOpcUa conventions | `S7Driver.cs` (whole file) | The driver performs no logging. CLAUDE.md Library Preferences mandate Serilog with a rolling daily file sink. Every error path is an empty catch block (Initialize cleanup line 130, ShutdownAsync lines 142/149/153, ProbeLoop line 483, PollL… |
| Driver.S7-008 | Medium | Error handling & resilience | `S7Driver.cs:286` | WriteAsync catch ladder is coarser than ReadAsync and loses information. The generic catch (Exception) maps everything - socket errors, timeouts, OverflowException from Convert.ToInt16 of an out-of-range value, NullReferenceException from… |
| Driver.S7-012 | Medium | Design-document adherence | `S7DriverOptions.cs:59`, `S7Driver.cs:457` | S7ProbeOptions.ProbeAddress is configured (default "MW0"), documented at length ("the driver runs a tick loop that issues a cheap read against S7ProbeOptions.ProbeAddress"), surfaced in the factory DTO (S7ProbeDto.ProbeAddress), and parsed… |
| Driver.S7-014 | Medium | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/` | Test coverage has notable gaps for the driver behavioural core: (1) no test exercises the ReadOneAsync type-reinterpret switch (Int16 from ushort, Int32 from uint, Float32 from UInt32 bits) - the most logic-heavy method in the driver is un… |
| Driver.S7.Cli-001 | Medium | Error handling & resilience | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.S7.Cli/Commands/WriteCommand.cs:65-80` | `WriteCommand.ParseValue` parses numeric and `DateTime` values with the raw BCL parsers (`short.Parse`, `float.Parse`, `DateTime.Parse`, etc.). On malformed input these throw `FormatException` / `OverflowException`, which are *not* `CliFx.… |
| Driver.S7.Cli-002 | Medium | Design-document adherence | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.S7.Cli/Commands/ReadCommand.cs:22-29`, `Commands/WriteCommand.cs:21-33`, `Commands/SubscribeCommand.cs:18-21`; `docs/Driver.S7.Cli.md:70-73,80-81` | The `--type` option help text on `read`, `write`, and `subscribe` advertises the full `S7DataType` set (`Int64 / UInt64 / Float64 / String / DateTime`), and `docs/Driver.S7.Cli.md` shows a worked `read ... -t String --string-length 80` exa… |
| Driver.S7.Cli-003 | Medium | Error handling & resilience | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.S7.Cli/Commands/ProbeCommand.cs:38-50` | `ProbeCommand` XML doc and the `Driver.S7.Cli.md` "fastest is the device talking" framing say the probe "connects ... prints health" and "surfaces `BadNotSupported`" when PUT/GET is disabled. But when the PLC is unreachable (connection ref… |
| Driver.TwinCAT-003 | Medium | Correctness & logic bugs | `AdsTwinCATClient.cs:264-281`, `283-300` | `MapToClrType` has a `_ => typeof(int)` fallthrough and `ConvertForWrite` has a `_ => throw NotSupportedException` fallthrough. `TwinCATDataType.Structure` is a declared enum member, and a config-supplied tag can carry `DataType: "Structur… |
| Driver.TwinCAT-005 | Medium | OtOpcUa conventions | `TwinCATDriver.cs` (whole file), `AdsTwinCATClient.cs` (whole file) | The driver performs no logging. `CLAUDE.md` Library Preferences mandate Serilog with a rolling daily file sink. Connect failures, ADS error codes, symbol-browse failures (`DiscoverAsync` swallows them in a bare `catch`), notification-regis… |
| Driver.TwinCAT-009 | Medium | Concurrency & thread safety | `TwinCATDriver.cs:80-99`, `41-72`, `366-388` | `ShutdownAsync` mutates `_devices`, `_tagsByName`, and `_nativeSubs` with no synchronization while `ReadAsync`/`WriteAsync`/`SubscribeAsync` may be iterating or indexing those same plain `Dictionary<>` instances on other threads (`_devices… |
| Driver.TwinCAT-010 | Medium | Error handling & resilience | `AdsTwinCATClient.cs:178-195` | `BrowseSymbolsAsync` checks `cancellationToken.IsCancellationRequested` and does `yield break` (a clean completion) rather than throwing `OperationCanceledException`. `DiscoverAsync` (`TwinCATDriver.cs:274`) explicitly has `catch (Operatio… |
| Driver.TwinCAT-011 | Medium | Error handling & resilience | `TwinCATStatusMapper.cs:29-42` | ADS error-code mapping has gaps and an inconsistency versus `docs/v2/driver-specs.md` section 6. The spec documents symbol-not-found as 0x0701 (1793 decimal) and symbol-version-changed as 0x0702 (1794 decimal). `MapAdsError` maps decimal 1… |
| Driver.TwinCAT-012 | Medium | Performance & resource management | `TwinCATDriver.cs:102`, `AdsTwinCATClient.cs:178-195` | `GetMemoryFootprint()` returns a hard-coded 0. `docs/v2/driver-stability.md` section "In-process only (Tier A/B) — driver-instance allocation tracking" requires the footprint to reflect "bytes attributable to their own caches (symbol cache… |
| Server-003 | Medium | Correctness & logic bugs | `src/Server/ZB.MOM.WW.OtOpcUa.Server/Phase7/RingBufferHistoryWriter.cs:96-119` | `ReadRawAsync`'s XML doc claims "newest-first," but `TagRingBuffer.Snapshot()` returns oldest-to-newest and the loop preserves that order — so results are oldest-first. Also `maxValuesPerNode` is capped against total buffer size *before* t… |
| Server-005 | Medium | Concurrency & thread safety | `src/Server/ZB.MOM.WW.OtOpcUa.Server/Alarms/AlarmConditionService.cs:166`, `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/DriverNodeManager.cs:303-311` | `OnValueChanged` raises `TransitionRaised` on the value-change thread; the subscriber `OnAlarmServiceTransition` drives `ConditionSink.OnTransition``alarm.ReportEvent`. `DriverNodeManager.Dispose` detaches the handler but does not synch… |
| Server-007 | Medium | Error handling & resilience | `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs:179-183` | `HealthEndpointsHost` is built without a `configDbHealthy` delegate, so the default `() => true` is used — `/healthz` always reports `configDbReachable = true` and never 503s on a DB outage. `_staleConfigFlag` is also never supplied by `Pr… |
| Server-010 | Medium | Security | `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaServerOptions.cs:59`, `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs:284-291` | `AutoAcceptUntrustedClientCertificates` defaults to `true` (`Program.cs` reads `?? true`). `BuildConfiguration` wires a handler that accepts any client cert failing with `BadCertificateUntrusted`. A deployment that forgets to flip the flag… |
| Server-011 | Medium | Security | `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs:322-346` | `BuildUserTokenPolicies` advertises a `UserName` token policy only when `SecurityProfile == Basic256Sha256SignAndEncrypt && Ldap.Enabled`. With the default `SecurityProfile = None` and `Ldap.Enabled = true`, the LDAP authenticator is wired… |
| Server-013 | Medium | Design-document adherence | `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaServerOptions.cs:9-19`, `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs:296-346`, `src/Server/ZB.MOM.WW.OtOpcUa.Server/Program.cs:89` | `docs/security.md` documents 7 transport security profiles and `CLAUDE.md` references a `SecurityProfileResolver`. The code's `OpcUaSecurityProfile` enum has only `None` and `Basic256Sha256SignAndEncrypt`; `BuildSecurityPolicies` adds a po… |
| Admin-010 | Low | OtOpcUa conventions | `Components/App.razor:9,16` | `App.razor` loads Bootstrap CSS and JS from the `cdn.jsdelivr.net` CDN. `admin-ui.md` section "Tech Stack" specifies "Bootstrap 5 vendored under `wwwroot/lib/bootstrap/`" precisely so the Admin app has no third-party runtime dependency. A… |
| Admin-011 | Low | Concurrency & thread safety | `Hubs/FleetStatusPoller.cs:24-26,98-103` | `FleetStatusPoller` keeps three plain `Dictionary<>` fields (`_last`, `_lastRole`, `_lastResilience`) mutated from `PollOnceAsync`. The poller `ExecuteAsync` loop is single-threaded so the steady-state poll path is safe, but `ResetCache()`… |
| Admin-012 | Low | Design-document adherence | `Services/EquipmentCsvImporter.cs:18-19,33-37,229,232` | `EquipmentCsvImporter` declares `EquipmentId` as a required CSV column and parses it into a `required` field. `admin-ui.md` section "Equipment CSV import" (revised after adversarial review finding #4) is explicit: "No `EquipmentId` column… |
@@ -389,3 +263,129 @@ Findings with status `Resolved`, `Won't Fix`, or `Deferred`.
| Driver.TwinCAT-013 | High | Resolved | Design-document adherence | `TwinCATDriver.cs:11-12` (capability list), whole file |
| Server-002 | High | Resolved | Correctness & logic bugs | `src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/AuthorizationGate.cs:60-63` |
| Server-009 | High | Resolved | Security | `src/Server/ZB.MOM.WW.OtOpcUa.Server/Security/LdapOptions.cs:44`, `src/Server/ZB.MOM.WW.OtOpcUa.Server/Program.cs:74` |
| Admin-006 | Medium | Resolved | Security | `Components/Layout/MainLayout.razor:47-49`, `Program.cs:129,131-135` |
| Admin-007 | Medium | Resolved | Design-document adherence | `Components/Pages/Clusters/NewCluster.razor:91,95-96` |
| Admin-008 | Medium | Resolved | Error handling & resilience | `Services/ReservationService.cs:28-37` |
| Admin-009 | Medium | Resolved | Testing coverage | `src/Server/ZB.MOM.WW.OtOpcUa.Admin` (whole module) |
| Analyzers-001 | Medium | Resolved | Correctness & logic bugs | `src/Tooling/ZB.MOM.WW.OtOpcUa.Analyzers/UnwrappedCapabilityCallAnalyzer.cs:135-139` |
| Analyzers-006 | Medium | Resolved | Testing coverage | `tests/Tooling/ZB.MOM.WW.OtOpcUa.Analyzers.Tests/UnwrappedCapabilityCallAnalyzerTests.cs` |
| Client.CLI-001 | Medium | Resolved | Correctness & logic bugs | `Commands/HistoryReadCommand.cs:73`, `Commands/HistoryReadCommand.cs:76` |
| Client.CLI-005 | Medium | Resolved | Concurrency & thread safety | `Commands/SubscribeCommand.cs:66-78`, `Commands/AlarmsCommand.cs:52-64` |
| Client.Shared-001 | Medium | Resolved | Correctness & logic bugs | `OpcUaClientService.cs:552` |
| Client.Shared-002 | Medium | Resolved | Correctness & logic bugs | `OpcUaClientService.cs:351-355`, `OpcUaClientService.cs:373` |
| Client.Shared-007 | Medium | Resolved | Concurrency & thread safety | `OpcUaClientService.cs:581-622` |
| Client.Shared-008 | Medium | Resolved | Error handling & resilience | `OpcUaClientService.cs:170-180`, `Helpers/ValueConverter.cs:15-31` |
| Client.UI-001 | Medium | Resolved | Correctness & logic bugs | `ViewModels/HistoryViewModel.cs:76`, `ViewModels/HistoryViewModel.cs:77` |
| Client.UI-002 | Medium | Resolved | Correctness & logic bugs | `ViewModels/MainWindowViewModel.cs:255`, `ViewModels/MainWindowViewModel.cs:333` |
| Client.UI-005 | Medium | Resolved | Concurrency & thread safety | `ViewModels/MainWindowViewModel.cs:286-304`, `ViewModels/MainWindowViewModel.cs:155-189` |
| Client.UI-007 | Medium | Resolved | Security | `Services/UserSettings.cs:22-23`, `Services/JsonSettingsService.cs:38-50`, `ViewModels/MainWindowViewModel.cs:393-408` |
| Client.UI-008 | Medium | Resolved | Performance & resource management | `ViewModels/MainWindowViewModel.cs:18`, `ViewModels/MainWindowViewModel.cs:125-148`, `App.axaml.cs:18-32` |
| Configuration-002 | Medium | Resolved | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Migrations/20260417215224_StoredProcedures.cs:325` |
| Configuration-003 | Medium | Resolved | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/Validation/DraftValidator.cs:73` |
| Configuration-006 | Medium | Resolved | Error handling & resilience | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/LocalCache/ResilientConfigReader.cs:79` |
| Configuration-009 | Medium | Resolved | Security | `src/Core/ZB.MOM.WW.OtOpcUa.Configuration/DesignTimeDbContextFactory.cs:14` |
| Core-003 | Medium | Resolved | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrie.cs:80-98` |
| Core-005 | Medium | Resolved | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core/Authorization/PermissionTrieCache.cs:59-70` |
| Core-006 | Medium | Resolved | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core/OpcUa/GenericDriverNodeManager.cs:42-64` |
| Core-007 | Medium | Resolved | Error handling & resilience | `src/Core/ZB.MOM.WW.OtOpcUa.Core/Resilience/AlarmSurfaceInvoker.cs:75-83` |
| Core.Abstractions-001 | Medium | Resolved | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/PollGroupEngine.cs:112` |
| Core.Abstractions-002 | Medium | Resolved | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/PollGroupEngine.cs:105-109` |
| Core.Abstractions-003 | Medium | Resolved | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core.Abstractions/PollGroupEngine.cs:64,121-130` |
| Core.AlarmHistorian-003 | Medium | Resolved | OtOpcUa conventions | `src/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian/SqliteStoreAndForwardSink.cs:107-127,218-243,246-253` |
| Core.AlarmHistorian-005 | Medium | Resolved | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian/SqliteStoreAndForwardSink.cs:66-71,141-143,199,386-388` |
| Core.AlarmHistorian-007 | Medium | Resolved | Error handling & resilience | `src/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian/SqliteStoreAndForwardSink.cs:172-174` |
| Core.AlarmHistorian-009 | Medium | Resolved | Design-document adherence | `src/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian/SqliteStoreAndForwardSink.cs:317-347` |
| Core.AlarmHistorian-010 | Medium | Resolved | Testing coverage | `tests/Core/ZB.MOM.WW.OtOpcUa.Core.AlarmHistorian.Tests/SqliteStoreAndForwardSinkTests.cs` |
| Core.ScriptedAlarms-002 | Medium | Resolved | Correctness & logic bugs | `ScriptedAlarmEngine.cs:162`, `ScriptedAlarmEngine.cs:90` |
| Core.ScriptedAlarms-004 | Medium | Resolved | Concurrency & thread safety | `ScriptedAlarmEngine.cs:138-143`, `ScriptedAlarmEngine.cs:227-234` |
| Core.ScriptedAlarms-005 | Medium | Resolved | Concurrency & thread safety | `ScriptedAlarmEngine.cs:365-369`, `ScriptedAlarmEngine.cs:416-424` |
| Core.ScriptedAlarms-007 | Medium | Resolved | Error handling & resilience | `ScriptedAlarmEngine.cs:216`, `ScriptedAlarmEngine.cs:251`, `ScriptedAlarmEngine.cs:154`, `ScriptedAlarmEngine.cs:387` |
| Core.ScriptedAlarms-012 | Medium | Resolved | Testing coverage | `tests/Core/ZB.MOM.WW.OtOpcUa.Core.ScriptedAlarms.Tests/ScriptedAlarmEngineTests.cs` |
| Core.Scripting-003 | Medium | Resolved | Security | `TimedScriptEvaluator.cs:9`, `ScriptSandbox.cs:30` |
| Core.Scripting-004 | Medium | Resolved | Correctness & logic bugs | `DependencyExtractor.cs:73` |
| Core.Scripting-007 | Medium | Resolved | Error handling & resilience | `TimedScriptEvaluator.cs:60` |
| Core.Scripting-010 | Medium | Resolved | Testing coverage | `tests/Core/ZB.MOM.WW.OtOpcUa.Core.Scripting.Tests/ScriptSandboxTests.cs:54` |
| Core.VirtualTags-002 | Medium | Resolved | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags/VirtualTagEngine.cs:237` |
| Core.VirtualTags-003 | Medium | Resolved | Correctness & logic bugs | `src/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags/VirtualTagEngine.cs:117-120` |
| Core.VirtualTags-005 | Medium | Resolved | Concurrency & thread safety | `src/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags/VirtualTagSource.cs:50-64` |
| Core.VirtualTags-008 | Medium | Resolved | Performance & resource management | `src/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags/DependencyGraph.cs:81-115` |
| Core.VirtualTags-012 | Medium | Resolved | Testing coverage | `tests/Core/ZB.MOM.WW.OtOpcUa.Core.VirtualTags.Tests/` |
| Driver.AbCip-004 | Medium | Resolved | Correctness & logic bugs | `AbCipDataType.cs:51-58`, `LibplctagTagRuntime.cs:47-49,53` |
| Driver.AbCip-005 | Medium | Resolved | Correctness & logic bugs | `AbCipDriver.cs:124-141` |
| Driver.AbCip-006 | Medium | Resolved | OtOpcUa conventions | `PlcTagHandle.cs:28-59`, `AbCipDriver.cs:806-807,832-833`, `LibplctagTagRuntime.cs:117` |
| Driver.AbCip-009 | Medium | Resolved | Concurrency & thread safety | `AbCipDriver.cs:621-648`, `AbCipDriver.cs:591-614` |
| Driver.AbCip-010 | Medium | Resolved | Error handling & resilience | `AbCipDriver.cs:621-648`, `AbCipDriver.cs:346-391` |
| Driver.AbCip-014 | Medium | Resolved | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Tests/AbCipStatusMapperTests.cs:28-40` |
| Driver.AbCip.Cli-001 | Medium | Resolved | Error handling & resilience | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli/Commands/WriteCommand.cs:70-85` |
| Driver.AbCip.Cli-002 | Medium | Resolved | Correctness & logic bugs | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.AbCip.Cli/Commands/ProbeCommand.cs:21-23`; `Commands/ReadCommand.cs:24-25`; `Commands/SubscribeCommand.cs:20-22` |
| Driver.AbLegacy-002 | Medium | Resolved | Correctness & logic bugs | `AbLegacyDriver.cs:368` |
| Driver.AbLegacy-003 | Medium | Resolved | Correctness & logic bugs | `AbLegacyAddress.cs:62-95` |
| Driver.AbLegacy-004 | Medium | Resolved | Correctness & logic bugs | `LibplctagLegacyTagRuntime.cs:36-37` |
| Driver.AbLegacy-007 | Medium | Resolved | Concurrency & thread safety | `AbLegacyDriver.cs:411-438`, `AbLegacyDriver.cs:386-409` |
| Driver.AbLegacy-008 | Medium | Resolved | Concurrency & thread safety | `AbLegacyDriver.cs:21`, `AbLegacyDriver.cs:138-146`, `AbLegacyDriver.cs:216-229` |
| Driver.AbLegacy-009 | Medium | Resolved | Error handling & resilience | `AbLegacyDriver.cs:41-74` |
| Driver.AbLegacy-010 | Medium | Resolved | Error handling & resilience | `AbLegacyStatusMapper.cs:26-56` |
| Driver.AbLegacy-012 | Medium | Resolved | Design-document adherence | `PlcFamilies/AbLegacyPlcFamilyProfile.cs:7-54`, `AbLegacyDriver.cs:48-52` |
| Driver.AbLegacy.Cli-001 | Medium | Resolved | Error handling & resilience | `Commands/WriteCommand.cs:46`, `Commands/WriteCommand.cs:62-72` |
| Driver.Cli.Common-002 | Medium | Resolved | Correctness & logic bugs | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common/SnapshotFormatter.cs:101-122` |
| Driver.Cli.Common-003 | Medium | Resolved | Concurrency & thread safety | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common/DriverCommandBase.cs:51-59` |
| Driver.Cli.Common-005 | Medium | Resolved | Testing coverage | `tests/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Cli.Common.Tests/SnapshotFormatterTests.cs:27-37` |
| Driver.FOCAS-003 | Medium | Resolved | Correctness & logic bugs | `FocasDriver.cs:71-79` |
| Driver.FOCAS-004 | Medium | Resolved | OtOpcUa conventions | `FocasDriver.cs:374-379`, `WireFocasClient.cs:48-50` |
| Driver.FOCAS-005 | Medium | Resolved | Concurrency & thread safety | `FocasDriver.cs:28`, `FocasDriver.cs:206-215`, `FocasDriver.cs:261`, `FocasDriver.cs:274` |
| Driver.FOCAS-006 | Medium | Resolved | Error handling & resilience | `FocasDriver.cs:859-874`, `WireFocasClient.cs:22-31` |
| Driver.FOCAS-012 | Medium | Resolved | Testing coverage | `FocasDriverFactoryExtensions.cs`, `FocasDriver.cs:495-629` (`FixedTreeLoopAsync`) |
| Driver.Galaxy-003 | Medium | Resolved | Correctness & logic bugs | `Runtime/StatusCodeMap.cs:86` |
| Driver.Galaxy-004 | Medium | Resolved | Correctness & logic bugs | `GalaxyDriver.cs:901` |
| Driver.Galaxy-006 | Medium | Resolved | Concurrency & thread safety | `GalaxyDriver.cs:848-861` |
| Driver.Galaxy-007 | Medium | Resolved | Concurrency & thread safety | `GalaxyDriver.cs:937-968` |
| Driver.Galaxy-009 | Medium | Resolved | Error handling & resilience | `GalaxyDriver.cs:354-371` |
| Driver.Galaxy-011 | Medium | Resolved | Performance & resource management | `GalaxyDriver.cs:411` |
| Driver.Galaxy-014 | Medium | Resolved | Testing coverage | `src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Galaxy` (module-wide) |
| Driver.Historian.Wonderware-002 | Medium | Resolved | Correctness and logic bugs | `Ipc/HistorianFrameHandler.cs:162`, `:181` |
| Driver.Historian.Wonderware-003 | Medium | Resolved | Correctness and logic bugs | `Backend/HistorianDataSource.cs:320-323`, `:457-460` |
| Driver.Historian.Wonderware-006 | Medium | Resolved | Error handling and resilience | `Ipc/PipeServer.cs:120-128` |
| Driver.Historian.Wonderware-009 | Medium | Resolved | Performance and resource management | `Backend/HistorianDataSource.cs:382-395`, `Ipc/Contracts.cs:85-99` |
| Driver.Historian.Wonderware.Client-002 | Medium | Resolved | Correctness & logic bugs | `WonderwareHistorianClient.cs:154-199`, `IAlarmHistorianSink.cs:66-74` |
| Driver.Historian.Wonderware.Client-005 | Medium | Resolved | Error handling & resilience | `Ipc/FrameReader.cs:31-32` |
| Driver.Historian.Wonderware.Client-007 | Medium | Resolved | Security | `WonderwareHistorianClient.cs:276` |
| Driver.Historian.Wonderware.Client-009 | Medium | Resolved | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Historian.Wonderware.Client.Tests/WonderwareHistorianClientTests.cs` |
| Driver.Modbus-002 | Medium | Resolved | Correctness & logic bugs | `ModbusDriver.cs:127-186` |
| Driver.Modbus-004 | Medium | Resolved | Performance & resource management | `ModbusDriver.cs:1468-1473` |
| Driver.Modbus-005 | Medium | Resolved | Correctness & logic bugs | `ModbusDriver.cs:777-798,323-330` |
| Driver.Modbus-006 | Medium | Resolved | Error handling & resilience | `ModbusDriver.cs:514-524,532-550` |
| Driver.Modbus.Addressing-002 | Medium | Resolved | Correctness & logic bugs | `ModbusAddressParser.cs:86-94` |
| Driver.Modbus.Addressing-003 | Medium | Resolved | Correctness & logic bugs | `ModbusAddressParser.cs:405-406`, `ModbusAddressParser.cs:128` |
| Driver.Modbus.Addressing-004 | Medium | Resolved | Correctness & logic bugs | `ModbusAddressParser.cs:182-194` |
| Driver.Modbus.Addressing-005 | Medium | Resolved | Error handling & resilience | `ModbusAddressParser.cs:200-213` |
| Driver.Modbus.Addressing-008 | Medium | Resolved | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Addressing.Tests/` |
| Driver.Modbus.Cli-001 | Medium | Resolved | Correctness & logic bugs | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli/Commands/SubscribeCommand.cs:43-51` |
| Driver.Modbus.Cli-002 | Medium | Resolved | Correctness & logic bugs | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.Modbus.Cli/Commands/WriteCommand.cs:54-89` |
| Driver.OpcUaClient-006 | Medium | Resolved | Concurrency & thread safety | `OpcUaClientDriver.cs:1330-1359` |
| Driver.OpcUaClient-007 | Medium | Resolved | Concurrency & thread safety | `OpcUaClientDriver.cs:1374`, `:1376-1383`, `:508` |
| Driver.OpcUaClient-008 | Medium | Resolved | Error handling & resilience | `OpcUaClientDriver.cs:1092-1099` |
| Driver.OpcUaClient-009 | Medium | Resolved | Error handling & resilience | `OpcUaClientDriver.cs:560-564` |
| Driver.OpcUaClient-010 | Medium | Resolved | Correctness & logic bugs | `OpcUaClientDriver.cs:823-824` |
| Driver.OpcUaClient-012 | Medium | Resolved | Security | `OpcUaClientDriver.cs:210-217` |
| Driver.OpcUaClient-013 | Medium | Resolved | Performance & resource management | `OpcUaClientDriver.cs:436-437` |
| Driver.OpcUaClient-015 | Medium | Resolved | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.Tests/*`, `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.OpcUaClient.IntegrationTests/OpcUaClientSmokeTests.cs` |
| Driver.S7-002 | Medium | Resolved | Correctness & logic bugs | `S7Driver.cs:350` |
| Driver.S7-004 | Medium | Resolved | OtOpcUa conventions | `S7Driver.cs` (whole file) |
| Driver.S7-008 | Medium | Resolved | Error handling & resilience | `S7Driver.cs:286` |
| Driver.S7-012 | Medium | Resolved | Design-document adherence | `S7DriverOptions.cs:59`, `S7Driver.cs:457` |
| Driver.S7-014 | Medium | Resolved | Testing coverage | `tests/Drivers/ZB.MOM.WW.OtOpcUa.Driver.S7.Tests/` |
| Driver.S7.Cli-001 | Medium | Resolved | Error handling & resilience | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.S7.Cli/Commands/WriteCommand.cs:65-80` |
| Driver.S7.Cli-002 | Medium | Resolved | Design-document adherence | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.S7.Cli/Commands/ReadCommand.cs:22-29`, `Commands/WriteCommand.cs:21-33`, `Commands/SubscribeCommand.cs:18-21`; `docs/Driver.S7.Cli.md:70-73,80-81` |
| Driver.S7.Cli-003 | Medium | Resolved | Error handling & resilience | `src/Drivers/Cli/ZB.MOM.WW.OtOpcUa.Driver.S7.Cli/Commands/ProbeCommand.cs:38-50` |
| Driver.TwinCAT-003 | Medium | Resolved | Correctness & logic bugs | `AdsTwinCATClient.cs:264-281`, `283-300` |
| Driver.TwinCAT-005 | Medium | Resolved | OtOpcUa conventions | `TwinCATDriver.cs` (whole file), `AdsTwinCATClient.cs` (whole file) |
| Driver.TwinCAT-009 | Medium | Resolved | Concurrency & thread safety | `TwinCATDriver.cs:80-99`, `41-72`, `366-388` |
| Driver.TwinCAT-010 | Medium | Resolved | Error handling & resilience | `AdsTwinCATClient.cs:178-195` |
| Driver.TwinCAT-011 | Medium | Resolved | Error handling & resilience | `TwinCATStatusMapper.cs:29-42` |
| Driver.TwinCAT-012 | Medium | Resolved | Performance & resource management | `TwinCATDriver.cs:102`, `AdsTwinCATClient.cs:178-195` |
| Server-003 | Medium | Resolved | Correctness & logic bugs | `src/Server/ZB.MOM.WW.OtOpcUa.Server/Phase7/RingBufferHistoryWriter.cs:96-119` |
| Server-005 | Medium | Resolved | Concurrency & thread safety | `src/Server/ZB.MOM.WW.OtOpcUa.Server/Alarms/AlarmConditionService.cs:166`, `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/DriverNodeManager.cs:303-311` |
| Server-007 | Medium | Resolved | Error handling & resilience | `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs:179-183` |
| Server-010 | Medium | Resolved | Security | `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaServerOptions.cs:59`, `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs:284-291` |
| Server-011 | Medium | Resolved | Security | `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs:322-346` |
| Server-013 | Medium | Resolved | Design-document adherence | `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaServerOptions.cs:9-19`, `src/Server/ZB.MOM.WW.OtOpcUa.Server/OpcUa/OpcUaApplicationHost.cs:296-346`, `src/Server/ZB.MOM.WW.OtOpcUa.Server/Program.cs:89` |