fix(transport): close bundle security + plaintext-retention gaps (4 findings)
T-003: move the unlock lockout server-side. The 3-strike counter used to live in the Razor page only — a second tab / CLI caller could re-upload the same bytes and grind PBKDF2 indefinitely. The counter now lives in IBundleSessionStore, keyed by ContentHash, so retries against identical bundle bytes are throttled regardless of client. BundleLockedException surfaces the new typed error path. T-005: bind the manifest's non-derivative fields into AES-GCM AAD. A SHA-256 of the manifest (with ContentHash + Encryption normalised to sentinels) is now passed to AesGcm.Encrypt / .Decrypt, so a tampered SourceEnvironment / ExportedBy / CreatedAtUtc on a stolen bundle yields an authentication-tag mismatch instead of slipping past the Step-4 typo-resistant confirmation gate. T-006: cap zip entry count, decompressed length, and compression ratio in LoadAsync's envelope validator BEFORE any payload is decompressed, using ZipArchiveEntry.Length / .CompressedLength. New TransportOptions fields default to 4 entries / 200 MB / 50x ratio. T-007: clear decrypted plaintext on the ApplyAsync failure path and zero the buffer on success before removing the session, so a 100 MB DecryptedContent doesn't sit in memory for the 30-min TTL after a failed apply. A BundleSessionEvictionService BackgroundService now also drives EvictExpired periodically so abandoned sessions clear without needing a fresh Get() call to trigger lazy eviction. Also resolves NO-010 — the misleading "writer never throws" XML doc was the same code+comment my prior NO-004 await-the-writer fix already rewrote.
This commit is contained in:
@@ -439,7 +439,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Documentation & comments |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.NotificationOutbox/NotificationOutboxActor.cs:469-477` |
|
||||
|
||||
**Description**
|
||||
|
||||
+7
-12
@@ -40,10 +40,10 @@ module file and counted in **Total**.
|
||||
| Severity | Open findings |
|
||||
|----------|---------------|
|
||||
| Critical | 0 |
|
||||
| High | 13 |
|
||||
| Medium | 56 |
|
||||
| High | 12 |
|
||||
| Medium | 52 |
|
||||
| Low | 90 |
|
||||
| **Total** | **159** |
|
||||
| **Total** | **154** |
|
||||
|
||||
## Module Status
|
||||
|
||||
@@ -63,7 +63,7 @@ module file and counted in **Total**.
|
||||
| [Host](Host/findings.md) | 2026-05-28 | `1eb6e97` | 0/0/2/5 | 7 | 22 |
|
||||
| [InboundAPI](InboundAPI/findings.md) | 2026-05-28 | `1eb6e97` | 0/1/3/4 | 8 | 25 |
|
||||
| [ManagementService](ManagementService/findings.md) | 2026-05-28 | `1eb6e97` | 0/0/2/2 | 4 | 23 |
|
||||
| [NotificationOutbox](NotificationOutbox/findings.md) | 2026-05-28 | `1eb6e97` | 0/0/3/3 | 6 | 10 |
|
||||
| [NotificationOutbox](NotificationOutbox/findings.md) | 2026-05-28 | `1eb6e97` | 0/0/2/3 | 5 | 10 |
|
||||
| [NotificationService](NotificationService/findings.md) | 2026-05-28 | `1eb6e97` | 0/1/2/3 | 6 | 25 |
|
||||
| [Security](Security/findings.md) | 2026-05-28 | `1eb6e97` | 0/0/0/2 | 2 | 21 |
|
||||
| [SiteCallAudit](SiteCallAudit/findings.md) | 2026-05-28 | `1eb6e97` | 0/0/2/4 | 6 | 6 |
|
||||
@@ -71,7 +71,7 @@ module file and counted in **Total**.
|
||||
| [SiteRuntime](SiteRuntime/findings.md) | 2026-05-28 | `1eb6e97` | 0/0/4/3 | 7 | 26 |
|
||||
| [StoreAndForward](StoreAndForward/findings.md) | 2026-05-28 | `1eb6e97` | 0/1/3/3 | 7 | 24 |
|
||||
| [TemplateEngine](TemplateEngine/findings.md) | 2026-05-28 | `1eb6e97` | 0/1/4/1 | 6 | 22 |
|
||||
| [Transport](Transport/findings.md) | 2026-05-28 | `1eb6e97` | 0/3/5/4 | 12 | 12 |
|
||||
| [Transport](Transport/findings.md) | 2026-05-28 | `1eb6e97` | 0/2/2/4 | 8 | 12 |
|
||||
|
||||
## Pending Findings
|
||||
|
||||
@@ -84,7 +84,7 @@ description, location, recommendation — lives in the module's `findings.md`.
|
||||
|
||||
_None open._
|
||||
|
||||
### High (13)
|
||||
### High (12)
|
||||
|
||||
| ID | Module | Title |
|
||||
|----|--------|-------|
|
||||
@@ -100,9 +100,8 @@ _None open._
|
||||
| TemplateEngine-017 | [TemplateEngine](TemplateEngine/findings.md) | Revision hash and diff both ignore `Description` and `Connections`, defeating staleness detection for real deployment changes |
|
||||
| Transport-001 | [Transport](Transport/findings.md) | Template Overwrite never syncs attributes / alarms / scripts |
|
||||
| Transport-002 | [Transport](Transport/findings.md) | ExternalSystem Overwrite never syncs methods |
|
||||
| Transport-003 | [Transport](Transport/findings.md) | Unlock lockout is enforced only client-side; server session is never marked Locked |
|
||||
|
||||
### Medium (56)
|
||||
### Medium (52)
|
||||
|
||||
| ID | Module | Title |
|
||||
|----|--------|-------|
|
||||
@@ -139,7 +138,6 @@ _None open._
|
||||
| ManagementService-021 | [ManagementService](ManagementService/findings.md) | Transport bundle handlers have zero test coverage |
|
||||
| NotificationOutbox-005 | [NotificationOutbox](NotificationOutbox/findings.md) | Ingest persistence inherits the CD-015 check-then-act race; under contention the second writer throws and the site retries |
|
||||
| NotificationOutbox-007 | [NotificationOutbox](NotificationOutbox/findings.md) | `NotificationOutboxOptions.DispatchBatchSize`, `DeliveredKpiWindow`, and `PurgeInterval` are not in the design document |
|
||||
| NotificationOutbox-010 | [NotificationOutbox](NotificationOutbox/findings.md) | Comment claims `PipeTo` is not used "because the writer never throws"; the surrounding try/catch is dead-letter for the documented failure mode |
|
||||
| NotificationService-020 | [NotificationService](NotificationService/findings.md) | NS-001 fix superseded; `AkkaHostedService` would register two competing `Notification` S&F handlers if both code paths ran |
|
||||
| NotificationService-024 | [NotificationService](NotificationService/findings.md) | No test affirms the central-only invariant; the orphaned-path tests give a false coverage signal |
|
||||
| SiteCallAudit-001 | [SiteCallAudit](SiteCallAudit/findings.md) | SupervisorStrategy override is dead code; XML claims Resume that is not enforced |
|
||||
@@ -158,9 +156,6 @@ _None open._
|
||||
| TemplateEngine-020 | [TemplateEngine](TemplateEngine/findings.md) | `Create*` audit entries are written with `EntityId = "0"` before `SaveChangesAsync` populates the real key |
|
||||
| TemplateEngine-021 | [TemplateEngine](TemplateEngine/findings.md) | `MoveTemplateAsync` skips folder cycle and sibling-name-collision validation |
|
||||
| Transport-004 | [Transport](Transport/findings.md) | `MaxUnlockAttemptsPerIpPerHour` option is declared but never enforced |
|
||||
| Transport-005 | [Transport](Transport/findings.md) | Manifest fields outside `ContentHash` are not bound to the encrypted payload |
|
||||
| Transport-006 | [Transport](Transport/findings.md) | Bundle ZIP read has no per-entry size cap or entry-count cap (zip-bomb / decompression-bomb) |
|
||||
| Transport-007 | [Transport](Transport/findings.md) | Failed import sessions retain decrypted plaintext for the full 30-minute TTL |
|
||||
| Transport-010 | [Transport](Transport/findings.md) | Critical Overwrite + cross-cutting paths uncovered by tests |
|
||||
|
||||
### Low (90)
|
||||
|
||||
@@ -121,7 +121,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | High |
|
||||
| Category | Security |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Transport/Import/BundleImporter.cs:184-203`, `src/ScadaLink.CentralUI/Components/Pages/Design/TransportImport.razor.cs:267-309`, `src/ScadaLink.Commons/Types/Transport/BundleSession.cs:14-16` |
|
||||
|
||||
**Description**
|
||||
@@ -190,7 +190,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Transport/Encryption/BundleSecretEncryptor.cs:31-49`, `src/ScadaLink.Transport/Serialization/ManifestValidator.cs:29-53` |
|
||||
|
||||
**Description**
|
||||
@@ -226,7 +226,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Security |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Transport/Serialization/BundleSerializer.cs:121-156`, `src/ScadaLink.Transport/Import/BundleImporter.cs:132-143` |
|
||||
|
||||
**Description**
|
||||
@@ -259,7 +259,7 @@ _Unresolved._
|
||||
|--|--|
|
||||
| Severity | Medium |
|
||||
| Category | Performance & resource management |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
| Location | `src/ScadaLink.Transport/Import/BundleImporter.cs:614-696`, `src/ScadaLink.Transport/Import/BundleSessionStore.cs:67-93` |
|
||||
|
||||
**Description**
|
||||
|
||||
Reference in New Issue
Block a user