Files
Oleksandr Bezdieniezhnykh bc04ba7f99 [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>
2026-05-22 11:24:27 +03:00

9.1 KiB
Raw Permalink Blame History

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/tileYz/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 defaultUnmappedMemberHandling.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 contracterror-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.