# REST 400 error message sanitization

**Task**: AZ-1113_rest_error_sanitizer
**Name**: Sanitize REST 400 error messages (F-AZ795-1/2, F-AZ810-1)
**Description**: Replace raw `JsonException` / `BadHttpRequestException` messages in client-facing HTTP 400 bodies with static, consumer-safe strings while preserving structured `errors[]` field paths.
**Complexity**: 2 points
**Dependencies**: AZ-795 (shared validation infra), AZ-810 (UavUploadValidationFilter)
**Component**: SatelliteProvider.Api, SatelliteProvider.Services.TileDownloader
**Tracker**: AZ-1113 (https://denyspopov.atlassian.net/browse/AZ-1113)
**Epic**: AZ-795

## Problem

Strict JSON validation (AZ-795+) surfaces deserialization failures as HTTP 400 + RFC 7807 `ValidationProblemDetails`. Several code paths echo raw `Exception.Message` values that leak internal .NET type names, JSON paths with framework wording, or parameter binding details (F-AZ795-1, F-AZ795-2, F-AZ810-1). All paths are auth-gated but the production error contract should not expose implementation fingerprints.

## Outcome

- Client-visible 400 messages use fixed, documented strings; full exception details remain server-side only (existing log / correlationId patterns).
- All three call sites sanitized in one change: `GlobalExceptionHandler`, `UavUploadValidationFilter`, `UavTileUploadHandler`.
- `error-shape.md` documents the sanitization policy; existing integration tests updated; new assertions lock the contract.

## Scope

### Included
- `SatelliteProvider.Api/GlobalExceptionHandler.cs` — sanitize `JsonException` messages in `errors[]` map entries; sanitize non-deserialization `BadHttpRequestException` `detail` (F-AZ795-1, F-AZ795-2)
- `SatelliteProvider.Api/Validators/UavUploadValidationFilter.cs` — sanitize metadata JSON parse failures (F-AZ810-1)
- `SatelliteProvider.Services.TileDownloader/UavTileUploadHandler.cs` — sanitize defense-in-depth metadata parse path (same finding class)
- Unit tests: extend `GlobalExceptionHandlerTests`; add/adjust filter/handler tests as needed
- Integration tests: assert response bodies do not contain `System.` substrings or known leaky patterns from fixture payloads
- `_docs/02_document/contracts/api/error-shape.md` — patch bump + Information Disclosure section

### Excluded
- FluentValidation rule message changes (already consumer-oriented)
- gRPC `DeliveryError` path (resolved cycle 9)
- F-AZ810-2 `DateTime` vs `DateTimeOffset` (separate task)
- FluentValidation 12.0.0 → 12.1.1 bump (D-AZ795-1)

## Acceptance Criteria

**AC-1: GlobalExceptionHandler JsonException sanitization**
Given an authenticated request whose body triggers `BadHttpRequestException` with inner `JsonException` (unknown field or type mismatch)
When the exception reaches `GlobalExceptionHandler`
Then HTTP 400 `errors[<fieldPath>]` contains a static message (no `.NET` type name, no `System.Text.Json` fingerprint)
And the raw exception is not echoed in `detail`

**AC-2: GlobalExceptionHandler non-JSON BadHttpRequest sanitization**
Given a `BadHttpRequestException` without inner `JsonException` (e.g. query binding failure)
When handled by `GlobalExceptionHandler`
Then `detail` is a static string (not `badRequest.Message` verbatim)

**AC-3: UAV upload filter sanitization**
Given `POST /api/satellite/upload` with malformed `metadata` JSON
When `UavUploadValidationFilter` catches `JsonException`
Then `errors["metadata"]` is a static string without `ex.Message`
And an integration test proves no `System.` substring in the response body

**AC-4: UAV handler defense-in-depth sanitization**
Given direct invocation or filter-bypass path where `UavTileUploadHandler` parses invalid metadata JSON
When `JsonException` is caught
Then the returned envelope error message is static (no `ex.Message`)

**AC-5: Contract documentation**
Given the change ships
When `error-shape.md` is read
Then an Information Disclosure section lists sanitized vs allowed message sources and the static strings used

## Non-Functional Requirements

**Compatibility**
- Preserve RFC 7807 shape, field paths in `errors[]`, and HTTP status codes — only message *content* changes
- Update tests that currently assert substring of raw `JsonException.Message` (e.g. `GlobalExceptionHandlerTests.TryHandleAsync_DeserializationFailure_*`)

**Security**
- No regression to AZ-353 5xx sanitization

## Unit Tests

| AC Ref | What to Test | Required Outcome |
|--------|-------------|-----------------|
| AC-1 | `GlobalExceptionHandler` + `JsonException` inner | `errors` values are static; no type name leak |
| AC-2 | `GlobalExceptionHandler` + bind-only `BadHttpRequestException` | `detail` is static |
| AC-4 | `UavTileUploadHandler` metadata parse failure | Envelope message static |

## Blackbox Tests

| AC Ref | Initial Data/Conditions | What to Test | Expected Behavior | NFR References |
|--------|------------------------|-------------|-------------------|----------------|
| AC-3 | Valid JWT + GPS claim; multipart with invalid metadata JSON | `POST /api/satellite/upload` | 400; body lacks `System.` | Security |

## Constraints

- Follow existing `ProblemDetails` / `ValidationProblemDetails` patterns in `error-shape.md` v1.0.0
- Do not add verbose logging beyond existing exception logs

## Risks & Mitigation

| Risk | Mitigation |
|------|------------|
| Tests assert old raw messages | Update unit + integration assertions in same PR |
| Consumers parsed error text | Field paths unchanged; only generic message strings — document in error-shape patch |

## Contract

Producer task — patch bump to `_docs/02_document/contracts/api/error-shape.md` (v1.0.0 → v1.0.1): document static 400 strings for deserializer/binding failures.
