# Security Audit Report (Cycle 7) **Date**: 2026-05-22 **Scope**: Cycle-7 delta over the cycle-5 audit (`_docs/05_security/security_report_cycle5.md`); cycle 6 produced no security report, so cycle 5 is the last full baseline. Cycle-7 surface = AZ-794 (`tileZoom/tileX/tileY` → `z/x/y` rename) + AZ-795 (strict-validation epic: FluentValidation, `UnmappedMemberHandling.Disallow`, GlobalExceptionHandler, error-shape contract) + AZ-796 (inventory-endpoint 9-rule validator). **Trigger**: `/autodev` Step 14 (Security Audit) — feature cycle 7, post-implementation, post-test-spec-sync, post-docs-update. **Verdict (cycle-7 delta)**: **PASS_WITH_WARNINGS** (3 Low findings; no Critical/High/Medium). **Verdict (cumulative)**: **PASS_WITH_WARNINGS** (carries forward 1 cycle-4 Medium dep finding via D2-cy4 + 2 cycle-5 Low informational notes + cycle-7's 3 Lows). ## Summary | Severity | Cycle 5 delta | Cycle 7 delta | Cumulative | |----------|---------------|---------------|------------| | Critical | 0 | 0 | 0 | | High | 0 | 0 | 0 | | Medium | 0 | 0 | 1 (D2-cy4 carry — `Microsoft.NET.Test.Sdk 17.8.0` transitive `NuGet.Frameworks`; test-runtime exposure only) | | Low | 2 informational notes | **3 NEW** (F-AZ795-1, F-AZ795-2, D-AZ795-1) | 5+ | ## OWASP Top 10:2021 Assessment | Category | Status (cycle-7 delta) | Findings | |----------|------------------------|----------| | A01 — Broken Access Control | PASS | — | | A02 — Cryptographic Failures | N/A | No crypto in cycle 7 | | A03 — Injection | PASS | — | | A04 — Insecure Design | PASS (improvement) | AZ-795 / AZ-796 centralise validation behind one filter + one error handler — direct improvement | | A05 — Security Misconfiguration | PASS | `UnmappedMemberHandling.Disallow` is defense-in-depth (mass-assignment prevention) | | A06 — Vulnerable Components | PASS_WITH_WARNINGS | D-AZ795-1 (Low; bump 12.0.0 → 12.1.1 hardening release) | | A07 — Auth Failures | PASS | JWT validation unchanged; new endpoint filter cannot run for anonymous callers | | A08 — Data Integrity Failures | N/A | No CI/CD or artifact-signing surface in cycle 7 | | A09 — Logging Failures | PASS_WITH_WARNINGS | F-AZ795-1 + F-AZ795-2 (Lows; `JsonException.Message` / `BadHttpRequestException.Message` echoed to client) | | A10 — SSRF | N/A | No URL-input fields in cycle 7 | ## Findings | # | Severity | Category | Location | Title | |---|----------|----------|----------|-------| | F-AZ795-1 | Low | Information Disclosure (A09) | `SatelliteProvider.Api/GlobalExceptionHandler.cs:108–117` | `JsonException.Message` propagated to client in 400 response (type-name + parse-position leak) | | F-AZ795-2 | Low | Information Disclosure (A09) | `SatelliteProvider.Api/GlobalExceptionHandler.cs:88–93` | Generic `BadHttpRequestException.Message` propagated as `Detail` for non-JSON 400 paths | | D-AZ795-1 | Low | Vulnerable & Outdated Components (A06) | NuGet | `FluentValidation` + `FluentValidation.DependencyInjectionExtensions` 12.0.0 → 12.1.1 (hardening release; no published CVE) | ### Finding Details **F-AZ795-1: `JsonException.Message` propagated to client in 400 response** (Low / A09 — Information Disclosure) - Location: `SatelliteProvider.Api/GlobalExceptionHandler.cs:108–117` (`TryExtractDeserializationErrors`) - Description: `System.Text.Json.JsonException.Message` is echoed in the 400 `ValidationProblemDetails.errors[fieldPath]` array. The default message includes the offending .NET type (`System.Int32`, `System.Guid`, …), the JSON path (already separately captured as the key), and the byte position / line number in the payload — e.g. *"The JSON value could not be converted to System.Int32. Path: $.tiles[0].z | LineNumber: 0 | BytePositionInLine: 27."*. - Impact: Low. The `UseAuthentication` + `UseAuthorization` middleware short-circuits anonymous callers with 401 before any endpoint filter runs, so the leak is only reachable by authenticated callers. The leaked content (type names, parse positions, `System.Text.Json` fingerprint) is already inferable from the OpenAPI spec at `/swagger/v1/swagger.json`; this finding narrows the attack surface for an authenticated tenant operator but does not expose secrets, PII, or pivot vectors. - Remediation: Sanitise the response message to a generic string (e.g. `"Could not deserialize value at this field path."`) while continuing to log the raw `jsonEx.Message` server-side under the request's `correlationId`. Update `error-shape.md` test case `validation-type-mismatch` and the integration tests to assert no `System.*` substring appears in any `errors[]` value. - Status: filed for next cycle. **F-AZ795-2: Generic `BadHttpRequestException.Message` propagated as `Detail`** (Low / A09 — Information Disclosure) - Location: `SatelliteProvider.Api/GlobalExceptionHandler.cs:88–93` (fallback 400 path when there is no `JsonException` inner exception) - Description: When `BadHttpRequestException` has no `JsonException` inner exception (e.g. framework model-binding failures, unsupported `Content-Type`, oversized request bodies), the framework-provided `Message` is echoed back as `ProblemDetails.Detail`. ASP.NET Core message strings for these paths can include parameter names and (rarely) framework version hints. - Impact: Same severity as F-AZ795-1. Pre-existing-class issue (model-binding messages were always shaped this way under ASP.NET Core); cycle 7 didn't introduce or worsen it. - Remediation: Same as F-AZ795-1 — sanitise the `Detail` to a generic string and log the raw `Message` server-side. Best done in tandem with F-AZ795-1. - Status: filed for next cycle. **D-AZ795-1: FluentValidation 12.0.0 → 12.1.1 hardening refresh** (Low / A06 — Vulnerable & Outdated Components) - Location: `SatelliteProvider.Api/SatelliteProvider.Api.csproj` (`FluentValidation` + `FluentValidation.DependencyInjectionExtensions`) - Description: 12.0.0 has no known CVEs (verified against GitHub Security Advisories, NVD, ReversingLabs Spectra Assure). 12.1.1 is the latest version (~5 months newer at audit time) and is a hardening release — minor upstream fixes, no security advisories. - Impact: Low. Pure forward-compatibility hardening. - Remediation: Bump both packages to 12.1.1 in a future minor-dependency-roll cycle. - Status: filed for next cycle. Not release-blocking. ## Dependency Vulnerabilities | Package | CVE | Severity | Fix Version | Status | |---------|-----|----------|-------------|--------| | FluentValidation 12.0.0 | — (hardening only) | Low | 12.1.1 | D-AZ795-1 — filed | | FluentValidation.DependencyInjectionExtensions 12.0.0 | — (hardening only) | Low | 12.1.1 | D-AZ795-1 — filed (same item) | | Microsoft.NET.Test.Sdk 17.8.0 (transitive `NuGet.Frameworks`) | — (cycle-4 carry-over D2-cy4) | Medium | TBD (next Test SDK refresh cycle) | carry-over from cycle 4 — owned by a separate unscheduled task | ## Recommendations ### Immediate (Critical/High) None. ### Short-term (Medium) None new in cycle 7. Cycle-4 carry-over D2-cy4 (`Microsoft.NET.Test.Sdk` Medium) remains in the backlog. ### Long-term (Low / Hardening) 1. **Sanitise client-visible 400 messages** (F-AZ795-1 + F-AZ795-2). Single change in `GlobalExceptionHandler.WriteClientErrorAsync` + matching test assertion. Estimated 1 hour of effort. Should be filed as a small follow-up child of AZ-795 (or as a standalone task under the same epic). 2. **Bump FluentValidation 12.0.0 → 12.1.1** (D-AZ795-1). Single `.csproj` edit + a regression test pass; no API surface change in 12.0.0 → 12.1.1 per the upstream changelog. ### Cumulative reminders (carry-overs) - Cycle-4 D2-cy4 — `Microsoft.NET.Test.Sdk 17.8.0` transitive `NuGet.Frameworks` Medium-severity finding, test-runtime exposure only. Owned by the next Test SDK refresh. ## Cycle-7 Architectural Wins The audit specifically wants to record three improvements introduced this cycle: 1. **Mass-assignment prevention by default** — `UnmappedMemberHandling.Disallow` on the global JSON pipeline rejects any unknown root or nested field across every public endpoint. The cycle-7 acceptance criteria explicitly enumerate this for the inventory endpoint; the protection is in force for every other endpoint that consumes a JSON body too. 2. **Uniform 4xx contract** — `error-shape.md` v1.0.0 unifies the wire shape across two failure layers (deserializer + FluentValidation). Future child tickets reuse `ValidationEndpointFilter`, `ProblemDetailsAssertions`, and the contract without adding new infrastructure. This dramatically reduces the chance of future endpoints drifting into their own bespoke error shapes. 3. **Auth-before-validation invariant verified** — endpoint filters added via `WithValidation()` cannot run for unauthenticated callers (the routing pipeline runs `UseAuthorization` BEFORE the endpoint filter chain). The audit explicitly verified the cycle-7 inventory endpoint and re-asserted the invariant in this report. ## Verdict **PASS_WITH_WARNINGS** — 3 Lows, 0 Mediums (cycle-7 delta), 0 Highs, 0 Criticals. Cycle 7 is **safe to release**. The 3 Lows are filed for follow-up cycles and do not block release. Cumulative posture: PASS_WITH_WARNINGS (1 cycle-4 Medium carry-over via D2-cy4 + Lows above). No regression of the cycle-5 PASS posture.