[AZ-1074] [AZ-1075] Cycle 9 closeout: security, tests, metrics
ci/woodpecker/push/01-test Pipeline failed
ci/woodpecker/push/02-build-push unknown status

Resolve F-AZ1074-1/2 (collection caps, generic gRPC internal errors).
Standalone integration compose stack, docs, security audit, perf and retro.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-06-25 17:32:14 +03:00
parent 7633134a8a
commit 7ed780b063
22 changed files with 618 additions and 40 deletions
@@ -0,0 +1,38 @@
# Dependency Scan (Cycle 9)
**Date**: 2026-06-25
**Mode**: Delta scan
**Scope**: Cycle-9 delta over cycle-8 (`dependency_scan_cycle8.md`). Surface = AZ-1074/AZ-1075 gRPC RouteTileDelivery + `SatelliteProvider.GrpcContracts`.
**Method**: `dotnet list SatelliteProvider.sln package --vulnerable --include-transitive` via Docker SDK 10.0 image + manifest diff on new/changed csproj files.
## Cycle-9 Package Manifest Diff
| csproj | Cycle 8 baseline | Cycle 9 change |
|--------|------------------|----------------|
| `SatelliteProvider.Api/SatelliteProvider.Api.csproj` | unchanged | **+1** `Grpc.AspNetCore` 2.71.0 |
| `SatelliteProvider.GrpcContracts/SatelliteProvider.GrpcContracts.csproj` | **NEW** | `Google.Protobuf` 3.31.1, `Grpc.AspNetCore` 2.71.0, `Grpc.Tools` 2.71.0 (PrivateAssets) |
| All other csproj | unchanged | **+0** |
## Vulnerable Package Scan (2026-06-25)
| Project | Finding | Severity | Notes |
|---------|---------|----------|-------|
| `SatelliteProvider.Api` | none | — | Includes new `Grpc.AspNetCore` 2.71.0 — clean |
| `SatelliteProvider.GrpcContracts` | none | — | New project — clean |
| `SatelliteProvider.IntegrationTests` | transitive `Microsoft.IdentityModel.JsonWebTokens` 7.0.3, `System.IdentityModel.Tokens.Jwt` 7.0.3 | Moderate | GHSA-59j7-ghrg-fj52 — **test-runtime only** (pre-existing; unchanged by cycle 9) |
| `SatelliteProvider.TestSupport` | same JWT packages 7.0.3 | Moderate | test-runtime only — pre-existing |
## Cycle-9 Findings
**No new dependency CVEs** from the gRPC package additions. Grpc.AspNetCore 2.71.0 / Google.Protobuf 3.31.1 report clean against NuGet advisory feed at scan time.
## Carry-overs
- **D-AZ795-1** (Low): FluentValidation 12.0.0 → 12.1.1 hardening — still open
- **D2-cy4** (Medium, test-runtime): `Microsoft.NET.Test.Sdk` transitive — still open
## Verdict
**PASS** (cycle-9 delta) — zero new CVEs in production/runtime packages.
Cumulative: **PASS_WITH_WARNINGS** — D2-cy4 + D-AZ795-1 carry-overs unchanged.
@@ -0,0 +1,27 @@
# Infrastructure & Configuration Review (Cycle 9)
**Date**: 2026-06-25
**Mode**: Delta scan
**Scope**: Cycle-9 infrastructure changes only.
| File | Change | Security relevance |
|------|--------|-------------------|
| `docker-compose.tests.yml` | Rewritten as self-contained stack; **no host port publishing** for postgres/api | **Positive** — avoids port conflicts; reduces accidental exposure of test DB/API to host network |
| `scripts/run-tests.sh` | Integration runs use `docker-compose.tests.yml` only | Aligns with above |
| `SatelliteProvider.Api/Dockerfile` | Added `GrpcContracts` csproj COPY | Build-order only; no new secrets |
| `SatelliteProvider.IntegrationTests/Dockerfile` | `linux/amd64` platform; `aspnet:10.0` runtime for Grpc.AspNetCore | Protoc/build stability; no new exposed ports |
| `docker-compose.yml` (dev) | Unchanged | Host ports 5433/18980 still published for local dev — pre-existing |
| CI/CD, `.env`, `appsettings.*` | Unchanged | — |
## Container checks (carried forward)
| Check | Status |
|-------|--------|
| Non-root user in API image | Still runs as root (pre-existing; not cycle-9 regression) |
| Secrets in build args | None |
| Dev TLS cert gitignored | `./certs/` — unchanged |
| JWT via env vars | Unchanged |
## Verdict
**PASS** (cycle-9 delta) — test harness change improves isolation; no new misconfiguration.
+22
View File
@@ -0,0 +1,22 @@
# OWASP Top 10 Review (Cycle 9)
**Date**: 2026-06-25
**Framework**: OWASP Top 10:2021
**Scope**: Cycle-9 gRPC delta (AZ-1074/AZ-1075)
| Category | Status (cycle-9 delta) | Notes |
|----------|------------------------|-------|
| A01 — Broken Access Control | **PASS** | `[Authorize]` on gRPC service; anonymous calls rejected (integration tests cover JWT baseline) |
| A02 — Cryptographic Failures | **N/A** | TLS via Kestrel dev cert / production ingress — unchanged pattern from AZ-505 |
| A03 — Injection | **PASS** | No new string-built SQL; tile coords validated before expand |
| A04 — Insecure Design | **PASS (post-follow-up)** | F-AZ1074-1 unbounded collections **resolved** — caps aligned with REST |
| A05 — Security Misconfiguration | **PASS** | gRPC message size limits set; test compose no longer publishes DB port to host |
| A06 — Vulnerable Components | **PASS_WITH_WARNINGS** | New Grpc.AspNetCore 2.71.0 clean; D-AZ795-1 + D2-cy4 carry-overs |
| A07 — Auth Failures | **PASS** | Same JWT contract as REST; gRPC metadata `Authorization: Bearer` |
| A08 — Data Integrity Failures | **N/A** | No CI/CD or signing changes |
| A09 — Logging Failures | **PASS_WITH_WARNINGS** | F-AZ1074-2 **resolved**; F-AZ795-1/F-AZ795-2 REST carry-overs still open |
| A10 — SSRF | **N/A** | No URL inputs in gRPC contract |
## Verdict
**PASS_WITH_WARNINGS** cumulative (REST carry-overs). Cycle-9 delta: **PASS** after Step-14 follow-up fixes.
@@ -0,0 +1,60 @@
# Security Audit Report (Cycle 9)
**Date**: 2026-06-25
**Scope**: Cycle-9 delta — AZ-1074 (gRPC RouteTileDelivery service) + AZ-1075 (integration tests) + `SatelliteProvider.GrpcContracts`.
**Trigger**: `/autodev` Step 14 — user chose **A) Run security audit**.
**Verdict (cycle-9 delta, post-follow-up)**: **PASS** — 0 Medium open, 1 Low resolved in follow-up, 0 new Critical/High.
**Verdict (cumulative)**: **PASS_WITH_WARNINGS** — cycle-4/7/8 carry-overs unchanged (D2-cy4, D-AZ795-1, F-AZ795-1, F-AZ795-2, F-AZ810-1, F-AZ810-2).
## Summary
| Severity | Cycle 9 at audit | Post Step-14 follow-up | Cumulative open |
|----------|------------------|------------------------|-----------------|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 0 |
| Medium | 1 (F-AZ1074-1) | **0 — RESOLVED** | 1 (D2-cy4 test-runtime) |
| Low | 1 (F-AZ1074-2) | **0 — RESOLVED** | 5+ (cycle 78 carry-overs) |
## OWASP Top 10:2021 (cycle-9 delta)
See `owasp_review_cycle9.md` — all categories PASS or N/A after follow-up.
## Findings
| # | Severity | Category | Location | Title | Status |
|---|----------|----------|----------|-------|--------|
| F-AZ1074-1 | Medium | Insecure Design (A04) | `RouteTileDeliveryOrchestrator.ValidateJob` | Unbounded gRPC waypoints/geofences/client_tiles | **RESOLVED** — caps 500/50/5000 |
| F-AZ1074-2 | Low | Information Disclosure (A09) | `RouteTileDeliveryGrpcService` catch-all | `ex.Message` in `DeliveryError` | **RESOLVED** — generic client message |
### F-AZ1074-1 detail (RESOLVED)
Aligned gRPC collection caps with REST: `waypoints ≤ 500`, `geofences ≤ 50`, `client_tiles ≤ 5000`. InvalidArgument via existing `ArgumentException``RpcException` mapping.
### F-AZ1074-2 detail (RESOLVED)
Internal errors now return `"An internal error occurred."` on the wire; `LogError` retains full exception.
## Carry-overs (unchanged)
- **F-AZ795-1, F-AZ795-2, F-AZ810-1, F-AZ810-2** — REST information-disclosure / time-handling (cycle 78)
- **D-AZ795-1** — FluentValidation 12.0.0 → 12.1.1
- **D2-cy4** — test SDK transitive (Medium, test-runtime only)
## Recommendations
### Immediate
- None blocking cycle 9 ship.
### Short-term (cycle 10+)
- Sanitise REST `GlobalExceptionHandler` + `UavUploadValidationFilter` (F-AZ795-1 / F-AZ810-1) in one ticket.
- Bump FluentValidation 12.0.0 → 12.1.1 (D-AZ795-1).
### Long-term
- Consider `region_size_meters` upper bound on gRPC path (REST uses 10_000 m cap) — advisory parity, not release-blocking.
## Artifacts
- `dependency_scan_cycle9.md`
- `static_analysis_cycle9.md`
- `owasp_review_cycle9.md`
- `infrastructure_review_cycle9.md`
@@ -0,0 +1,48 @@
# Static Analysis (Cycle 9)
**Date**: 2026-06-25
**Mode**: Delta scan
**Scope**: AZ-1074 + AZ-1075 gRPC surface. Cycle-8 baseline remains authoritative for REST validators.
**Files in scope**:
- `SatelliteProvider.Api/Grpc/RouteTileDeliveryGrpcService.cs` (new)
- `SatelliteProvider.Api/Program.cs` (`AddGrpc`, `MapGrpcService`, message size limits)
- `SatelliteProvider.Services.RouteManagement/TileProvision/RouteTileDeliveryOrchestrator.cs` (validation hardening)
- `SatelliteProvider.GrpcContracts/tile_provision.proto` + generated stubs
- `SatelliteProvider.IntegrationTests/RouteTileDeliveryGrpcTests.cs`, `GrpcTestHelpers.cs`
- `SatelliteProvider.IntegrationTests/Dockerfile` (linux/amd64, aspnet runtime)
- `docker-compose.tests.yml` (self-contained test stack)
**Method**: End-to-end read of new files; grep for hardcoded secrets; trace auth middleware order; compare gRPC validation bounds vs REST `CreateRouteRequestValidator`.
## Findings
### F-AZ1074-1 — Unbounded gRPC request collections enable authenticated DoS (Medium / A04) — **RESOLVED in cycle 9 (Step-14 follow-up)**
- **Location**: `RouteTileDeliveryOrchestrator.ValidateJob` (pre-fix).
- **Description**: `DeliverRouteTiles` accepted unbounded `waypoints`, `geofences`, and `client_tiles` protobuf repeated fields. REST `POST /api/satellite/route` caps `points` at 500 and `geofences.polygons` at 50 (cycle-8 F-AZ809-1 fix); gRPC had no equivalent caps before cycle 9 Step 14.
- **Impact**: Medium. Auth-gated (`[Authorize]` on `RouteTileDeliveryGrpcService`; JWT metadata required). Authenticated operator could force large CPU/memory work in `RouteTileExpander.Expand` and `ClientTileCatalog.IndexByZxy`.
- **Resolution**: Added `MaxWaypoints = 500`, `MaxGeofencePolygons = 50`, `MaxClientTiles = 5000` (inventory cap parity) to `ValidateJob`. Unit test `DeliverAsync_TooManyWaypoints_Throws` added.
### F-AZ1074-2 — Internal exception message echoed to gRPC client (Low / A09) — **RESOLVED in cycle 9 (Step-14 follow-up)**
- **Location**: `RouteTileDeliveryGrpcService.cs:55-58` (pre-fix).
- **Description**: Generic `catch (Exception)` wrote `ex.Message` into stream `DeliveryError.Message` — parallel to cycle-7 F-AZ795-1 (REST ProblemDetails path).
- **Impact**: Low. Auth-gated. Could leak internal exception text to authenticated clients.
- **Resolution**: Client message replaced with generic `"An internal error occurred."`; full exception still logged server-side.
## Pass areas (cycle-9 delta)
| Area | Result |
|------|--------|
| SQL injection | N/A — no new raw SQL |
| Hardcoded secrets | None in new files |
| gRPC auth | `[Authorize]` + `UseAuthentication`/`UseAuthorization` before `MapGrpcService` |
| JWT on gRPC | Integration tests pass Bearer token via metadata — matches REST contract |
| Message size limits | `MaxReceiveMessageSize = 16 MiB`, `MaxSendMessageSize = 64 MiB` configured |
| Protobuf parsing | Bounded by Kestrel/gRPC message limits; collection caps added post-audit |
| Test fixtures | `GrpcTestHelpers` uses env-resolved JWT via `JwtTestHelpers.MintAuthenticated` — no embedded secrets |
## Verdict
**PASS_WITH_WARNINGS** at audit time (1 Medium open → **resolved in Step-14 follow-up**). Post-fix delta: **PASS** for cycle-9 new code.