[AZ-808] [AZ-809] [AZ-810] [AZ-811] [AZ-812] Cycle 8 security audit

PASS_WITH_WARNINGS. Zero Critical / High.

New cycle-8 findings:
- F-AZ809-1 (Medium / A04 Insecure Design): unbounded
  geofences.polygons enables an authenticated DoS on
  POST /api/satellite/route. Cap candidate: 50 or 500.
- F-AZ810-1 (Low / A09): JsonException.Message echoed in
  UavUploadValidationFilter (new instance of cycle-7 F-AZ795-1
  pattern in a second code path).
- F-AZ810-2 (Low / Informational): UavTileMetadata.CapturedAt
  typed DateTime not DateTimeOffset; freshness window drifts in
  non-UTC dev environments. Zero impact in UTC-deployed prod.

Carry-overs (cycle 7): F-AZ795-1, F-AZ795-2, D-AZ795-1 still
open. Cycle 4 D2-cy4 still open (test-runtime Medium).

Cycle-8 architectural wins recorded: per-endpoint validation
reached 100% coverage; three approved validation paths
formalised; OSM wire-format normalisation under strict mode
(AZ-812); UAV-handler defence-in-depth retained.

Highest-priority cycle-9 follow-up: F-AZ809-1 polygon cap.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-23 15:17:31 +03:00
parent 6207ab7c27
commit ac40a8b352
6 changed files with 613 additions and 4 deletions
@@ -0,0 +1,60 @@
# Dependency Scan (Cycle 8)
**Date**: 2026-05-23
**Mode**: Delta scan
**Scope**: Cycle-8 delta over the cycle-7 dependency scan (`_docs/05_security/dependency_scan_cycle7.md`). Cycle-8 surface = AZ-808 + AZ-809 + AZ-810 + AZ-811 (strict input validation rolled out across the remaining 4 endpoints) + AZ-812 (region-API wire rename `Latitude`/`Longitude``Lat`/`Lon`).
**Method**: Manifest diff via `git diff --name-only 865dfdb..b763da3 -- '*.csproj'` (cycle-7 tip → cycle-8 tip; verified empty). `dotnet list package --vulnerable` is intentionally not run (per `AGENTS.md`: that command hangs the agent shell in this workspace). The manifest-diff substitute is deterministic because the result is null.
## Cycle-8 Package Manifest Diff
| csproj | Cycle 7 baseline | Cycle 8 change | Net effect on supply chain |
|--------|------------------|----------------|----------------------------|
| `SatelliteProvider.Api/SatelliteProvider.Api.csproj` | references `Microsoft.AspNetCore.OpenApi 10.0.7`, `Microsoft.AspNetCore.Authentication.JwtBearer 10.0.7`, `Newtonsoft.Json 13.0.4`, `Serilog.AspNetCore 8.0.3`, `Serilog.Sinks.File 6.0.0`, `SixLabors.ImageSharp 3.1.11`, `Swashbuckle.AspNetCore 10.1.7`, `FluentValidation 12.0.0`, `FluentValidation.DependencyInjectionExtensions 12.0.0` | **+0 PackageReferences**. Every cycle-8 validator + filter reuses the AZ-795 infrastructure already shipped in cycle 7. | None. |
| `SatelliteProvider.Common/SatelliteProvider.Common.csproj` | unchanged from cycle 5 | **+0 PackageReferences** — the cycle-8 DTO changes (`[JsonRequired]` on `RequestRegionRequest`, `CreateRouteRequest`, `RoutePoint`, `GeoPoint`, `GeofencePolygon.NorthWest/SouthEast`, `Geofences.Polygons`, `UavTileMetadata.*`) and the AZ-812 rename are BCL + `System.Text.Json.Serialization` only. | None. |
| `SatelliteProvider.DataAccess/SatelliteProvider.DataAccess.csproj` | unchanged from cycle 5 | **+0 PackageReferences**. | None. |
| `SatelliteProvider.Services.TileDownloader/SatelliteProvider.Services.TileDownloader.csproj` | unchanged from cycle 5 | **+0 PackageReferences**. The cycle-8 validator path lives in the API project; the existing `UavTileUploadHandler` defence-in-depth path is untouched. | None. |
| `SatelliteProvider.Services.RegionProcessing/SatelliteProvider.Services.RegionProcessing.csproj` | unchanged from cycle 5 | **+0 PackageReferences**. | None. |
| `SatelliteProvider.Services.RouteManagement/SatelliteProvider.Services.RouteManagement.csproj` | unchanged from cycle 5 | **+0 PackageReferences**. | None. |
| `SatelliteProvider.Tests/SatelliteProvider.Tests.csproj` | unchanged from cycle 5 | **+0 PackageReferences** — the 8 new validator unit-test files reuse the cycle-7 `FluentValidation.TestHelper` namespace (transitive via `FluentValidation` main package, picked up via `ProjectReference` to the API). | None. |
| `SatelliteProvider.IntegrationTests/SatelliteProvider.IntegrationTests.csproj` | unchanged from cycle 5 | **+0 PackageReferences** — the 4 new integration test files (`CreateRouteValidationTests`, `GetTileByLatLonValidationTests`, `RegionFieldRenameTests`, `RegionRequestValidationTests`, `UavUploadValidationTests`) reuse the cycle-7 `ProblemDetailsAssertions` helper + the pre-existing `Xunit` + `Microsoft.AspNetCore` ProjectReference. | None. |
| `SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj` | unchanged from cycle 5 | **+0 PackageReferences**. | None. |
**Net cycle-8 dependency change**: **zero new `PackageReference` lines, zero removed lines, zero version bumps**. Every `*.csproj` file in the repo is byte-identical between `865dfdb` (cycle-7 tip) and `b763da3` (cycle-8 tip).
## Cycle-7 Carry-overs
Because cycle 8 added no new packages and bumped none, every cycle-7 dependency finding remains in force unchanged:
### D-AZ795-1 (Low / Hardening) — FluentValidation 12.0.0 → 12.1.1
- Filed in `dependency_scan_cycle7.md` § "FluentValidation 12.0.0" and `security_report_cycle7.md` § "D-AZ795-1".
- Status at cycle-8 tip: **still open**. Cycle 8 did not bump either `FluentValidation` or `FluentValidation.DependencyInjectionExtensions` from 12.0.0 to 12.1.1. The same hardening-release recommendation carries forward.
- Cycle-8-specific re-check at https://github.com/FluentValidation/FluentValidation/security/advisories (audit date 2026-05-23): no NEW advisories published against 12.x since the cycle-7 audit. The bump remains pure forward-compatibility hardening.
### D2-cy4 (Medium / test-runtime only) — `Microsoft.NET.Test.Sdk 17.8.0` transitive `NuGet.Frameworks`
- Filed in `dependency_scan_cycle4.md` and re-confirmed in cycles 5 and 7.
- Status at cycle-8 tip: **still open**. Cycle 8 did not bump `Microsoft.NET.Test.Sdk`. Test-runtime exposure only; not reachable from the API process.
## Cycle-8 New Source Code Runtime Surface
Because no new packages were added, the new runtime surface introduced by cycle 8 sits **entirely within already-vetted packages**:
| New surface | Hosted by | Risk delta |
|-------------|-----------|------------|
| 4 new `AbstractValidator<T>` subclasses (`RegionRequestValidator`, `CreateRouteRequestValidator`, `GetTileByLatLonQueryValidator`, `UavTileBatchMetadataPayloadValidator`) + 4 helper validators (`RoutePointValidator`, `GeofencePolygonValidator`, `UavTileMetadataValidator`, the inner `GeoCornerValidator`) | `FluentValidation 12.0.0` — same package surface assessed in cycle 7. | None — reflection scan path (`AddValidatorsFromAssemblyContaining<Program>()`) is unchanged from cycle 7; cycle 8 adds more registered types but uses the same registration call. |
| `RejectUnknownQueryParamsEndpointFilter` + `UavUploadValidationFilter` — two new `IEndpointFilter` types | ASP.NET Core 10 — `Microsoft.AspNetCore.Http` already in the BCL footprint. | None — `IEndpointFilter` API surface is unchanged; `UavUploadValidationFilter` is `AddTransient<>`-registered, so per-request instance isolation matches the existing `WithValidation<T>()` pattern from cycle 7. |
| `[JsonRequired]` annotations on 6 modified DTOs (`RequestRegionRequest`, `CreateRouteRequest`, `RoutePoint`, `GeoPoint`, `GeofencePolygon`, `UavTileMetadata`) | `System.Text.Json.Serialization` — BCL. | None — cycle-7 baseline already used `[JsonRequired]` on `TileCoord`; cycle 8 just expands coverage. |
| `[JsonPropertyName]` annotations added by AZ-812 to `RequestRegionRequest.Lat`/`Lon` | `System.Text.Json.Serialization` — BCL. | None. |
## Cycle-8 Findings
**No new dependency findings.**
The cycle-7 D-AZ795-1 Low/Hardening recommendation (`FluentValidation 12.0.0 → 12.1.1`) is **carried forward unchanged** and re-iterated in this cycle's report. Cycle 8 did not regress any prior cycle's posture.
## Verdict
**PASS** (cycle-8 delta) — zero new CVEs, zero new supply-chain blockers, zero new packages.
Cumulative verdict (carrying forward earlier cycles): **PASS_WITH_WARNINGS** — D2-cy4 (cycle-4 Medium, test-runtime only) + D-AZ795-1 (cycle-7 Low/Hardening) both still in effect. Cycle 8 adds nothing to the cumulative dependency-finding ledger.
@@ -0,0 +1,70 @@
# Infrastructure & Configuration Review (Cycle 8)
**Date**: 2026-05-23
**Mode**: Delta scan
**Scope**: Cycle-8 changes to deployment configs, CI/CD files, environment templates, and shell scripts only.
## Cycle-8 Infrastructure-Layer Diff
Computed via `git diff --name-only 865dfdb..b763da3` (cycle-7 tip → cycle-8 tip), filtered to infrastructure-relevant paths:
| File | Diff summary | Security relevance |
|------|--------------|--------------------|
| `Dockerfile`, `Dockerfile.tests`, `Dockerfile.api` (and any image-build file) | **NOT modified** | None — cycle 8 did not touch any image build. |
| `docker-compose.yml`, `docker-compose.tests.yml`, `docker-compose.prod.yml` (and any orchestration file) | **NOT modified** | None — cycle 8 did not touch any compose file. |
| `.woodpecker.yml`, `.github/workflows/**` (and any CI/CD pipeline definition) | **NOT modified** | None — cycle 8 added no automated pipeline changes. |
| `.env`, `.env.example`, `.env.tests` (and any environment template) | **NOT modified** | None — cycle 8 read no new env vars (every cycle-8 validator and filter is pure code; FluentValidation 12.0.0 has no config knobs). |
| `appsettings.json`, `appsettings.Development.json`, `appsettings.tests.json` | **NOT modified** | None — cycle 8 added no new configuration sections. The pre-existing `UavQualityConfig` section is unchanged. |
| `scripts/probe_latlon_validation.sh` | NEW manual probe script for AZ-811 | Reviewed in `static_analysis_cycle8.md` § Test Code Review § `scripts/probe_*_validation.sh`. ✓ |
| `scripts/probe_region_validation.sh` | NEW manual probe script for AZ-808 | Reviewed in `static_analysis_cycle8.md` § Test Code Review § `scripts/probe_*_validation.sh`. ✓ |
| `scripts/probe_route_validation.sh` | NEW manual probe script for AZ-809 | Reviewed in `static_analysis_cycle8.md` § Test Code Review § `scripts/probe_*_validation.sh`. ✓ |
| `scripts/probe_upload_validation.sh` | NEW manual probe script for AZ-810 | Reviewed in `static_analysis_cycle8.md` § Test Code Review § `scripts/probe_*_validation.sh`. ✓ |
| `scripts/run-performance-tests.sh` | Modified — diff is exclusively the AZ-812 wire-format rename (`?Latitude=…&Longitude=…&ZoomLevel=…``?lat=…&lon=…&zoom=…` across query strings, and `{"latitude":…,"longitude":…}``{"lat":…,"lon":…}` across JSON bodies in PT-01 through PT-08 invocations) | No new credentials, no new shell-injection surface. The change is a forced wire-update made necessary by AZ-812's contract rename. ✓ |
| `.vscode/launch.json`, `.vscode/tasks.json`, `README.md` | Modified — developer-tooling artifacts | Out of scope for security audit (not deployed; do not affect runtime). |
## Probe Shell Scripts — Common Safety Posture
All four new `probe_*_validation.sh` scripts follow the same defensive pattern (verified per `static_analysis_cycle8.md`):
- `#!/usr/bin/env bash` with `set -euo pipefail` at the top — fail-fast on undefined vars, broken pipes, command failures.
- `API_URL="${API_URL:-https://localhost:8080}"` default; `JWT="${JWT:-}"` with an explicit `if [[ -z "${JWT}" ]]; then echo "ERROR: set JWT env var to a bearer token. …"; exit 2; fi` guard.
- `curl -k` used (justified — the dev cert is self-signed; the scripts target localhost in dev/test only — documented in each script's header).
- Hand-built JSON payloads via shell variable interpolation — values are integer / quoted strings only, no caller-supplied shell strings.
- No embedded secrets, no hardcoded `$JWT`, no `.env` reads.
Identical posture to the cycle-7 `probe_inventory_validation.sh`. ✓
## Container & Image Security — Carried Forward Unchanged
| Check | Status (carried from cycle 5/6/7) | Cycle-8 impact |
|-------|-----------------------------------|----------------|
| Non-root container user (Dockerfile `USER` directive) | Already in effect | None — no Dockerfile change. |
| Minimal base image (alpine/distroless/etc.) | The API image uses the .NET 10 SDK base — same as cycle 7; image hardening is still owned by a separate, unscheduled follow-up task. | None. |
| No secrets in build args | Verified cycle 5; no `Dockerfile` change in cycle 7 or 8 | None. |
| Health checks | Compose `healthcheck` block on Postgres unchanged | None. |
## CI/CD Security — Carried Forward Unchanged
| Check | Status | Cycle-8 impact |
|-------|--------|----------------|
| Secrets management (env vars / vault, not pipeline literals) | Existing pattern preserved | None. |
| No credentials in pipeline definitions | `.woodpecker.yml` untouched in cycle 8 | None. |
| Artifact signing | Existing posture (none — owned by a separate operational improvement track) | None. |
| Dependency-audit step in pipeline | Existing posture (manual audit per `dependency_scan_cycle*.md`; no automated `dotnet list package --vulnerable` in CI due to the build-hang issue noted in `AGENTS.md`) | None. |
## Environment & Secrets
- `.env.example` — not modified in cycle 8. The cycle-8 code reads no new env vars.
- `appsettings.Development.json` — not modified in cycle 8.
- `appsettings.json` — production template; unchanged in cycle 8.
- The cycle-7 host-port change (`5432:5432``5433:5432`) is preserved through cycle 8; not relevant to production exposure (production containers run on a private docker network without host-port mapping per the existing deployment model).
## Cycle-7 + Earlier Carry-overs
No infrastructure-layer carry-overs from earlier cycles. The cycle-2 `UavQualityConfig`-derived `KestrelServerOptions.Limits.MaxRequestBodySize = 500 MiB` (set in `Program.cs:41-43`) is noted in `owasp_review_cycle8.md` § A05 as a configuration choice that contributed to the F-AZ809-1 exploit-math worst case, but it is not itself an infrastructure-layer finding — the value is correct for the UAV upload endpoint, and the proper fix is per-endpoint size narrowing (a code change, not an infra change).
## Verdict (Phase 4)
**PASS** — zero new infrastructure-layer findings.
The four new probe scripts are dev/test only, env-driven, fail-fast under `set -euo pipefail`, and contain no embedded credentials. The single perf-script update is a wire-rename diff with no security impact. Cycle 8 made no Docker, compose, CI, env-template, or appsettings changes.
+111
View File
@@ -0,0 +1,111 @@
# OWASP Top 10 Review (Cycle 8)
**Date**: 2026-05-23
**Mode**: Delta scan against OWASP Top 10:2021 (current at audit time per https://owasp.org/www-project-top-ten/ — verified 2026-05-23; the 2025 candidate revision is still in public-comment phase and not adopted).
**Scope**: Cycle-8 delta only — AZ-808 (region POST validator), AZ-809 (route POST validator + per-point + per-polygon), AZ-810 (UAV upload metadata validator + custom filter), AZ-811 (lat/lon GET validator + unknown-query-param filter), AZ-812 (region-API `Latitude`/`Longitude``Lat`/`Lon` rename). Earlier cycles' OWASP reviews remain authoritative for their respective surfaces; this file does NOT re-walk the cycle-5 / cycle-7 baselines.
## A01 — Broken Access Control
**Status**: PASS
- `.RequireAuthorization()` is preserved on every cycle-8 endpoint:
- `POST /api/satellite/request` at `Program.cs:251`
- `POST /api/satellite/route` at `Program.cs:267`
- `GET /api/satellite/tiles/latlon` at `Program.cs:213`
- `POST /api/satellite/upload` at `Program.cs:238` (additionally requires the `GPS` permission claim via `SatellitePermissions.UavUploadPolicy`).
- Endpoint-filter execution order is governed by ASP.NET Core's middleware → routing → endpoint-filter pipeline. `app.UseAuthorization()` (line 206) reads the endpoint metadata produced by `.RequireAuthorization()` and short-circuits anonymous callers with 401 BEFORE the endpoint dispatch reaches any endpoint filter. Cycle-8 verification: none of the four new validation paths runs unless the caller is authenticated.
- The new `RejectUnknownQueryParamsEndpointFilter` (lat/lon GET) does not establish its own auth gate — it relies on the endpoint chain's existing `.RequireAuthorization()`. Anonymous query-param probing is impossible.
- No new CORS policy in cycle 8. `TilesCors` (cycle-6 baseline) is unchanged.
- No new IDOR paths — the four endpoints operate on caller-supplied identifiers but do not couple them to any tenant or owner field; tiles remain globally-scoped in the post-AZ-484 model. Geospatial identifiers (`Lat`, `Lon`, `Z/X/Y`) are deterministic projections of physical reality, not capability tokens.
## A02 — Cryptographic Failures
**Status**: N/A (cycle 8)
- Cycle 8 has no cryptographic operations. JWT validation is unchanged from cycle 4 (`AddSatelliteJwt` — HS256 with ≥ 32-byte secret, `ValidateLifetime + ValidateIssuer + ValidateAudience = true`, ClockSkew = 30s).
- The cycle-5 UUIDv5 SHA-1 surface (`Uuidv5.Create`) is unaffected.
- TLS posture (Kestrel `Http1AndHttp2` with self-signed dev cert / ingress termination in prod) — unchanged from cycle 6.
- F-AZ810-2 (`DateTime` vs `DateTimeOffset` parsing) is NOT a crypto failure — it's a time-handling correctness concern documented under A09.
## A03 — Injection
**Status**: PASS
- No SQL / Dapper / Npgsql usage in any cycle-8 new file. (`grep -r 'Dapper\|Npgsql' SatelliteProvider.Api/Validators SatelliteProvider.Api/DTOs SatelliteProvider.Common/DTO` → zero matches across the cycle-8 surface.)
- No `Process.Start` / shell-out / `eval` in any cycle-8 new file.
- All inputs reaching the cycle-8 validators are strongly typed by the time the rules execute (`double`, `int`, `Guid`, `DateTime`, `string`, `IReadOnlyList<T>`). `System.Text.Json` has already parsed and rejected anything malformed before the validator runs, and the cycle-7 deserializer hardening (`UnmappedMemberHandling.Disallow`) is in force on every cycle-8 path including the multipart `metadata` field (`UavUploadValidationFilter.cs:73` uses the same global `JsonSerializerOptions` via `IOptions<JsonOptions>`).
- The cycle-8 wire-format rename (AZ-812) means the deserializer now strictly enforces `lat` / `lon` against the post-rename JSON schema — legacy `Latitude` / `Longitude` fields are rejected as unknown members, surfacing as `JsonException` → 400 via `GlobalExceptionHandler` (not silently bound to a fallback property). This is a small *anti-injection* improvement at the schema boundary.
## A04 — Insecure Design
**Status**: PASS_WITH_WARNINGS (Medium — F-AZ809-1)
- AZ-808 / AZ-809 / AZ-810 / AZ-811 are themselves a *design fix* for the remaining unprotected endpoints — completing the AZ-795 epic's per-endpoint rollout. Pre-cycle-8, four endpoints (region POST, route POST, lat/lon GET, UAV upload) used ad-hoc inline `try/catch` blocks or no input validation at all. Cycle 8 centralises every public endpoint behind one of three approved validation paths:
1. `WithValidation<T>()` for JSON-body endpoints (RegionRequest, CreateRouteRequest).
2. `WithValidation<T>()` + `RejectUnknownQueryParamsEndpointFilter` for query-string endpoints (GetTileByLatLonQuery).
3. Custom `IEndpointFilter` for non-standard wire formats (`UavUploadValidationFilter` for multipart).
- The cycle-7 architecture-doc § 9 coverage table now reaches **100% of public-facing input endpoints** with validators (region POST, route POST, lat/lon GET, inventory POST, UAV upload). Future drift visibility is high.
- **However, F-AZ809-1** (from `static_analysis_cycle8.md`) identifies a design-level gap: `CreateRouteRequestValidator` lacks a max-count cap on `Geofences.Polygons`, in contrast to every other list-bearing field across the API (route `Points` ≤ 500, UAV `Items` ≤ 100, inventory `Tiles`/`LocationHashes` ≤ 5000). The pattern across the API is "cap every collection field"; one collection slipped past. The global `KestrelServerOptions.Limits.MaxRequestBodySize = 500 MiB` was sized for the UAV upload endpoint but applies to every route — leaving the validator as the sole gate against a million-element collection submission on the route endpoint. **Filed as Medium**.
## A05 — Security Misconfiguration
**Status**: PASS
- `UnmappedMemberHandling.Disallow` (cycle-7 global default) is now backed by per-endpoint FluentValidation rules at every cycle-8 endpoint, completing the defence-in-depth hardening for mass-assignment prevention.
- Swagger exposure is still gated by `app.Environment.IsDevelopment()` (unchanged).
- `appsettings.Development.json` clearly tags DEV-ONLY JWT iss/aud values; `appsettings.json` ships empty so production fail-fast triggers if env vars are missing (unchanged from cycle 4).
- The new `AddTransient<UavUploadValidationFilter>` registration in `Program.cs:128` is correct — transient (not singleton) ensures each request gets a fresh filter instance, preventing accidental cross-request state retention through filter-instance fields.
- The cycle-7 `AddValidatorsFromAssemblyContaining<Program>()` scope rule is unchanged; the cycle-8 validators all live in `SatelliteProvider.Api.dll`, so the reflection scan correctly picks them up.
- **Note (informational, not a finding)**: the global Kestrel body-size limit (`MaxRequestBodySize = 500 MiB`) was originally set for the UAV upload endpoint in cycle 2 (AZ-488). It applies to every endpoint by default because Kestrel exposes a per-server, not per-endpoint, default. Cycle 8's F-AZ809-1 highlights the consequence: tight per-endpoint count caps in validators are now the primary defence on JSON endpoints, not the framework body limit. A future hardening cycle could narrow the body limit per-endpoint via `IRequestSizeLimitMetadata` on the `RouteHandlerBuilder` for non-upload endpoints — but this is hardening, not a finding.
## A06 — Vulnerable & Outdated Components
**Status**: PASS_WITH_WARNINGS (carry-over Low)
- See `dependency_scan_cycle8.md` for the full table. Summary:
- Cycle 8 added zero new packages and bumped zero existing packages.
- **D-AZ795-1** (cycle-7 carry-over): `FluentValidation` + `FluentValidation.DependencyInjectionExtensions` 12.0.0 → 12.1.1 hardening recommendation — still open. Cycle 8 did not bump.
- **D2-cy4** (cycle-4 carry-over): `Microsoft.NET.Test.Sdk 17.8.0` transitive `NuGet.Frameworks` Medium — test-runtime exposure only, still open.
## A07 — Identification and Authentication Failures
**Status**: PASS
- JWT validation parameters unchanged from cycle 4 (`AddSatelliteJwt`).
- No new auth-bypass paths introduced by cycle 8. The four new validators / filters cannot run for anonymous callers (see A01).
- The five new integration test files (`CreateRouteValidationTests`, `GetTileByLatLonValidationTests`, `RegionFieldRenameTests`, `RegionRequestValidationTests`, `UavUploadValidationTests`) all mint valid tokens via the shared `JwtTestHelpers.MintAuthenticated(...)` — proves the happy path is properly auth-gated and not relying on any test-only bypass.
- `RejectUnknownQueryParamsEndpointFilter` rejects malicious query keys (e.g. `?debug=1`, `?Authorization=…`) at the endpoint-filter layer — narrows the auth fingerprinting surface vs. the pre-cycle-8 behaviour where unknown keys silently bound to defaults.
## A08 — Software and Data Integrity Failures
**Status**: N/A (cycle 8)
- No CI/CD changes, no artifact-signing changes, no auto-update paths touched in cycle 8. The only CI/script-adjacent file modified is `scripts/run-performance-tests.sh` (wire-rename diff only — see `static_analysis_cycle8.md` § Test Code Review).
## A09 — Security Logging and Monitoring Failures
**Status**: PASS_WITH_WARNINGS (3 Lows — F-AZ810-1 new + F-AZ795-1 + F-AZ795-2 carry-over)
- `GlobalExceptionHandler` 5xx branch logs `Method`, `Path`, `correlationId`, and the exception object (via Serilog default). The 4xx branch does NOT log the exception (intentional, avoids noisy log signal from malformed-payload spam). Unchanged from cycle 7.
- The cycle-8 validators and the new `UavUploadValidationFilter` do NOT add any logging — they return `Results.ValidationProblem(...)` directly without any audit trail. This is consistent with cycle 7's `ValidationEndpointFilter<T>` posture (the response itself carries enough detail for the client to self-debug; the server logs would otherwise drown in 400s from typo-prone callers). Acceptable for the threat model.
- **F-AZ810-1** (NEW in cycle 8): `UavUploadValidationFilter.cs:82` echoes `JsonException.Message` to the client — same information-disclosure pattern as cycle-7 F-AZ795-1 in a NEW code path. Both surfaces leak `System.*` type names + parse positions. **Filed as Low**.
- **F-AZ795-1** (cycle-7 carry-over): `GlobalExceptionHandler.cs:108-117` — still open.
- **F-AZ795-2** (cycle-7 carry-over): `GlobalExceptionHandler.cs:88-93` — still open. Cycle 8 reduces practical reachability on the 4 newly-validated endpoints but doesn't eliminate it.
- **F-AZ810-2** (Informational, listed under A09 as a time-handling correctness concern with downstream logging/monitoring implications): `UavTileMetadata.CapturedAt` typed `DateTime` not `DateTimeOffset` — stale tiles could pass the freshness check in dev environments with non-UTC host TZ if the caller omits the `Z` suffix. Zero observable impact in UTC-deployed production. **Filed as Low / Informational**.
## A10 — Server-Side Request Forgery (SSRF)
**Status**: N/A (cycle 8)
- No URL-input fields, no outbound HTTP calls triggered by the cycle-8 surface. The pre-existing `GoogleMapsDownloaderV2` (outbound calls to Google Maps for tile fetches in the region/route processing paths) is not modified by cycle 8.
- The new `RejectUnknownQueryParamsEndpointFilter` works on the request's already-parsed query collection — no URL parsing, no outbound resolution.
## Cross-Reference with `security_approach.md`
The repo does not contain `_docs/00_problem/security_approach.md` (same as cycle 7). The OWASP review proceeds against the cycle-5 + cycle-6 + cycle-7 architectural decisions documented in `_docs/02_document/architecture.md` § 7 (Security Architecture) and § 9 (Strict wire-format validation at the API edge — added in cycle 7, extended in cycle 8). Cycle 8's input-validation completion cleanly extends those decisions; the F-AZ809-1 gap is the only deviation and is documented as such.
## Verdict (Phase 3)
**PASS_WITH_WARNINGS** — A04 (Insecure Design) is `PASS_WITH_WARNINGS` because of F-AZ809-1 (Medium); A06 + A09 are `PASS_WITH_WARNINGS` from the carry-over Lows + the new F-AZ810-1 Low. Every other OWASP category is PASS or N/A.
Per the skill's verdict-logic, Medium severity yields PASS_WITH_WARNINGS — not FAIL (FAIL is reserved for Critical or High). The cycle-8 threat model (authenticated callers only on every relevant endpoint) contains the Medium within an auth-gated surface, but F-AZ809-1 must be the highest-priority cycle-9 follow-up because the missing collection cap is the only inconsistency across the API's otherwise-uniform "cap every collection field" pattern, and the global Kestrel body limit (500 MiB) means the validator is the sole gate against an unbounded submission today. If a future feature exposes the route endpoint to an untrusted-tenant audience, this Medium would promote without warning.
+125
View File
@@ -0,0 +1,125 @@
# Security Audit Report (Cycle 8)
**Date**: 2026-05-23
**Scope**: Cycle-8 delta over the cycle-7 audit (`_docs/05_security/security_report_cycle7.md`). Cycle-8 surface = AZ-808 (region POST validator) + AZ-809 (route POST validator + per-point + per-polygon) + AZ-810 (UAV upload metadata validator + custom `UavUploadValidationFilter`) + AZ-811 (lat/lon GET validator + `RejectUnknownQueryParamsEndpointFilter`) + AZ-812 (region-API `Latitude`/`Longitude``Lat`/`Lon` wire rename — OSM convention).
**Trigger**: `/autodev` Step 14 (Security Audit) — feature cycle 8, post-implementation, post-test-spec-sync, post-docs-update.
**Verdict (cycle-8 delta)**: **PASS_WITH_WARNINGS** — 1 Medium (F-AZ809-1) + 2 new Lows (F-AZ810-1, F-AZ810-2) + 2 cycle-7 Low carry-overs (F-AZ795-1, F-AZ795-2) + 1 cycle-7 dependency Low carry-over (D-AZ795-1) + 1 cycle-4 dependency Medium carry-over (D2-cy4). Zero Critical / High.
**Verdict (cumulative)**: **PASS_WITH_WARNINGS** — 1 cycle-4 Medium (D2-cy4, test-runtime only) + 1 cycle-8 Medium (F-AZ809-1, auth-gated DoS) + multiple Lows.
## Summary
| Severity | Cycle 7 delta | Cycle 8 delta | Cumulative |
|----------|---------------|---------------|------------|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 0 |
| Medium | 0 | **1 NEW** (F-AZ809-1 — unbounded `geofences.polygons` enables an authenticated DoS on `POST /api/satellite/route`) | 2 (F-AZ809-1 cycle-8 + D2-cy4 cycle-4 carry — `Microsoft.NET.Test.Sdk 17.8.0` transitive `NuGet.Frameworks`; test-runtime exposure only) |
| Low | 3 (F-AZ795-1, F-AZ795-2, D-AZ795-1) | **2 NEW** (F-AZ810-1 — `JsonException.Message` echo in `UavUploadValidationFilter`; F-AZ810-2 — `DateTime` vs `DateTimeOffset` in `UavTileMetadata.CapturedAt`) | 5+ (3 cycle-7 carry + 2 cycle-8 new) |
## OWASP Top 10:2021 Assessment
| Category | Status (cycle-8 delta) | Findings |
|----------|------------------------|----------|
| A01 — Broken Access Control | PASS | — |
| A02 — Cryptographic Failures | N/A | No crypto in cycle 8 |
| A03 — Injection | PASS | — (cycle 8 strengthens — strict deserialization now backed by per-endpoint range checks at all four newly-validated endpoints) |
| A04 — Insecure Design | PASS_WITH_WARNINGS | F-AZ809-1 (Medium — unbounded `geofences.polygons` cap absence) |
| A05 — Security Misconfiguration | PASS | (Informational note re: global Kestrel 500 MiB body limit; not a finding) |
| A06 — Vulnerable Components | PASS_WITH_WARNINGS | D-AZ795-1 (Low — cycle-7 carry; FluentValidation 12.0.0 → 12.1.1 hardening still available) |
| A07 — Auth Failures | PASS | — (JWT unchanged; every cycle-8 endpoint retains `RequireAuthorization()`) |
| A08 — Data Integrity Failures | N/A | No CI/CD or artifact-signing surface in cycle 8 |
| A09 — Logging Failures | PASS_WITH_WARNINGS | F-AZ810-1 (Low — new instance of F-AZ795-1 pattern in `UavUploadValidationFilter`) + F-AZ795-1 + F-AZ795-2 (Low cycle-7 carry-overs) + F-AZ810-2 (Low / Informational time-handling) |
| A10 — SSRF | N/A | No URL-input fields in cycle 8 |
## Findings
| # | Severity | Category | Location | Title |
|---|----------|----------|----------|-------|
| F-AZ809-1 | **Medium** | Insecure Design (A04) | `SatelliteProvider.Api/Validators/CreateRouteRequestValidator.cs:72-82` | Unbounded `geofences.polygons` collection enables an authenticated DoS on `POST /api/satellite/route` |
| F-AZ810-1 | Low | Information Disclosure (A09) | `SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs:75-84` | `JsonException.Message` propagated to client in new code path (parallel to cycle-7 F-AZ795-1) |
| F-AZ810-2 | Low | Time-handling correctness (A09 adjacent) | `SatelliteProvider.Common/DTO/UavTileMetadata.cs:30` + `SatelliteProvider.Api/Validators/UavTileMetadataValidator.cs:52-60` | `UavTileMetadata.CapturedAt` typed `DateTime` not `DateTimeOffset` — freshness window drifts by host TZ in non-UTC dev environments |
| F-AZ795-1 | Low | Information Disclosure (A09) | `SatelliteProvider.Api/GlobalExceptionHandler.cs:108-117` | (cycle-7 carry) `JsonException.Message` propagated to client in 400 response — still open |
| F-AZ795-2 | Low | Information Disclosure (A09) | `SatelliteProvider.Api/GlobalExceptionHandler.cs:88-93` | (cycle-7 carry) Generic `BadHttpRequestException.Message` propagated as `Detail` for non-JSON 400 paths — still open |
| D-AZ795-1 | Low | Vulnerable & Outdated Components (A06) | NuGet | (cycle-7 carry) `FluentValidation` + `FluentValidation.DependencyInjectionExtensions` 12.0.0 → 12.1.1 (hardening release; no published CVE) — still open |
| D2-cy4 | Medium | Vulnerable & Outdated Components (A06) | NuGet (test runtime) | (cycle-4 carry) `Microsoft.NET.Test.Sdk 17.8.0` transitive `NuGet.Frameworks` — test-runtime exposure only — still open |
### Finding Details
**F-AZ809-1: Unbounded `geofences.polygons` collection enables an authenticated DoS** (Medium / A04 — Insecure Design)
- Location: `SatelliteProvider.Api/Validators/CreateRouteRequestValidator.cs:72-82`
- Description: The cycle-8 `CreateRouteRequestValidator` chains `RuleForEach(req => req.Geofences!.Polygons).SetValidator(new GeofencePolygonValidator())` but enforces only `NotEmpty` on the collection — no upper bound on `Geofences.Polygons.Count`. The sibling `Points` collection IS capped at 500; the global `KestrelServerOptions.Limits.MaxRequestBodySize` is set to 500 MiB to accommodate the UAV upload endpoint and applies to every route. With ~90 bytes per minimum-shape polygon JSON, an authenticated caller can submit ~5.8 million polygons in a single request; each invalid polygon yields ~3 `ValidationFailure` allocations, for ~17 million `ValidationFailure` objects in worst case — sufficient to saturate the LOH and trigger a full GC pass.
- Impact: Medium. Auth-gated (`RequireAuthorization()` at `Program.cs:267`) — only tenant operators with a valid JWT can reach the endpoint. Within the cycle-8 threat model this is contained; promotion risk if the route endpoint is later exposed to an untrusted-tenant audience.
- Remediation: Add `Must(p => p is null || p.Count <= MaxPolygons)` with a defensible upper bound. Reasonable cap candidates: `50` (consistent with the historical use case — geofence rectangles for AOI restriction); `500` (consistent with the sibling `Points` cap on the same DTO). The pattern across the API is "cap every collection field" — one collection slipped past in cycle 8.
- Status: filed for cycle 9 as the **highest-priority** follow-up under AZ-809 (or a sibling child ticket).
**F-AZ810-1: `JsonException.Message` propagated to client in `UavUploadValidationFilter`** (Low / A09 — Information Disclosure)
- Location: `SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs:75-84` (the `catch (JsonException ex)` block of the metadata parse)
- Description: The cycle-8 `UavUploadValidationFilter` echoes the raw `JsonException.Message` directly to the client as the value of `errors["metadata"]` — the SAME information-disclosure pattern as cycle-7 F-AZ795-1 (in `GlobalExceptionHandler.cs`), introduced in a second code path that bypasses the global exception handler (the filter intercepts and returns `Results.ValidationProblem(...)` directly).
- Impact: Low. Auth-gated (`.RequireAuthorization(SatellitePermissions.UavUploadPolicy)` at `Program.cs:238`) — only callers holding a valid JWT *with* the `GPS` permission claim can reach the filter. Leaks `System.*` type names + JSON parse positions — content already inferable from the OpenAPI spec.
- Remediation: Sanitise the response message to a generic string (e.g. `"`metadata` could not be parsed as JSON. See the server log for details."`) while continuing to log the raw `ex.Message` server-side under the request's `correlationId`. Lock-step with F-AZ795-1's remediation. Add an integration-test assertion in `UavUploadValidationTests` that no `System.*` substring appears in the response body's `errors[]` value.
- Status: filed for cycle 9 as a child of the same F-AZ795-1 ticket — both call sites get the sanitiser at once.
**F-AZ810-2: `UavTileMetadata.CapturedAt` typed `DateTime` not `DateTimeOffset`** (Low / Informational — Time-handling correctness)
- Location: `SatelliteProvider.Common/DTO/UavTileMetadata.cs:30` + `SatelliteProvider.Api/Validators/UavTileMetadataValidator.cs:52-60`
- Description: When `System.Text.Json` deserializes an ISO-8601 string without a `Z` / `+HH:MM` suffix into a `DateTime`, `Kind = Unspecified`. `DateTime.ToUniversalTime()` treats `Unspecified` values as **local time** — the freshness comparison drifts by the host's timezone offset. In a UTC-deployed prod container the local offset is zero; in a developer's local environment with `TZ=Europe/Kyiv` (UTC+02:00 or +03:00) a `capturedAt` value of `"2026-05-22T12:00:00"` would be treated as `2026-05-22T10:00:00Z` in summer, shifting the freshness window by the offset.
- Impact: Low / Informational. Zero observable impact in the supported deployment configuration (Docker containers run in UTC; the cycle-2 `docker-compose.yml` does not override `TZ`). No security exploit: an attacker cannot force the server's TZ. Reported as a defence-in-depth correctness item.
- Remediation (two options): (1) Change the DTO type to `DateTimeOffset` + custom JSON converter rejecting offset-less ISO-8601. (2) Add a FluentValidation rule rejecting `DateTime.Kind == DateTimeKind.Unspecified`. Option 2 is the minimum behaviour-preserving fix; Option 1 is correct for v2.0 of `uav-tile-upload.md`.
- Status: filed for cycle 9 as Low; not release-blocking — every documented client and every integration-test fixture sends the `Z` suffix.
**F-AZ795-1 / F-AZ795-2 / D-AZ795-1**: see `_docs/05_security/security_report_cycle7.md` § "Finding Details" — all three carry-overs are unchanged at cycle-8 tip. The cycle-8 F-AZ810-1 finding is a NEW instance of the F-AZ795-1 pattern in a different code path; both surfaces must be sanitised in lock-step.
**D2-cy4**: see `_docs/05_security/dependency_scan_cycle4.md` — unchanged at cycle-8 tip. Test-runtime exposure only.
## Dependency Vulnerabilities
| Package | CVE | Severity | Fix Version | Status |
|---------|-----|----------|-------------|--------|
| FluentValidation 12.0.0 | — (hardening only) | Low | 12.1.1 | D-AZ795-1 cycle-7 carry — still open |
| FluentValidation.DependencyInjectionExtensions 12.0.0 | — (hardening only) | Low | 12.1.1 | D-AZ795-1 cycle-7 carry — still open (same item) |
| Microsoft.NET.Test.Sdk 17.8.0 (transitive `NuGet.Frameworks`) | — (cycle-4 carry) | Medium | TBD (next Test SDK refresh cycle) | D2-cy4 cycle-4 carry — still open |
No new dependency vulnerabilities in cycle 8. Cycle 8 added zero new packages.
## Recommendations
### Immediate (Critical/High)
None.
### Short-term (Medium)
1. **Add `MaxPolygons` cap to `CreateRouteRequestValidator`** (F-AZ809-1) — single `.Must(...)` chain on `geofences.polygons`. Recommended `MaxPolygons = 50` (use-case-driven) or `500` (sibling-collection-consistent — matches `Points` cap on the same DTO). One-line code change + matching unit test. **Highest-priority cycle-9 follow-up**.
### Long-term (Low / Hardening)
2. **Sanitise client-visible 400 messages** (F-AZ795-1 + F-AZ795-2 + F-AZ810-1) — single sanitiser applied to BOTH `GlobalExceptionHandler.WriteClientErrorAsync` AND `UavUploadValidationFilter.InvokeAsync`. Pre-existing-class instance in `UavTileUploadHandler.cs:80` must also be sanitised at the same time so the defence-in-depth path doesn't continue leaking. Add matching test assertions in `TileInventoryValidationTests` + `UavUploadValidationTests`. Estimated 2 hours. File as a small follow-up child of AZ-795 (epic).
3. **Bump FluentValidation 12.0.0 → 12.1.1** (D-AZ795-1) — single `.csproj` edit + regression test pass. No API surface change in 12.0.0 → 12.1.1 per the upstream changelog.
4. **Add `DateTimeKind.Unspecified` rejection rule to `UavTileMetadataValidator`** (F-AZ810-2 Option 2) — single `.Must(capturedAt => capturedAt.Kind != DateTimeKind.Unspecified)` rule. Doesn't break any documented client (all examples send `Z` suffix). One-line code change + matching unit test.
5. **Per-endpoint Kestrel body-size narrowing** (informational hardening) — apply `.WithMetadata(new RequestSizeLimitMetadata { MaxRequestBodySize = … })` to each non-upload endpoint so the 500 MiB global is constrained for JSON endpoints (e.g. 1 MiB for region/route, 10 MiB for inventory). Reduces F-AZ809-1's exploit surface even after the polygon cap is applied. Tracker child of AZ-795 or a standalone hardening task.
### Pre-existing-class follow-up (Low / Informational)
6. **Drop the `try/catch (ArgumentException)` block in the `CreateRoute` handler** (`Program.cs:387-399`) — pre-cycle-8 inconsistency that cycle 8 reduced the reachability of but did not eliminate. The validator now intercepts most `ArgumentException` cases; the catch leaks `ex.Message` in a non-conformant `{error: string}` shape. Fold into the same cycle-9 follow-up as #2 above.
### Cumulative reminders (carry-overs)
- **D2-cy4** — `Microsoft.NET.Test.Sdk 17.8.0` transitive `NuGet.Frameworks` Medium, test-runtime only. Owned by the next Test SDK refresh.
## Cycle-8 Architectural Wins
The audit specifically wants to record four improvements introduced this cycle:
1. **Per-endpoint input-validation completion** — the AZ-795 strict-validation epic introduced in cycle 7 reached **100% coverage** in cycle 8. Every public-facing input endpoint (region POST, route POST, lat/lon GET, inventory POST, UAV upload) now runs a validator behind the same `error-shape.md` v1.0.0 contract. Future drift visibility is high; the architecture doc's coverage table is now complete.
2. **Three approved validation paths formalised** — the architecture doc (`_docs/02_document/architecture.md` § 9) now codifies three approved validation paths: `WithValidation<T>()` for JSON-body endpoints; `WithValidation<T>()` + `RejectUnknownQueryParamsEndpointFilter` for query-string endpoints; custom `IEndpointFilter` for non-standard wire formats. Future endpoints have a clear pattern to follow; an audit can verify any new endpoint via the chosen-path criterion.
3. **Wire-format normalisation under strict mode** (AZ-812) — `Latitude` / `Longitude``Lat` / `Lon` on the region endpoint aligns with OSM convention used everywhere else in the API. Under `UnmappedMemberHandling.Disallow` (cycle 7 global), legacy `Latitude` / `Longitude` payloads are now rejected at the deserializer with a structured 400 — no silent coercion, no compatibility-shim layer. This is a hard breaking change for any consumer still on the old shape, but it is intentional and documented as the post-cycle-8 contract.
4. **Defence-in-depth retained for the UAV upload handler**`UavTileUploadHandler.HandleAsync` retains every envelope check it had pre-cycle-8 (`metadata` presence, JSON parse, `Items.Count == 0`, `Items.Count != files.Count`, `Items.Count > MaxBatchSize`). The new `UavUploadValidationFilter` is *additive* — it intercepts the primary endpoint path but does not weaken the service-layer guard for direct callers (e.g. unit tests). The two layers' shape choices are mutually compatible.
## Verdict
**PASS_WITH_WARNINGS** — 1 Medium (F-AZ809-1, auth-gated DoS on the route endpoint via unbounded `geofences.polygons`) + 2 cycle-8 Lows + 3 cycle-7 Low carry-overs + 1 cycle-4 Medium carry-over (test-runtime only). Zero Critical / High.
Per the skill's verdict-logic, Medium severity yields PASS_WITH_WARNINGS. Cycle 8 is **safe to release** within its documented threat model (authenticated callers only on every endpoint). F-AZ809-1 must be the highest-priority cycle-9 follow-up because the missing collection cap is the only inconsistency across the API's otherwise-uniform "cap every collection field" pattern, and the global Kestrel body limit means the validator is the sole gate against an unbounded submission today.
Cumulative posture: PASS_WITH_WARNINGS (1 cycle-4 Medium carry-over + 1 cycle-8 Medium + multiple Lows). No regression of the cycle-7 PASS_WITH_WARNINGS posture; cycle 8 added one new Medium but completed an architecturally important hardening epic.
+243
View File
@@ -0,0 +1,243 @@
# Static Analysis (Cycle 8)
**Date**: 2026-05-23
**Mode**: Delta scan
**Scope**: Source code introduced or changed by AZ-808 + AZ-809 + AZ-810 + AZ-811 + AZ-812. Cycle-7 baseline (`static_analysis_cycle7.md`) remains authoritative for the AZ-794 / AZ-795 / AZ-796 surface; this scan only audits the cycle-8 delta.
**Files in scope** (40 changed source files; non-test detail):
- **API — 11 files**
- `SatelliteProvider.Api/Program.cs` (DI + endpoint wiring deltas — `WithValidation<RequestRegionRequest>`, `WithValidation<CreateRouteRequest>`, `WithValidation<GetTileByLatLonQuery>`, `AddEndpointFilter<UavUploadValidationFilter>`, `AddTransient<UavUploadValidationFilter>`, `RejectUnknownQueryParamsEndpointFilter` registration)
- `SatelliteProvider.Api/DTOs/GetTileByLatLonQuery.cs` (new — record with nullable bindings)
- `SatelliteProvider.Api/Validators/RegionRequestValidator.cs` (new — AZ-808)
- `SatelliteProvider.Api/Validators/CreateRouteRequestValidator.cs` (new — AZ-809)
- `SatelliteProvider.Api/Validators/GeofencePolygonValidator.cs` (new — AZ-809)
- `SatelliteProvider.Api/Validators/RoutePointValidator.cs` (new — AZ-809)
- `SatelliteProvider.Api/Validators/UavTileMetadataValidator.cs` (new — AZ-810)
- `SatelliteProvider.Api/Validators/UavTileBatchMetadataPayloadValidator.cs` (new — AZ-810)
- `SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs` (new — AZ-810)
- `SatelliteProvider.Api/Validators/GetTileByLatLonQueryValidator.cs` (new — AZ-811)
- `SatelliteProvider.Api/Validators/RejectUnknownQueryParamsEndpointFilter.cs` (new — AZ-811)
- `SatelliteProvider.Api/Swagger/ParameterDescriptionFilter.cs` (AZ-811 — added `lat`/`lon`/`zoom` description entries)
- **Common — 6 DTO files** (AZ-808/809/810/812 — `[JsonRequired]` annotations + AZ-812 rename)
- `SatelliteProvider.Common/DTO/RequestRegionRequest.cs`
- `SatelliteProvider.Common/DTO/CreateRouteRequest.cs`
- `SatelliteProvider.Common/DTO/GeofencePolygon.cs`
- `SatelliteProvider.Common/DTO/GeoPoint.cs`
- `SatelliteProvider.Common/DTO/RoutePoint.cs`
- `SatelliteProvider.Common/DTO/UavTileMetadata.cs`
- **Test code** (reviewed for fixture-only secrets + auth-bypass patterns) — 8 new validator unit tests + 4 new integration test files + several modified integration test helpers.
- **Shell scripts** (reviewed for embedded secrets + unsafe sequences) — 4 new probe scripts (`probe_latlon_validation.sh`, `probe_region_validation.sh`, `probe_route_validation.sh`, `probe_upload_validation.sh`) + 1 modified perf script (`run-performance-tests.sh`, wire-rename diff only).
**Method**: Read each new file end-to-end; targeted `Grep` for injection / hardcoded-credential / unsafe-API patterns (`password|secret|api.?key|bearer|token` over `SatelliteProvider.Api/Validators` returned **0 matches**); diff-review of every DTO change vs. its cycle-7 baseline; trace each `[JsonRequired]` chain through to its FluentValidation rule.
## Findings
### F-AZ809-1 — Unbounded `geofences.polygons` collection enables an authenticated DoS via `CreateRouteRequest` (Medium / A04 — Insecure Design)
- **Location**: `SatelliteProvider.Api/Validators/CreateRouteRequestValidator.cs:72-82` (`When(req => req.Geofences is not null, () => …)`).
- **Description**: The `CreateRouteRequestValidator` chains a `RuleForEach(req => req.Geofences!.Polygons).SetValidator(new GeofencePolygonValidator())` block but only enforces `NotEmpty` on the collection. There is **no upper bound on `Geofences.Polygons.Count`**. The parent collection `Points` IS capped (`MaxPoints = 500` at line 27) — the polygons collection is the only nested list-bearing field on this endpoint without a cap.
- **Code**:
```csharp
When(req => req.Geofences is not null, () =>
{
RuleFor(req => req.Geofences!.Polygons)
.NotNull().WithMessage("`geofences.polygons` is required when `geofences` is present.")
.NotEmpty().WithMessage("`geofences.polygons` must contain at least 1 polygon when `geofences` is present.")
.OverridePropertyName("geofences.polygons");
RuleForEach(req => req.Geofences!.Polygons)
.SetValidator(new GeofencePolygonValidator())
.OverridePropertyName("geofences.polygons");
});
```
- **Exploit math** (worst-case envelope under current configuration):
- `KestrelServerOptions.Limits.MaxRequestBodySize = uavBatchBodyLimit = MaxBatchSize × MaxBytes = 100 × 5 MiB = 500 MiB` (set globally in `Program.cs:41-43` for the UAV endpoint; the same limit applies to every endpoint by default because Kestrel exposes a per-server, not per-endpoint, default).
- Minimum JSON polygon footprint: `{"northWest":{"lat":1.0,"lon":2.0},"southEast":{"lat":3.0,"lon":4.0}}` ≈ 90 bytes including the comma separator.
- Theoretical maximum polygon count in a single 500 MiB request: `500 MiB / 90 bytes ≈ 5.8 million polygons`.
- With invalid polygons (e.g. `lat` out of range), `GeofencePolygonValidator` adds 2 corner-range `ValidationFailure` objects + 1 cross-field NW-of-SE failure = ~3 failures per polygon. Worst-case allocation: ~17 million `ValidationFailure` instances + the matching error-map keys before the filter formats the `ValidationProblemDetails` body.
- **Impact**: Medium. A single authenticated authorized request can saturate the LOH (large object heap) and trigger a full GC pass on the API process. The endpoint is `RequireAuthorization()`-gated (line 251 in `Program.cs`) so anonymous callers cannot reach the validator — the attacker must hold a valid JWT. But once authenticated, a single malformed request degrades the service for every other tenant operator until GC reclaims the heap. Repeatable. No data leak, no privilege escalation; pure availability impact.
- **OWASP mapping**: A04 — Insecure Design (missing rate/size limit on a collection-bearing input field). Adjacent to A05 (Security Misconfiguration — the global Kestrel limit was set for the UAV endpoint in cycle 5 but applies to every endpoint).
- **Remediation**: Add `Must(p => p is null || p.Count <= MaxPolygons)` with a defensible upper bound. Reasonable cap candidates:
- `MaxPolygons = 50` (consistent with the historical use case — a route is unlikely to need more than a handful of geofence rectangles for AOI restriction).
- `MaxPolygons = MaxPoints = 500` (consistent with the sibling `Points` cap on the same DTO).
- The matching `cumulative_review_batches_01-04_cycle8_report.md` already enumerates `points.Count <= 500` (route), `items.Count <= 100` (UAV upload), `coords.Count <= 1000` (tile inventory, cycle 7) as bounded — the missing entry for `geofences.polygons` is the one inconsistency.
- **Status**: open — file as a cycle-9 follow-up under AZ-809 (or a sibling child ticket). Not release-blocking for cycle 8 itself: exploitation requires an authenticated caller with a valid GPS-permission-less JWT — the same threat model already had access to the cycle-7-pre-existing `inventory.Tiles.Count` cap, so the marginal new exposure is moderate, not catastrophic. But it MUST be fixed before any untrusted-tenant exposure is added to the route endpoint.
### F-AZ810-1 — `JsonException.Message` propagated to client in `UavUploadValidationFilter` (Low / A09 — Information Disclosure)
- **Location**: `SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs:75-84` (the `catch (JsonException ex)` block of the metadata parse).
- **Code**:
```csharp
catch (JsonException ex)
{
return Results.ValidationProblem(new Dictionary<string, string[]>
{
[MetadataField] = new[] { $"`metadata` could not be parsed as JSON: {ex.Message}" },
});
}
```
- **Description**: The cycle-8 `UavUploadValidationFilter` echoes the raw `JsonException.Message` directly to the client as the value of `errors["metadata"]`. This is the **same information-disclosure pattern as cycle-7 F-AZ795-1** (in `GlobalExceptionHandler.cs:108-117`), introduced in a *second* code path that bypasses the global exception handler (the filter intercepts and returns `Results.ValidationProblem(...)` directly). Cycle-7 F-AZ795-1 remains open; cycle 8 adds a second instance of the same pattern that would also need to be sanitised by the same remediation.
- **Impact**: Low. Same severity classification as F-AZ795-1. Auth-gated (`.RequireAuthorization(SatellitePermissions.UavUploadPolicy)` at `Program.cs:238`) — only callers holding a valid JWT *with* the `GPS` permission claim can reach the filter and trigger this path. The leaked content (type names, parse positions, `System.Text.Json` fingerprint) is already inferable from the OpenAPI spec; the new path narrows the attack surface for an authenticated GPS-permissioned operator but does not expose secrets, PII, or pivot vectors.
- **Remediation**: Sanitise the response message to a generic string (e.g. `"`metadata` could not be parsed as JSON. See the server log for details."`) while continuing to log the raw `ex.Message` server-side under the request's `correlationId`. Best done in tandem with F-AZ795-1's remediation since both paths surface the same exception class through the same response shape. Add an integration-test assertion in `UavUploadValidationTests` that no `System.*` substring appears in the response body's `errors[]` value, mirroring the cycle-7 gap noted in `static_analysis_cycle7.md` § F-AZ795-1 *Test coverage gap*.
- **Status**: open — file as a cycle-9 follow-up child of the same F-AZ795-1 ticket so both call sites get the sanitiser at once.
### F-AZ810-2 — `UavTileMetadata.CapturedAt` typed `DateTime` not `DateTimeOffset` (Low / Informational — Time-handling correctness)
- **Location**: `SatelliteProvider.Common/DTO/UavTileMetadata.cs:30` + `SatelliteProvider.Api/Validators/UavTileMetadataValidator.cs:52-60`.
- **Code**:
```csharp
// UavTileMetadata.cs:30
[JsonRequired]
public DateTime CapturedAt { get; init; }
// UavTileMetadataValidator.cs:52-60
RuleFor(m => m.CapturedAt)
.Must(capturedAt => capturedAt.ToUniversalTime() <= tp.GetUtcNow().UtcDateTime.AddSeconds(futureSkewSeconds))
.WithMessage($"`capturedAt` must be within {futureSkewSeconds}s of the current time (no future-dated tiles).")
.Must(capturedAt => capturedAt.ToUniversalTime() >= tp.GetUtcNow().UtcDateTime.AddDays(-maxAgeDays))
.WithMessage($"`capturedAt` must be within the last {maxAgeDays} days.");
```
- **Description**: When `System.Text.Json` deserializes an ISO-8601 string into a `DateTime`, the resulting `DateTime.Kind` depends on the string's offset suffix:
- `"2026-05-22T12:00:00Z"` → `Kind = Utc`.
- `"2026-05-22T12:00:00+03:00"` → `Kind = Local` after normalization to local time.
- `"2026-05-22T12:00:00"` (no suffix) → `Kind = Unspecified`.
For `Kind = Unspecified`, `DateTime.ToUniversalTime()` treats the value as **local time**, which means the freshness comparison drifts by the host's timezone offset. In a UTC-deployed prod container (Linux `TZ=UTC`), the local offset is zero and there is no observable impact. In a developer's local environment (e.g. `TZ=Europe/Kyiv` = UTC+02:00 or +03:00), a `capturedAt` value of `"2026-05-22T12:00:00"` would be treated as `2026-05-22T10:00:00Z` (in summer), shifting the freshness window by the offset.
- **Impact**: Low / Informational. Zero observable impact in the supported deployment configuration (Docker containers run in UTC by default; the cycle-2 `docker-compose.yml` does not override `TZ`). The freshness rule could be loosely-bounded by the same offset (in either direction) in a dev environment with a non-UTC host TZ. No security exploit: an attacker cannot force the server's TZ; they can only submit `capturedAt` values, and the server's UTC-deployed configuration treats those deterministically.
- **OWASP mapping**: A09 — Security Logging and Monitoring Failures (adjacent — a stale freshness window could mask an out-of-band attack on the upload endpoint).
- **Remediation** (two options):
1. **Strict**: Change the DTO type to `DateTimeOffset` so the parsed value always carries an explicit offset, and add a JSON converter that rejects offset-less ISO-8601 strings at deserialization (`JsonConverterAttribute` pointing at a custom converter that checks for the `Z` / `+HH:MM` suffix and throws `JsonException` if missing — surfaces as a 400 via `GlobalExceptionHandler`).
2. **Lenient**: Add a FluentValidation rule that rejects `DateTime.Kind == DateTimeKind.Unspecified` so the caller must supply a tz-aware ISO-8601 string. Keeps the DTO shape; doesn't break clients that already send `"Z"` suffix.
Option 2 is the minimum behaviour-preserving fix. Option 1 is correct for a v2.0 of `uav-tile-upload.md`.
- **Status**: open — file as a Low cycle-9 follow-up. Not release-blocking for cycle 8 because every documented client in `uav-tile-upload.md` example payloads and every integration-test fixture sends the `Z` suffix.
## Pattern Sweep — Cycle-8 Delta
### Injection (SQL / Command / XSS / Template)
| Pattern | Result |
|---------|--------|
| `string.Format`, interpolation `$"..."`, or concatenation feeding into a Dapper / Npgsql command in the new files | None. The 9 new files in `SatelliteProvider.Api/Validators/` and `SatelliteProvider.Api/DTOs/` do not touch the data layer. |
| `Process.Start`, `subprocess`, `eval`, `Invoke-Expression`, raw `system()` | None. |
| User-input echoed into HTML (XSS) | None. The API returns JSON only. The cycle-8 `RejectUnknownQueryParamsEndpointFilter` echoes the offending parameter name back as a JSON string value — `System.Text.Json` performs canonical JSON escaping of control characters; no HTML injection vector. |
| Template injection (Razor / Liquid / etc.) | None. No templating in the new files. |
### Authentication & Authorization
| Pattern | Result |
|---------|--------|
| Hardcoded credentials, secrets, API keys | `Grep -i 'password|secret|api.?key|bearer|token'` against `SatelliteProvider.Api/Validators/` returned **0 matches**. The new integration test files (`CreateRouteValidationTests`, `GetTileByLatLonValidationTests`, `RegionRequestValidationTests`, `UavUploadValidationTests`, `RegionFieldRenameTests`) reuse the runner-side `JwtTestHelpers.MintAuthenticated(...)` helper for token attachment; no hardcoded secret material. |
| Missing `.RequireAuthorization()` on a public endpoint | Every cycle-8 endpoint binding in `Program.cs` retains `.RequireAuthorization()`: `RequestRegion` (line 251), `CreateRoute` (267), `GetTileByLatLon` (213), `UploadUavTileBatch` (238 — additionally requires the `GPS` permission claim via `SatellitePermissions.UavUploadPolicy`). |
| Validator running before auth check | No. ASP.NET Core endpoint filters run AFTER the routing layer's authorization middleware (`app.UseAuthorization()` at `Program.cs:206`). All four cycle-8 filters (`ValidationEndpointFilter<RequestRegionRequest>`, `ValidationEndpointFilter<CreateRouteRequest>`, `RejectUnknownQueryParamsEndpointFilter` + `ValidationEndpointFilter<GetTileByLatLonQuery>`, `UavUploadValidationFilter`) cannot run for anonymous callers — the 401 short-circuit fires first. |
| Permission/policy regression | `RequiresGpsPermission` on `/api/satellite/upload` retained. `RejectUnknownQueryParamsEndpointFilter` does NOT call `RequireAuthorization()` itself — it relies on the endpoint chain's existing `.RequireAuthorization()` (line 213). Correct. |
### Cryptographic Failures
| Pattern | Result |
|---------|--------|
| Weak hash (MD5 / SHA1) used for passwords or signatures | None in cycle 8. Pre-existing UUIDv5 SHA-1 surface (`Uuidv5.Create`) is untouched. |
| New crypto material introduced | None. Cycle 8 has no cryptography. |
| Plaintext transmission | API listens on `https://+:8080` with ALPN (cycle-6 baseline, unchanged). |
### Data Exposure
| Pattern | Result |
|---------|--------|
| Sensitive data in logs | The new validators and filters do NOT log the request body. `GlobalExceptionHandler` 5xx branch logs `Method`, `Path`, `correlationId`, and the exception object (pre-existing). Cycle 8 doesn't widen this. |
| Sensitive fields in API responses | F-AZ810-1 (`JsonException.Message` echo) above is the only new echo-back path. No password hashes, no PII (the four endpoints carry only metadata: geospatial coords, integer IDs, timestamps). The `RejectUnknownQueryParamsEndpointFilter` echoes the offending parameter NAME (not value) back — and the list of allowed param names IS already in the OpenAPI spec, so no new fingerprinting surface. |
| Debug endpoints in production | Swagger is gated by `app.Environment.IsDevelopment()` (unchanged). |
| Secrets in version control | `.env*` files are gitignored. The 4 new probe scripts (`probe_*_validation.sh`) all use `${JWT:?…}`-style env-var reads with explicit "set JWT env var" error guards; no embedded credentials. |
### Insecure Deserialization
| Pattern | Result |
|---------|--------|
| `Pickle` / `BinaryFormatter` / unsafe XML / `JsonConvert.DeserializeObject<T>` with `TypeNameHandling.All` | None in cycle 8. `UavUploadValidationFilter.cs:73` uses `JsonSerializer.Deserialize<UavTileBatchMetadataPayload>(...)` with the global `JsonSerializerOptions` from `IOptions<JsonOptions>` — which has `UnmappedMemberHandling.Disallow` set by cycle-7 `ConfigureHttpJsonOptions`. ✓ |
| Unbounded collection sizes | **F-AZ809-1 above** — `geofences.polygons` lacks a cap. Other cycle-8 list fields ARE capped: `Points.Count <= 500` (`CreateRouteRequestValidator:60-61`), `Items.Count <= MaxBatchSize=100` (`UavTileBatchMetadataPayloadValidator:27-28`). Cycle-7 `Tiles.Count <= 5000` / `LocationHashes.Count <= 5000` unchanged. The framework-level `MaxRequestBodySize` bound is in force for all paths — but it's set at 500 MiB to accommodate the UAV upload endpoint, which makes per-endpoint count caps the primary defence. |
### Integer Overflow / Bounded Math
The cycle-8 validators do not perform any integer arithmetic beyond `Math.Max(options.ValueLengthLimit, uavQuality.MaxBatchSize * 512)` (`Program.cs:61` — `100 * 512 = 51_200`, no overflow possible). No left-shifts, no power-of-two computations on caller-controlled values. ✓
### ReDoS / Algorithmic Complexity
Cycle-8 validation rules are all O(1) per entry × N entries with bounded N (with the F-AZ809-1 exception above):
| Validator | Per-entry cost | Total cost (worst case) |
|-----------|----------------|-------------------------|
| `RegionRequestValidator` | O(1) — 5 range checks on scalar fields | O(1) |
| `CreateRouteRequestValidator` (root + cross-field invariant) | O(1) base + O(N points) + O(P polygons unbounded) | **O(N × P)** with unbounded P — see F-AZ809-1 |
| `RoutePointValidator` (via `RuleForEach`) | O(1) — 2 range checks | O(500) bounded by parent `MaxPoints` |
| `GeofencePolygonValidator` (via `RuleForEach`) | O(1) — 2 corner null + 4 range + 2 cross-field | O(P) where P is unbounded — see F-AZ809-1 |
| `UavTileMetadataValidator` (via `RuleForEach`) | O(1) — 4 range checks + 2 freshness | O(100) bounded by `MaxBatchSize` |
| `UavTileBatchMetadataPayloadValidator` (root) | O(1) | O(1) |
| `GetTileByLatLonQueryValidator` | O(1) — 3 cascaded NotNull + range | O(1) |
| `RejectUnknownQueryParamsEndpointFilter` | O(K log K) where K = caller's query-param count | O(K) bounded by Kestrel's per-request header/query limits (default 32 KB of query string) |
No regex, no recursion, no nested loops with caller-controlled bounds (except F-AZ809-1).
## `UavUploadValidationFilter` Defence-in-Depth Verification
The filter intercepts the primary multipart-upload code path. The pre-existing `UavTileUploadHandler.HandleAsync` retains the SAME envelope checks (`SatelliteProvider.Services.TileDownloader/UavTileUploadHandler.cs:64-141`) — confirming the filter is **defence-in-depth on top of** the handler's checks, not a replacement:
| Check | `UavUploadValidationFilter` (filter layer) | `UavTileUploadHandler` (handler layer — defence-in-depth) |
|-------|--------------------------------------------|-----------------------------------------------------------|
| `metadata` form field present + non-empty | Line 62-68 | Line 68-71 (`string.IsNullOrWhiteSpace`) |
| `metadata` parses as JSON | Line 71-84 (catches `JsonException`) | Line 74-81 (catches `JsonException`) — **same pattern, same `ex.Message` echo (pre-existing parallel to F-AZ810-1)** |
| `metadata.items` non-null + non-empty | Line 86-92 (`payload is null`) + `UavTileBatchMetadataPayloadValidator:25-26` (`NotNull`/`NotEmpty`) | Line 83-86 (`Items.Count == 0`) |
| `metadata.items.Count == files.Count` | Line 105-118 | Line 88-91 |
| `metadata.items.Count <= MaxBatchSize` | `UavTileBatchMetadataPayloadValidator:27-28` | Line 93-96 |
The handler's envelope checks are still reachable by any caller invoking `IUavTileUploadHandler.HandleAsync(...)` directly (e.g. unit tests, or a future programmatic flow). Cycle 8 left them intact — correct ✓. The duplicated `JsonException.Message` echo in the handler (line 80) is a pre-existing-class instance of the same Low finding as F-AZ810-1; the remediation MUST sanitise both call sites in lock-step or the defence-in-depth path will continue to leak.
## Pre-existing-Surface Inconsistency Noted (NOT a cycle-8 regression)
`Program.cs:387-399` — the `CreateRoute` handler still wraps its body in `try { ... } catch (ArgumentException ex) { return Results.BadRequest(new { error = ex.Message }); }`. This is a pre-cycle-8 inconsistency: the response shape is the unstructured `{error: string}` object, NOT the `ValidationProblemDetails` shape mandated by `error-shape.md` v1.0.0. Cycle 8 added `WithValidation<CreateRouteRequest>` at line 268 which intercepts most `ArgumentException` cases — so the `catch` block is now largely dead code, but it WILL fire if `IRouteService.CreateRouteAsync` itself throws `ArgumentException` (e.g. a future internal validation rule). When it fires, the response leaks `ex.Message` in a non-conformant shape.
- **Severity**: Low / Informational. Pre-existing inconsistency; cycle 8 reduced its reachable surface but did not eliminate it.
- **Remediation**: Drop the `try/catch` block now that the validator covers the same cases at the filter layer; let unexpected `ArgumentException` propagate to `GlobalExceptionHandler` for uniform 400 + sanitised `ValidationProblemDetails` (or 500 with a `correlationId` if the cause is internal). One-line edit; recommend folding into the same cycle-9 follow-up as F-AZ795-1 / F-AZ810-1 since all three converge on the same error-shape consistency improvement.
## Test Code Review
### `SatelliteProvider.Tests/Validators/*ValidatorTests.cs` (8 new files)
- Pure CPU; no I/O, no network, no file system, no DB.
- All inputs constructed inline. No fixture-file reads, no hardcoded JWTs.
- All test files share the cycle-7 `ValidatorTestModuleInitializer.cs` (`[ModuleInitializer]` → `GlobalValidatorConfig.ApplyOnce()` at test-assembly load) — single source of truth for the camelCase property resolver. No test drift risk.
- ✓ No findings.
### `SatelliteProvider.IntegrationTests/{CreateRouteValidationTests,GetTileByLatLonValidationTests,RegionFieldRenameTests,RegionRequestValidationTests,UavUploadValidationTests}.cs` + modified helpers
- All five new files use the runner-side `JwtTestHelpers.MintAuthenticated(...)` to attach a Bearer token, mirroring cycle 7's pattern. No hardcoded secret material; the token's signing secret comes from the `JWT_SECRET` env var (32+ bytes, dev-only in `docker-compose.tests.yml`).
- Test inputs use raw `HttpRequestMessage` with hand-built JSON strings — exercises the exact wire shape the validator + deserializer see in production. Each file covers the cycle-8 negative cases for its endpoint.
- `ProblemDetailsAssertions.cs` was extended (additive) — no removed assertions. The cycle-7 `error-shape.md` Inv-2 / Inv-4 contract assertions all still apply.
- `UavUploadTests.cs` + `UavUploadValidationTests.cs` both clamp their fixture-generated coordinates into non-overlapping OSM-valid sub-ranges (cycle-8 hotfix commit `b763da3` per `_docs/03_implementation/batch_04_cycle8_report.md` § AC-9) — a test-data correctness fix, not a security finding.
- ✓ No findings.
### `scripts/probe_{latlon,region,route,upload}_validation.sh` (4 new files)
- All four scripts begin with `set -euo pipefail` — fail-fast on undefined vars, broken pipes, command failures.
- All four read `${API_URL:-https://localhost:8080}` (default to localhost) and `${JWT:-}` with an explicit "ERROR: set JWT env var" guard that `exit 2`s if `$JWT` is empty. No embedded credentials.
- `curl -k` is used (justified — the dev cert is self-signed; the scripts target localhost in dev/test only — documented in each script's header).
- ✓ No findings — same posture as cycle-7's `probe_inventory_validation.sh`.
### `scripts/run-performance-tests.sh` (modified, wire-rename only)
- Diff is exclusively the AZ-812 wire-format rename: `?Latitude=…&Longitude=…&ZoomLevel=…` → `?lat=…&lon=…&zoom=…` and `{"latitude":…,"longitude":…}` → `{"lat":…,"lon":…}` across PT-01 through PT-08 invocations. No new code, no new credentials, no shell-injection surface introduced.
- ✓ No findings.
## Cycle-7 Carry-overs (still open at cycle-8 tip)
| Finding | Source cycle | Carry-over status | Cycle-8 interaction |
|---------|--------------|-------------------|---------------------|
| **F-AZ795-1** — `JsonException.Message` propagated in `GlobalExceptionHandler.cs:108-117` | cycle 7 | open | Unchanged. Cycle 8 introduced **F-AZ810-1** which is the SAME pattern in a NEW code path; both need lock-step sanitiser remediation. |
| **F-AZ795-2** — Generic `BadHttpRequestException.Message` propagated in `GlobalExceptionHandler.cs:88-93` | cycle 7 | open | Unchanged. Cycle 8 routes more endpoint failures through `WithValidation<T>()` which builds `ValidationProblemDetails` directly — reducing the practical reachability of this path on the 4 newly-validated endpoints, but not eliminating it (model-binding failures pre-validator can still hit the path). |
## Verdict (Phase 2)
**PASS_WITH_WARNINGS** — 1 Medium (F-AZ809-1) + 2 new Lows (F-AZ810-1, F-AZ810-2) + 2 cycle-7 Low carry-overs (F-AZ795-1, F-AZ795-2). No Critical or High findings.
Per the skill's verdict-logic, Medium severity yields PASS_WITH_WARNINGS — not FAIL (FAIL is reserved for Critical or High). F-AZ809-1 is exploitable only by an authenticated tenant operator with a valid JWT (the route endpoint requires `RequireAuthorization()` without a permission scope, so any tenant with API access reaches it). The Medium is contained within the cycle-8 threat model — every cycle-8 endpoint is auth-gated — but should be the **highest-priority cycle-9 follow-up**: pre-existing-class Mediums tend to be the entry vector for higher-severity issues when adjacent threat-model assumptions shift (e.g. if a future feature exposes the route endpoint to an untrusted-tenant audience).
The 2 new Lows + 2 carry-over Lows are not release-blockers in isolation; they are filed for the next cycle's follow-up batch.
+4 -4
View File
@@ -4,11 +4,11 @@
flow: existing-code flow: existing-code
step: 14 step: 14
name: Security Audit name: Security Audit
status: not_started status: completed
sub_step: sub_step:
phase: 0 phase: 5
name: awaiting-choice name: report-rendered
detail: "" detail: "verdict: PASS_WITH_WARNINGS (1 Medium F-AZ809-1 + 2 new Lows + 3 carry-over Lows + 1 carry-over Medium D2-cy4 test-runtime only)"
retry_count: 0 retry_count: 0
cycle: 8 cycle: 8
tracker: jira tracker: jira