fix(security): close auth & site-scoping gaps across 8 findings

Resolves the auth-theme batch from the 2026-05-28 baseline review (8 findings
across Security/CentralUI/ManagementService/CLI). The most consequential gaps:
NotificationReport + SiteCallsReport now route through SiteScopeService so a
site-scoped Deployment user cannot see or act on other sites' rows (CUI-028);
QueryAuditLogCommand is no longer "any authenticated user" — gated Admin-only
to match /api/audit/query's strictness (MS-018); RoleMapper preserves the
broader grant when a user is in both an unscoped and scoped Deployment LDAP
group, instead of silently narrowing to the scoped set (Sec-016); and the
dead SiteScopeRequirement/Handler are deleted so SiteScopeService is
unambiguously the sole site-scoping mechanism (Sec-017). Pending findings:
172 → 164.
This commit is contained in:
Joseph Doherty
2026-05-28 03:35:29 -04:00
parent f93b7b99bb
commit e536178323
28 changed files with 814 additions and 196 deletions
+18 -3
View File
@@ -8,7 +8,7 @@
| Last reviewed | 2026-05-28 |
| Reviewer | claude-agent |
| Commit reviewed | `1eb6e97` |
| Open findings | 7 |
| Open findings | 6 |
## Summary
@@ -836,7 +836,7 @@ _Unresolved._
|--|--|
| Severity | Medium |
| Category | Error handling & resilience |
| Status | Open |
| Status | Resolved |
| Location | `src/ScadaLink.CLI/Commands/AuditQueryHelpers.cs:186-193`, `src/ScadaLink.CLI/Commands/AuditExportHelpers.cs:147-153` |
**Description**
@@ -867,7 +867,22 @@ audit `SendGetAsync` already populates.
**Resolution**
_Unresolved._
Resolved 2026-05-28 (commit pending). Promoted `CommandHelpers.IsAuthorizationFailure`
from `private` to `internal` so both helpers can reuse the same auth-failure rule
(HTTP 403 OR `FORBIDDEN`/`UNAUTHORIZED` error code, case-insensitive).
`AuditQueryHelpers.RunQueryAsync` now returns
`CommandHelpers.IsAuthorizationFailure(response) ? 2 : 1` on the error path instead
of an unconditional 1. `AuditExportHelpers.RunExportAsync` doesn't ride
`ManagementResponse` (it streams directly via `SendGetStreamAsync`), so a new
`AuditExportHelpers.TryExtractErrorCode` helper parses the server's JSON error
envelope to extract `code`, and the `!IsSuccessStatusCode` branch returns exit 2 on
either HTTP 403 or a `FORBIDDEN`/`UNAUTHORIZED` envelope code. Regression tests:
`AuditQueryCommandTests.RunQuery_Http403_ReturnsExitCode2`,
`..._UnauthorizedCodeOnNon403_ReturnsExitCode2`,
`..._GenericServerError_ReturnsExitCode1` (negative guard);
`AuditExportCommandTests.RunExport_Http403_ReturnsExitCode2`,
`..._UnauthorizedCodeOnNon403_ReturnsExitCode2`. All five fail on the pre-fix code
and pass after.
### CLI-019 — `bundle export` decodes the entire base64 bundle into memory before writing