[AZ-794] [AZ-795] [AZ-796] Cycle 7 Steps 12-15 sync (test-spec / docs / security / perf)

Step 12 (Test-Spec Sync): adds BT-27 for the AZ-796 9-rule
validation surface and 12 cycle-7 AC rows + Coverage Summary
update to traceability-matrix.md.

Step 13 (Update Docs): module-layout + module docs for the new
SatelliteProvider.Api/Validators namespace + GlobalExceptionHandler
+ updated TileInventory DTO; tests_unit + tests_integration
document the new InventoryRequestValidatorTests (16 unit tests
covering all 9 rules) + TileInventoryValidationTests (16
integration tests) + ProblemDetailsAssertions support;
glossary entries for Validation Problem Details / FluentValidation
/ Unmapped Member Handling; system-flows F8 (Tile Inventory Bulk
Lookup) expanded with deserializer + validator gates and a 13-row
Validation Surface table; data_parameters § Tile Inventory
documents the v2 input schema + constraints; ripple_log_cycle7
captures the doc-side ripple decisions.

Step 14 (Security Audit): 5-phase audit ran; verdict
PASS_WITH_WARNINGS (3 Low findings — D-AZ795-1 FluentValidation
12.0.0 -> 12.1.1 recommended bump, F-AZ795-1 JsonException.Message
leak in 400 detail, F-AZ795-2 BadHttpRequestException.Message leak).
No Critical / High; auth runs before validation (confirmed in
Program.cs); two NuGet additions (FluentValidation 12.0.0 +
.DependencyInjectionExtensions 12.0.0) both CVE-clean. Per-phase
reports plus consolidated security_report_cycle7.md.

Step 15 (Performance Test): docker compose stack used for perf
run, scripts/run-performance-tests.sh exited 0 with 8/8 scenarios
PASS (second consecutive clean exit-0); added PT-09 cycle-7 smoke
probe (v2 z/x/y schema, 2500-tile all-miss batch) measuring
min=27ms median=44ms p95=73ms max=86ms (13.7x under AZ-505 AC-4
1000ms budget). PT-07/08 improvements traced to the cycle-6 TLS
handshake-overhead identification, not application-side change.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-22 11:24:27 +03:00
parent 865dfdb3b9
commit bc04ba7f99
17 changed files with 779 additions and 32 deletions
+106
View File
@@ -0,0 +1,106 @@
# 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:108117` | `JsonException.Message` propagated to client in 400 response (type-name + parse-position leak) |
| F-AZ795-2 | Low | Information Disclosure (A09) | `SatelliteProvider.Api/GlobalExceptionHandler.cs:8893` | 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:108117` (`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:8893` (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<T>`, `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<T>()` 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.