refactor: rename ScadaLink → ZB.MOM.WW.ScadaBridge (code + projects + namespaces)
Solution + 23 src projects + 26 test projects renamed; folders, csproj, namespaces, and ScadaLinkDbContext/ScadaBridgeDbContext class updated. ActorSystem "scadalink" → "scadabridge", Akka seed-node URLs migrated. SQL roles/logins, LDAP domains, CLI command name, and CLI config dir (~/.scadalink → ~/.scadabridge) also renamed. Build green; 5 Host.Tests fail awaiting SQL login rename in next commit. Pre-existing StaleTagMonitor timing flakes unchanged. Rename script committed at tools/rename-to-scadabridge.sh.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Module | `src/ScadaLink.InboundAPI` |
|
||||
| Module | `src/ZB.MOM.WW.ScadaBridge.InboundAPI` |
|
||||
| Design doc | `docs/requirements/Component-InboundAPI.md` |
|
||||
| Status | Reviewed |
|
||||
| Last reviewed | 2026-05-28 |
|
||||
@@ -33,7 +33,7 @@ are High severity and should be addressed before production use.
|
||||
#### Re-review 2026-05-17 (commit `39d737e`)
|
||||
|
||||
All 13 findings from the initial review remain `Resolved`; the module source under
|
||||
`src/ScadaLink.InboundAPI` is unchanged since the last InboundAPI fix commit
|
||||
`src/ZB.MOM.WW.ScadaBridge.InboundAPI` is unchanged since the last InboundAPI fix commit
|
||||
(`8dd7412`), which precedes `39d737e`. This re-review re-walked all 10 checklist
|
||||
categories against the resolved code and surfaced **4 new findings** — none touching
|
||||
the previously-fixed concurrency/trust-model code, but all in areas the first pass
|
||||
@@ -68,16 +68,16 @@ statement that the timeout covers routed calls (InboundAPI-016); and (4) `RouteH
|
||||
|
||||
All 17 prior findings remain `Resolved`. The module has grown materially since the
|
||||
last pass — a new `AuditWriteMiddleware` (Audit Log #23 M4 Bundle D) now lives under
|
||||
`src/ScadaLink.InboundAPI/Middleware/`, the `ApiKeyValidator` was rewired to hash the
|
||||
`src/ZB.MOM.WW.ScadaBridge.InboundAPI/Middleware/`, the `ApiKeyValidator` was rewired to hash the
|
||||
candidate with `IApiKeyHasher` (ConfigurationDatabase-012), and an `IInstanceRouter`
|
||||
seam was introduced. This re-review re-walked all 10 checklist categories against
|
||||
`1eb6e97` and surfaced **8 new findings** concentrated on the new audit middleware
|
||||
and a stranded follow-up from InboundAPI-008:
|
||||
|
||||
1. The InboundAPI-008 resolution explicitly deferred registering an `IActiveNodeGate`
|
||||
implementation in `ScadaLink.Host` as a "follow-up outside this module's scope" —
|
||||
implementation in `ZB.MOM.WW.ScadaBridge.Host` as a "follow-up outside this module's scope" —
|
||||
that follow-up is still unfulfilled (no production registration anywhere in
|
||||
`src/ScadaLink.Host/`), so the design-mandated standby-node gating is silently
|
||||
`src/ZB.MOM.WW.ScadaBridge.Host/`), so the design-mandated standby-node gating is silently
|
||||
disabled in production today (`InboundAPI-022`, High).
|
||||
2. `AuditWriteMiddleware` is wired in `Program.cs` against `/api/*` rather than the
|
||||
specific `POST /api/{methodName}` route, so GETs against `/api/audit/query` and
|
||||
@@ -133,7 +133,7 @@ configuration database, but the invariant is undocumented.)
|
||||
| Severity | High |
|
||||
| Category | Concurrency & thread safety |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:17`, `:32`, `:40`, `:89`, `:123-128` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:17`, `:32`, `:40`, `:89`, `:123-128` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -169,7 +169,7 @@ via `GetOrAdd` so concurrent first-callers share one handler. Regression tests
|
||||
| Severity | Medium — re-triaged: already fixed by the InboundAPI-001 fix; verified and closed |
|
||||
| Category | Concurrency & thread safety |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:152-161` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:152-161` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -209,7 +209,7 @@ handler that `GetOrAdd` keeps. Regression test
|
||||
| Severity | High |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.ConfigurationDatabase/Repositories/InboundApiRepository.cs:22-23`, consumed by `src/ScadaLink.InboundAPI/ApiKeyValidator.cs:33` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.ConfigurationDatabase/Repositories/InboundApiRepository.cs:22-23`, consumed by `src/ZB.MOM.WW.ScadaBridge.InboundAPI/ApiKeyValidator.cs:33` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -251,7 +251,7 @@ longer depends on it.
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:117-141` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:117-141` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -293,7 +293,7 @@ added.
|
||||
| Severity | High |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:56-93` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:56-93` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -337,7 +337,7 @@ Regression tests `CompileAndRegister_ForbiddenApi_RejectsScript` (theory),
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/EndpointExtensions.cs:54-62` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs:54-62` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -365,7 +365,7 @@ It rejects requests whose declared `Content-Length` exceeds `InboundApiOptions.
|
||||
MaxRequestBodyBytes` (default 1 MiB) with HTTP 413 *before* the handler buffers the
|
||||
body into a `JsonDocument`, and also lowers the per-request `IHttpMaxRequestBodySizeFeature`
|
||||
cap so a chunked/unknown-length stream is cut off by Kestrel while being read. The
|
||||
limit is configurable via the bound `ScadaLink:InboundApi` options section. Regression
|
||||
limit is configurable via the bound `ScadaBridge:InboundApi` options section. Regression
|
||||
tests `OversizedBody_ShortCircuitsWith413_AndDoesNotRunHandler`, `BodyAtLimit_RunsHandler`,
|
||||
and `FilterCapsMaxRequestBodySizeFeature` added.
|
||||
|
||||
@@ -376,7 +376,7 @@ and `FilterCapsMaxRequestBodySizeFeature` added.
|
||||
| Severity | Medium |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:188-203` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:188-203` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -402,7 +402,7 @@ Resolved 2026-05-16 (commit `<pending>`). The drift was confirmed real:
|
||||
so a method script following the documented `Database.Connection("name")` API
|
||||
would fail to compile. Resolution direction: the design doc is stale, not the
|
||||
code. Implementing `Database.Connection()` would hand inbound API scripts a
|
||||
*raw* MS SQL client, in direct tension with the ScadaLink script trust model
|
||||
*raw* MS SQL client, in direct tension with the ScadaBridge script trust model
|
||||
(scripts are forbidden `System.IO`, raw network, etc.; `ForbiddenApiChecker`
|
||||
statically enforces this). Rather than carve a hole in the trust model, the
|
||||
"Database Access" section was removed from `docs/requirements/Component-InboundAPI.md`
|
||||
@@ -418,7 +418,7 @@ explicit design change. Code and doc now agree; no code or test change required.
|
||||
| Severity | Medium |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/EndpointExtensions.cs:19-23`, `src/ScadaLink.Host/Program.cs:149` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs:19-23`, `src/ZB.MOM.WW.ScadaBridge.Host/Program.cs:149` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -447,7 +447,7 @@ auth/script work, so Traefik/clients only reach the live node — consistent wit
|
||||
registered (non-clustered host / tests) the endpoint defaults to "allow", preserving
|
||||
prior behaviour. Regression tests `StandbyNode_ShortCircuitsWith503_AndDoesNotRunHandler`,
|
||||
`ActiveNode_PassesGate_RunsHandler`, and `NoGateRegistered_PassesGate_RunsHandler`
|
||||
added. **Follow-up (outside this module's scope):** `ScadaLink.Host` should register
|
||||
added. **Follow-up (outside this module's scope):** `ZB.MOM.WW.ScadaBridge.Host` should register
|
||||
an `IActiveNodeGate` implementation backed by `ActiveNodeHealthCheck` /
|
||||
`Cluster.State.Leader` in the central-role branch of `Program.cs` so the gate is
|
||||
actually enforced in production; until then the endpoint defaults to "allow".
|
||||
@@ -459,7 +459,7 @@ actually enforced in production; until then the endpoint defaults to "allow".
|
||||
| Severity | Low |
|
||||
| Category | Performance & resource management |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:123-128` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:123-128` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -496,7 +496,7 @@ re-evaluated. Regression tests `FailedCompilation_IsNotRetriedOnEveryRequest`
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/ParameterValidator.cs:64-90`, `:112-118` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/ParameterValidator.cs:64-90`, `:112-118` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -541,7 +541,7 @@ follow-up. Regression tests `UnexpectedBodyField_ReturnsInvalid` and
|
||||
| Severity | Low |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/ApiKeyValidator.cs:39-52` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/ApiKeyValidator.cs:39-52` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -580,14 +580,14 @@ indistinguishable contract.
|
||||
| Severity | Low |
|
||||
| Category | Code organization & conventions |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/ParameterValidator.cs:128-133` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/ParameterValidator.cs:128-133` |
|
||||
|
||||
**Description**
|
||||
|
||||
`ParameterDefinition` is a persistence-/contract-shaped POCO: it is the deserialized
|
||||
form of `ApiMethod.ParameterDefinitions` (a column in the configuration database) and
|
||||
describes the public API contract. CLAUDE.md's code-organization rules place
|
||||
persistence-ignorant entity/contract types in `ScadaLink.Commons`. Defining it inside
|
||||
persistence-ignorant entity/contract types in `ZB.MOM.WW.ScadaBridge.Commons`. Defining it inside
|
||||
the InboundAPI project means any other component that needs to read or produce method
|
||||
parameter definitions (e.g. Central UI's method editor, CLI, Management Service)
|
||||
cannot share the type and will duplicate it.
|
||||
@@ -595,7 +595,7 @@ cannot share the type and will duplicate it.
|
||||
**Recommendation**
|
||||
|
||||
Move `ParameterDefinition` (and a matching return-definition type, if added) to
|
||||
`ScadaLink.Commons` under the InboundApi entity/types namespace so it is shared by all
|
||||
`ZB.MOM.WW.ScadaBridge.Commons` under the InboundApi entity/types namespace so it is shared by all
|
||||
components that work with method definitions.
|
||||
|
||||
**Resolution**
|
||||
@@ -604,22 +604,22 @@ Resolved 2026-05-16 (commit `<pending>`): root cause confirmed against the sourc
|
||||
`ParameterDefinition` was a persistence-ignorant, API-contract-shaped POCO (the
|
||||
deserialized form of the `ApiMethod.ParameterDefinitions` configuration-database
|
||||
column) declared inside the component project, contrary to CLAUDE.md's
|
||||
code-organization rule that such shared contract types live in `ScadaLink.Commons`.
|
||||
The type was moved to `src/ScadaLink.Commons/Types/InboundApi/ParameterDefinition.cs`
|
||||
(namespace `ScadaLink.Commons.Types.InboundApi`) — placed under `Types/` with an
|
||||
code-organization rule that such shared contract types live in `ZB.MOM.WW.ScadaBridge.Commons`.
|
||||
The type was moved to `src/ZB.MOM.WW.ScadaBridge.Commons/Types/InboundApi/ParameterDefinition.cs`
|
||||
(namespace `ZB.MOM.WW.ScadaBridge.Commons.Types.InboundApi`) — placed under `Types/` with an
|
||||
`InboundApi` domain subfolder, matching the existing `Types/Scripts/` precedent, since
|
||||
the column itself is the persisted form and this type is its deserialized contract
|
||||
shape (not an EF-mapped entity). It remains a pure POCO with no EF attributes and no
|
||||
behaviour. `ParameterValidator` now imports the moved type via a `using
|
||||
ScadaLink.Commons.Types.InboundApi;` directive; a tree-wide search confirmed
|
||||
ZB.MOM.WW.ScadaBridge.Commons.Types.InboundApi;` directive; a tree-wide search confirmed
|
||||
`ParameterValidator.cs` was the type's only declaration and only direct consumer (all
|
||||
other `ParameterDefinition*` matches are the unrelated `ParameterDefinitions` string
|
||||
property). No return-definition type exists in the codebase — only a `ReturnDefinition`
|
||||
string column — so none was invented. No behavioural change, so no new runtime
|
||||
regression test: this is a compile-level type move, and the existing 52
|
||||
`ScadaLink.InboundAPI.Tests` (including the `ParameterValidator` suite) act as the
|
||||
regression guard. `dotnet test` for `ScadaLink.InboundAPI.Tests` (52 passed) and
|
||||
`ScadaLink.Commons.Tests` (226 passed) are green; `dotnet build ScadaLink.slnx`
|
||||
`ZB.MOM.WW.ScadaBridge.InboundAPI.Tests` (including the `ParameterValidator` suite) act as the
|
||||
regression guard. `dotnet test` for `ZB.MOM.WW.ScadaBridge.InboundAPI.Tests` (52 passed) and
|
||||
`ZB.MOM.WW.ScadaBridge.Commons.Tests` (226 passed) are green; `dotnet build ZB.MOM.WW.ScadaBridge.slnx`
|
||||
succeeds with 0 warnings / 0 errors.
|
||||
|
||||
### InboundAPI-013 — `ApiKeyValidationResult.NotFound` factory returns HTTP 400, contradicting its name
|
||||
@@ -629,7 +629,7 @@ succeeds with 0 warnings / 0 errors.
|
||||
| Severity | Low |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/ApiKeyValidator.cs:78-79` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/ApiKeyValidator.cs:78-79` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -667,7 +667,7 @@ from "key not approved"), but that doc edit is outside this module's editable sc
|
||||
| Severity | Medium |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:201-205`, `src/ScadaLink.Commons/Entities/InboundApi/ApiMethod.cs:10` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:201-205`, `src/ZB.MOM.WW.ScadaBridge.Commons/Entities/InboundApi/ApiMethod.cs:10` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -725,7 +725,7 @@ is validation-only (no coercion). Regression tests: `ReturnValueValidatorTests`
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/ForbiddenApiChecker.cs:63-119`, `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:109-126` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/ForbiddenApiChecker.cs:63-119`, `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:109-126` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -799,7 +799,7 @@ namespace-deny-list regression guards.
|
||||
| Severity | Medium |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/RouteHelper.cs:59-152`, `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:177`, `:199` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/RouteHelper.cs:59-152`, `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:177`, `:199` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -863,13 +863,13 @@ of running orphaned. Regression tests (in the new `RouteHelperTests`):
|
||||
| Severity | Low |
|
||||
| Category | Testing coverage |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/RouteHelper.cs:1-165`, `tests/ScadaLink.InboundAPI.Tests/` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/RouteHelper.cs:1-165`, `tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/` |
|
||||
|
||||
**Description**
|
||||
|
||||
`RouteHelper`/`RouteTarget` is the entire WP-4 cross-site routing surface — the
|
||||
`Route.To().Call()/GetAttribute(s)/SetAttribute(s)` API that inbound API scripts use
|
||||
to reach instances at any site. It has zero tests: the `ScadaLink.InboundAPI.Tests`
|
||||
to reach instances at any site. It has zero tests: the `ZB.MOM.WW.ScadaBridge.InboundAPI.Tests`
|
||||
project covers `ApiKeyValidator`, `ParameterValidator`, `InboundScriptExecutor`, and
|
||||
`InboundApiEndpointFilter`, but no test file exercises `RouteHelper`. Untested
|
||||
behaviours include site resolution via `IInstanceLocator` (including the
|
||||
@@ -892,7 +892,7 @@ wiring is added.
|
||||
|
||||
**Resolution**
|
||||
|
||||
Resolved 2026-05-17 (commit `<pending>`): confirmed — `ScadaLink.InboundAPI.Tests` had
|
||||
Resolved 2026-05-17 (commit `<pending>`): confirmed — `ZB.MOM.WW.ScadaBridge.InboundAPI.Tests` had
|
||||
no file exercising `RouteHelper`/`RouteTarget`. To make the surface testable without a
|
||||
live actor system, an `IInstanceRouter` seam was introduced in the module (the routing
|
||||
transport `RouteHelper` depends on); the production `CommunicationServiceInstanceRouter`
|
||||
@@ -912,7 +912,7 @@ the InboundAPI-016 deadline-token inheritance behaviour. All 15 pass.
|
||||
| Severity | Medium |
|
||||
| Category | Error handling & resilience |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/Middleware/AuditWriteMiddleware.cs:257` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/Middleware/AuditWriteMiddleware.cs:257` |
|
||||
|
||||
**Resolution (2026-05-28):** kept the fire-and-forget (audit emission must
|
||||
never block or alter the user-facing response per alog.md §13) but added
|
||||
@@ -968,7 +968,7 @@ synchronous throw) to pin the new contract.
|
||||
|--|--|
|
||||
| Severity | Low |
|
||||
| Category | Performance & resource management |
|
||||
| Location | `src/ScadaLink.InboundAPI/Middleware/AuditWriteMiddleware.cs:141` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/Middleware/AuditWriteMiddleware.cs:141` |
|
||||
| Status | Resolved |
|
||||
|
||||
**Resolution (2026-05-28):** Added a `RequestHasBody(HttpRequest)` guard that returns `true` only when `ContentLength > 0` or the method is POST / PUT / PATCH (the `HttpMethods.IsPost/Put/Patch` helpers); the `EnableBuffering` + `ReadBufferedRequestBodyAsync` call now sits behind that guard. Bodyless GET / HEAD / DELETE / TRACE / OPTIONS requests (and any explicit `Content-Length: 0`) skip the `FileBufferingReadStream` allocation; body-carrying methods with no Content-Length (chunked POST etc.) still buffer. Regression tests `BodylessMethod_SkipsEnableBuffering_RequestStreamIsNotReplaced` (GET/HEAD/DELETE theory), `BodylessPost_ContentLengthZero_SkipsEnableBuffering`, and `PostWithBody_StillEnablesBuffering_AndCapturesRequestSummary` (anti-regression).
|
||||
@@ -1000,7 +1000,7 @@ bodyless request through the middleware.
|
||||
| Severity | Low |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/EndpointExtensions.cs:70` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs:70` |
|
||||
|
||||
**Resolution (2026-05-28):** swapped the case-sensitive `Contains("json")`
|
||||
substring match for `Contains("json", StringComparison.OrdinalIgnoreCase)` so
|
||||
@@ -1041,7 +1041,7 @@ regression test posting with `application/JSON` and Transfer-Encoding: chunked.
|
||||
| Severity | Medium |
|
||||
| Category | Design-document adherence |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/RouteHelper.cs:141-143`, `:182-183`, `:225-226`; `src/ScadaLink.Commons/Messages/InboundApi/RouteToInstanceRequest.cs:15-21`, `:36-40`, `:55-59` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/RouteHelper.cs:141-143`, `:182-183`, `:225-226`; `src/ZB.MOM.WW.ScadaBridge.Commons/Messages/InboundApi/RouteToInstanceRequest.cs:15-21`, `:36-40`, `:55-59` |
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -1104,9 +1104,9 @@ backlog), they can stamp the parent id without any further plumbing.
|
||||
| Severity | High |
|
||||
| Category | Security |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/IActiveNodeGate.cs`, `src/ScadaLink.InboundAPI/InboundApiEndpointFilter.cs:52-60`; absent from `src/ScadaLink.Host/Program.cs` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/IActiveNodeGate.cs`, `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundApiEndpointFilter.cs:52-60`; absent from `src/ZB.MOM.WW.ScadaBridge.Host/Program.cs` |
|
||||
|
||||
**Resolution** — Added `src/ScadaLink.Host/Health/ActiveNodeGate.cs`, a production `IActiveNodeGate` implementation backed by `AkkaHostedService` that mirrors `ActiveNodeHealthCheck`'s leadership probe (member status `Up` AND `Cluster.State.Leader == SelfAddress`), and registered it as a singleton in the central-role branch of `Program.cs`. A structural regression test (`CentralCompositionRootTests.Central_IActiveNodeGate_IsRegisteredAsActiveNodeGate`) reflects over the built `IServiceProvider` to assert the registration's existence and concrete type — failing on `main` and passing after the fix. The `InboundApiEndpointFilter`'s fall-through-to-allow behaviour is retained as the documented safe default for non-clustered hosts and tests.
|
||||
**Resolution** — Added `src/ZB.MOM.WW.ScadaBridge.Host/Health/ActiveNodeGate.cs`, a production `IActiveNodeGate` implementation backed by `AkkaHostedService` that mirrors `ActiveNodeHealthCheck`'s leadership probe (member status `Up` AND `Cluster.State.Leader == SelfAddress`), and registered it as a singleton in the central-role branch of `Program.cs`. A structural regression test (`CentralCompositionRootTests.Central_IActiveNodeGate_IsRegisteredAsActiveNodeGate`) reflects over the built `IServiceProvider` to assert the registration's existence and concrete type — failing on `main` and passing after the fix. The `InboundApiEndpointFilter`'s fall-through-to-allow behaviour is retained as the documented safe default for non-clustered hosts and tests.
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -1118,14 +1118,14 @@ when `gate is { IsActiveNode: false }` returns HTTP 503. The filter's behaviour
|
||||
when **no implementation is registered** (line 51 comment) is to fall through and
|
||||
serve the request — the resolution paragraph for InboundAPI-008 closes with:
|
||||
|
||||
> "Follow-up (outside this module's scope): `ScadaLink.Host` should register an
|
||||
> "Follow-up (outside this module's scope): `ZB.MOM.WW.ScadaBridge.Host` should register an
|
||||
> `IActiveNodeGate` implementation backed by `ActiveNodeHealthCheck` /
|
||||
> `Cluster.State.Leader` in the central-role branch of `Program.cs` so the gate is
|
||||
> actually enforced in production; until then the endpoint defaults to "allow"."
|
||||
|
||||
A grep of the entire `src/ScadaLink.Host/` tree at `1eb6e97` finds **zero**
|
||||
A grep of the entire `src/ZB.MOM.WW.ScadaBridge.Host/` tree at `1eb6e97` finds **zero**
|
||||
`IActiveNodeGate` registrations: `grep -rn "IActiveNodeGate\|AddSingleton.*ActiveNode"
|
||||
src/ScadaLink.Host/` returns no matches. The follow-up was never carried out. So
|
||||
src/ZB.MOM.WW.ScadaBridge.Host/` returns no matches. The follow-up was never carried out. So
|
||||
in production today the standby central node still serves the inbound API exactly
|
||||
as InboundAPI-008 described — executes method scripts, runs `Route.To()` calls,
|
||||
races the active node, and may operate against stale singleton state. The new
|
||||
@@ -1138,7 +1138,7 @@ The design says the inbound API is "Central cluster only (active node)" and
|
||||
**Recommendation**
|
||||
|
||||
Register an `IActiveNodeGate` implementation in the central-role branch of
|
||||
`ScadaLink.Host/Program.cs`. The natural backing is the existing
|
||||
`ZB.MOM.WW.ScadaBridge.Host/Program.cs`. The natural backing is the existing
|
||||
`ActiveNodeHealthCheck` (already wired for `/health/active`) or a direct read of
|
||||
`Cluster.Get(actorSystem).State.Leader == Cluster.Get(actorSystem).SelfAddress`.
|
||||
Add an integration test in the Host that spins up the central role and asserts
|
||||
@@ -1153,9 +1153,9 @@ realisation of the InboundAPI-008 vulnerability.
|
||||
| Severity | Low |
|
||||
| Category | Testing coverage |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/EndpointExtensions.cs:31-140`, `tests/ScadaLink.InboundAPI.Tests/` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/EndpointExtensions.cs:31-140`, `tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/` |
|
||||
|
||||
**Resolution (2026-05-28):** Added `tests/ScadaLink.InboundAPI.Tests/EndpointExtensionsTests.cs`, a `TestServer`-hosted suite (same pattern as `EndpointContentTypeTests`) that drives the `POST /api/{methodName}` wiring end-to-end. Seven cases pin the composed flow: happy path (200 + script result body), missing API key (401), unknown method (403, indistinguishable from "not approved" per InboundAPI-011), invalid JSON body (400), missing required parameter (400 from `ParameterValidator`), script throws (500 with sanitized error body — the executor's catch-all replaces the raw exception with `"Internal script error"`), and the `HttpContext.Items[AuditWriteMiddleware.AuditActorItemKey]` actor-stash invariant (verified by an inline capture middleware reading the slot after the endpoint runs). All 7 new tests pass; total InboundAPI.Tests now 158 (was 151).
|
||||
**Resolution (2026-05-28):** Added `tests/ZB.MOM.WW.ScadaBridge.InboundAPI.Tests/EndpointExtensionsTests.cs`, a `TestServer`-hosted suite (same pattern as `EndpointContentTypeTests`) that drives the `POST /api/{methodName}` wiring end-to-end. Seven cases pin the composed flow: happy path (200 + script result body), missing API key (401), unknown method (403, indistinguishable from "not approved" per InboundAPI-011), invalid JSON body (400), missing required parameter (400 from `ParameterValidator`), script throws (500 with sanitized error body — the executor's catch-all replaces the raw exception with `"Internal script error"`), and the `HttpContext.Items[AuditWriteMiddleware.AuditActorItemKey]` actor-stash invariant (verified by an inline capture middleware reading the slot after the endpoint runs). All 7 new tests pass; total InboundAPI.Tests now 158 (was 151).
|
||||
|
||||
**Description**
|
||||
|
||||
@@ -1190,7 +1190,7 @@ resolved key name after successful auth, but is absent on auth failures).
|
||||
| Severity | Low |
|
||||
| Category | Performance & resource management |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.InboundAPI/InboundScriptExecutor.cs:30`, `:77`, `:223`, `:233` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.InboundAPI/InboundScriptExecutor.cs:30`, `:77`, `:223`, `:233` |
|
||||
|
||||
**Resolution (2026-05-28):** capped the cache at `KnownBadMethodsCap = 1000`
|
||||
entries via a new `TryRecordBadMethod` helper that short-circuits when the cap
|
||||
@@ -1236,7 +1236,7 @@ immediate change required; this is a watch-list item.
|
||||
| Severity | Medium |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Host/Program.cs:183-185`; consumers: `src/ScadaLink.ManagementService/AuditEndpoints.cs:93-94`; emitter: `src/ScadaLink.InboundAPI/Middleware/AuditWriteMiddleware.cs:175-252` |
|
||||
| Location | `src/ZB.MOM.WW.ScadaBridge.Host/Program.cs:183-185`; consumers: `src/ZB.MOM.WW.ScadaBridge.ManagementService/AuditEndpoints.cs:93-94`; emitter: `src/ZB.MOM.WW.ScadaBridge.InboundAPI/Middleware/AuditWriteMiddleware.cs:175-252` |
|
||||
|
||||
**Resolution (2026-05-28):** Took the defensive path-exclusion in
|
||||
`Program.cs` (Option 1 from the recommendation). The `UseWhen` predicate
|
||||
@@ -1255,7 +1255,7 @@ the excluded prefixes, which would be noisy in code review.
|
||||
`Program.cs` wires the audit middleware as
|
||||
`app.UseWhen(ctx => ctx.Request.Path.StartsWithSegments("/api"), branch => branch.UseAuditWriteMiddleware())`
|
||||
— scoped to the `/api` *prefix*, not to the `POST /api/{methodName}` route.
|
||||
Meanwhile, `ScadaLink.ManagementService/AuditEndpoints.cs` maps
|
||||
Meanwhile, `ZB.MOM.WW.ScadaBridge.ManagementService/AuditEndpoints.cs` maps
|
||||
`MapGet("/api/audit/query", ...)` (line 93) and `MapGet("/api/audit/export", ...)`
|
||||
(line 94). Both routes therefore inherit `AuditWriteMiddleware`, which emits an
|
||||
`AuditEvent { Channel = AuditChannel.ApiInbound, Kind = AuditKind.InboundRequest, ... }`
|
||||
|
||||
Reference in New Issue
Block a user