fix(inbound): authorize+secure Database helper, async/deadline-bound DB, wait-timeout-bound WaitForAttribute
Resolves InboundAPI-026/027/028/029 (+ newly-surfaced -030). - 026: authorize the scoped Database helper in the design doc; SQL-injection protection is parameter binding (values never concatenated); allow writes via ExecuteAsync; drop the false 'read-only' claim. Named connections only. - 027: async ADO.NET end-to-end (no .GetAwaiter().GetResult()); honour the method deadline token on ExecuteScalarAsync/ExecuteReaderAsync/ExecuteNonQueryAsync + a CommandTimeout backstop derived from the method timeout. - 028: negative-path tests (null-gateway, deadline cancellation, parameterization) + e2e Database + WaitForAttribute cases through the real endpoint. - 029: WaitForAttribute is bounded by its WAIT timeout (per-wait CTS + client-abort + explicit token), NOT the method deadline (spec §6) — a long wait may outlive the method timeout; WithRequestAborted threads the raw client-abort token separately. - 030: Central UI compile-surface mirrors (InboundScriptHost / SandboxInboundScriptHost) gained the Database member (drifted since the runtime helper was added) so the authorized async API type-checks at the design-time gate.
This commit is contained in:
@@ -189,7 +189,7 @@ Inbound API scripts **cannot** call shared scripts directly — shared scripts a
|
||||
- `Route.To("instanceUniqueCode").GetAttributes("attr1", "attr2", ...)` — Read multiple attribute values in a **single call**, returned as a dictionary of name-value pairs.
|
||||
- `Route.To("instanceUniqueCode").SetAttribute("attributeName", value)` — Write a single attribute value on a specific instance at any site.
|
||||
- `Route.To("instanceUniqueCode").SetAttributes(dictionary)` — Write multiple attribute values in a **single call**, accepting a dictionary of name-value pairs.
|
||||
- `Route.To("instanceUniqueCode").WaitForAttribute("attributeName", targetValue, timeout)` — Wait, event-driven, until an attribute on a specific instance at any site reaches `targetValue` (value-equality only across the wire), bounded by `timeout`. Returns `true` if matched within the timeout, `false` if it timed out. The cluster call is bounded by the wait timeout rather than the generic integration timeout.
|
||||
- `Route.To("instanceUniqueCode").WaitForAttribute("attributeName", targetValue, timeout)` — Wait, event-driven, until an attribute on a specific instance at any site reaches `targetValue` (value-equality only across the wire), bounded by `timeout`. Returns `true` if matched within the timeout, `false` if it timed out. **The wait is bounded by its own `timeout`, not the generic method-level timeout** — this is the one routed call that may legitimately outlive the method timeout (the site enforces `timeout` and returns `false` when it elapses). A client disconnect still cancels the wait. This is the deliberate exception to the rule below that routed calls inherit the method-level timeout (see "Routing Behavior"): a long event-driven wait is the explicit reason `timeout` governs here.
|
||||
|
||||
#### Input/Output
|
||||
- **Input parameters** are available as defined in the method definition.
|
||||
@@ -199,20 +199,38 @@ Inbound API scripts **cannot** call shared scripts directly — shared scripts a
|
||||
- `Parameters["key"]` — Raw dictionary access.
|
||||
- `Parameters.Get<T>("key")` — Typed access (same API as site runtime scripts). See Site Runtime component for full type support.
|
||||
|
||||
> **No direct database access.** Inbound API scripts are not given a raw database
|
||||
> client. Handing a script a raw `SqlConnection` is in direct tension with the
|
||||
> ScadaBridge script trust model (scripts are forbidden `System.IO`, `Process`,
|
||||
> `Threading`, `Reflection`, and raw network access). The `ForbiddenApiChecker`
|
||||
> statically enforces this by delegating to the shared `ScriptAnalysis`
|
||||
> `ScriptTrustValidator` (component #25), which is the single authoritative
|
||||
> source of truth for the forbidden-API policy. The unified policy permits
|
||||
> `System.Diagnostics.Stopwatch`/`Debug` while retaining the `Process`-only ban,
|
||||
> and adds reflection-gateway member and `dynamic`/`Activator` hardening.
|
||||
> This is defence-in-depth static enforcement, not a true runtime sandbox. Scripts
|
||||
> interact with the system only through the curated `Route` and `Parameters`
|
||||
> surfaces above. If a method needs data from the configuration or machine-data
|
||||
> databases, that access belongs behind a dedicated, scoped helper — not a
|
||||
> general-purpose connection — and would be added here as an explicit design change.
|
||||
#### Database Access
|
||||
|
||||
Inbound API scripts may read from and write to the configuration / machine-data
|
||||
databases through the **curated, scoped `Database` helper** — `InboundDatabaseHelper`,
|
||||
exposed as `InboundScriptContext.Database`. This is the "dedicated, scoped helper added
|
||||
as an explicit design change" that the script trust model requires: scripts are **never**
|
||||
handed a raw `SqlConnection`, and never reference `System.Data` (the `ForbiddenApiChecker`,
|
||||
delegating to the shared `ScriptAnalysis` `ScriptTrustValidator` (#25), still statically
|
||||
bans `System.IO`, `Process`, `Threading`, `Reflection`, and raw network access — that is
|
||||
defence-in-depth static enforcement, not a true runtime sandbox).
|
||||
|
||||
The helper API (all asynchronous — scripts `await` them; bounded by the method timeout):
|
||||
|
||||
- `await Database.QuerySingleAsync<T>("connectionName", sql, parameters)` — first column of the first row as `T` (default if no rows).
|
||||
- `await Database.QueryAsync("connectionName", sql, parameters)` — all rows as case-insensitive column→value dictionaries.
|
||||
- `await Database.ExecuteAsync("connectionName", sql, parameters)` — run a write (INSERT/UPDATE/DELETE/DDL); returns rows affected. **Writes are permitted** — the move-in integration records results, not just reads them.
|
||||
|
||||
Containment rules (enforced, not advisory):
|
||||
|
||||
- **Named connections only.** `connectionName` selects one of the connections configured
|
||||
on the central database gateway (`IDatabaseGateway`); a script cannot supply an arbitrary
|
||||
connection string or reach a database the gateway is not configured for.
|
||||
- **SQL-injection protection.** Statement text is authored by the (design-time) method
|
||||
script, but every request-derived **value** is passed via `parameters` and bound as a
|
||||
named `@`-prefixed SQL parameter — never string-concatenated into the command text.
|
||||
Request input therefore reaches the database only through parameter binding.
|
||||
- **Deadline-bound.** Calls use the async ADO.NET path end-to-end (no pool-thread blocking)
|
||||
and honour the method deadline token, with a `CommandTimeout` backstop derived from the
|
||||
method timeout, so a slow query is bounded by the method timeout.
|
||||
|
||||
For everything else, scripts interact with the system through the curated `Route` and
|
||||
`Parameters` surfaces above.
|
||||
|
||||
### Routing Behavior
|
||||
- The `Route.To()` helper resolves the instance's site assignment from the configuration database and routes the request to the correct site cluster via the Communication Layer.
|
||||
|
||||
Reference in New Issue
Block a user