# Security Approach — Azaion.Missions > **Status**: derived-from-code (autodev `/document` Step 6, 2026-05-14). > All claims below trace to actual code, configuration, or a tracked suite-level finding. Items called out as "currently divergent" are intentional carry-forward — see `_docs/02_document/architecture.md` § 8 ADRs and `00_discovery.md` § Spec ↔ Code Divergences. --- ## 1. Authentication **Mechanism**: JWT bearer (HS256) with **local validation only** — this service never calls back to the issuing `admin` service. **Trust model**: a single shared HMAC secret (`JWT_SECRET`) is provisioned to `admin` (issuer) and to every backend service on each edge device (validators). Rotation requires a coordinated re-deploy across all of them. **Validation parameters** (`Auth/JwtExtensions.cs`): | Parameter | Value | Notes | |-----------|-------|-------| | Algorithm | HS256 (`SymmetricSecurityKey(UTF-8(JWT_SECRET))`) | Symmetric → asymmetric switch is suite-wide concern, not in this Epic | | `ValidateLifetime` | `true` | Tokens with `exp` in the past are rejected | | `ClockSkew` | `TimeSpan.FromMinutes(1)` | Tighter than .NET's 5-min default | | `ValidateIssuer` | **`false`** | Known CMMC L2 finding (suite-tracked AZ-487/AZ-494); consistent with shared-secret trust | | `ValidateAudience` | **`false`** | Same finding as above | | `ValidateIssuerSigningKey` | `true` | Always required when `ValidateLifetime`/`ValidateIssuer` are set explicitly | **Failure outcomes**: | Condition | HTTP code | |-----------|-----------| | Missing `Authorization` header | 401 | | Invalid signature | 401 | | Expired token (with 1-min skew) | 401 | | Token signed with old `JWT_SECRET` after rotation | 401 (until coordinated re-deploy + re-login) | | Valid signature + lifetime, but missing `permissions=FL` claim | 403 | **`admin` outage**: tokens issued before the outage continue to validate locally. This service does **not** require `admin` to be reachable for any flow. Once issued tokens expire, new logins fail at `admin`'s end (UI concern), but this service stays up. ## 2. Authorization **Single named policy**: `"FL"`. Every controller action in `01_vehicle_catalog` and `02_mission_planning` carries `[Authorize(Policy = "FL")]`. The policy is satisfied by a `permissions` claim with value `"FL"`. **Role → permission matrix** is suite-level (`../../suite/_docs/00_roles_permissions.md`); this service does NOT enforce roles, only the `FL` permission. **No per-method authz**: every protected endpoint has the same gate. There is no notion of "read-only operator" vs "full-access operator" inside this service. **Hardcoded policy name carries legacy wording**: the string `"FL"` (originally "Flight") survives the rename to `missions`. Renaming the permission code is a fleet-wide auth change (would invalidate every issued token until new ones are minted) and is **NOT** in this Epic. Tracked as a TODO in `../../suite/_docs/00_roles_permissions.md`. **Typo risk**: the `"FL"` string is repeated in feature controllers as a raw string. A typo silently turns into a permanent 403 with no compile-time detection. Mitigation: code review + the `module-layout.md` § Verification Needed #4 entry. **No per-user attribution / audit**: the JWT's `sub` / user-id claim is parsed by `JwtBearerHandler` into `ClaimsPrincipal`, but nothing in this service consumes it. Logs are timestamp-only — incident reconstruction requires correlation by request time, not by user. ## 3. Data protection **At rest**: PostgreSQL on-disk encryption is the device-level concern (suite-level, NOT this service). This service does NOT encrypt data at the column level. **In transit**: - The container `EXPOSE 8080` is **plain HTTP**. TLS termination is handled by the suite's edge reverse proxy (per `../../suite/_docs/00_top_level_architecture.md`). - No `app.UseHttpsRedirection()` in this service. If the reverse proxy is misconfigured or absent, traffic between the operator UI and this service may be cleartext on the local edge network. **Secrets management**: - `JWT_SECRET` and `DATABASE_URL` are env vars (with hardcoded dev fallbacks). See § 6 below. - No secret manager (Vault, AWS SM, K8s Secrets) — secrets are baked into the device's docker compose env at provisioning time. - No runtime gate prevents startup with the dev fallback in production (ADR-005 carry-forward). ## 4. Input validation **None at the application layer**. No `[Required]`, no `[Range]`, no min-length attributes; no custom validators. The following are all accepted by ASP.NET Core model binding without rejection: | Bad input | Accepted today | |-----------|---------------| | `CreateVehicleRequest.Name = ""` | yes | | `CreateVehicleRequest.BatteryCapacity = -1` | yes | | `CreateVehicleRequest.Type = (VehicleType)999` | yes — int casts to enum without range check | | `CreateWaypointRequest.OrderNum = -1` | yes | | `CreateWaypointRequest.GeoPoint = null` (or all three of `Lat`/`Lon`/`Mgrs` null) | yes | | `GetMissionsQuery.Page = -1` / `PageSize = 1_000_000` | yes — no bounds | This is a **carry-forward concern** — input-shape testing is not a security gate today; the threat surface is mitigated by the closed edge network and authenticated single-operator workflow. Tightening is on the autodev backlog (Phase B feature cycle). ## 5. CORS **Open in every environment**: `AllowAnyOrigin / AllowAnyMethod / AllowAnyHeader` in `Program.cs`. Spec does not mandate a CORS policy. The closed edge network behind the suite reverse proxy is the deployment-shape mitigation. Worth confirming on the **first production rollout** that the upstream proxy whitelists origins; if not, this is a finding to surface. ## 6. Production-deploy footguns These are explicit security-relevant risks the code carries today, all tracked at the suite level or as carry-forward: | Footgun | Where | Mitigation | |---------|-------|------------| | **Dev fallback for `JWT_SECRET`** silently accepted in production if env var unset | `Program.cs` (no `IsDevelopment` gate, ADR-005) | Suite-level remediation pending; recommend "fail-fast at startup if `JWT_SECRET` is unset OR equals the well-known dev fallback" | | **Dev fallback for `DATABASE_URL`** silently accepted in production if env var unset | `Program.cs` | Same pattern as `JWT_SECRET`; misconfigured deploy hits localhost Postgres on the device, which usually doesn't exist → process exits, but the failure mode is loud (crash) not silent | | **Swagger UI mounted unconditionally** | `Program.cs` (no `IsDevelopment` gate, ADR-005) | Reverse-proxy-level allowlist on `/swagger` is the suite-level mitigation; verify on first production rollout | | **CORS `AllowAnyOrigin/Method/Header`** in production | `Program.cs` | Reverse-proxy origin whitelist is the suite-level mitigation | | **No HTTPS redirection** | `Program.cs` (no `app.UseHttpsRedirection()`) | Reverse proxy enforces TLS upstream | | **Stack trace logged for unhandled 500s** | `Middleware/ErrorHandlingMiddleware.cs` `LogError(ex, ...)` | Stack is logged only — NOT returned in the HTTP response body (the 500 body is the generic `"Internal server error"` message from middleware) | | **JWT `iss`/`aud` validation disabled** | `Auth/JwtExtensions.cs` | CMMC L2 row 3 finding; tracked at suite level under AZ-487 / AZ-494 | | **Cascade-delete is NOT transaction-wrapped** (data-integrity, not auth) | `Services/MissionService.cs`, `Services/WaypointService.cs` (ADR-006) | One-line fix queued; recommended to land with B6 | | **Hardcoded permission string `"FL"`** in feature controllers | `Controllers/{Vehicles,Missions}Controller.cs` | Risk: typo silently turns into permanent 403; mitigation by code review + `module-layout.md` | | **Permission code `"FL"` retains legacy "Flight" wording** post-rename | `Auth/JwtExtensions.cs` | Fleet-wide auth change deferred (not in this Epic); TODO in `../../suite/_docs/00_roles_permissions.md` | ## 7. Audit logging **None at the application level today.** The only structured log emitted by application code is `_logger.LogError(ex, "Unhandled exception")` in `ErrorHandlingMiddleware` for 500s. There is: - **No correlation ID** per request - **No per-user attribution** (the JWT user-id claim is not consumed) - **No security-event log** (auth failures are logged by `JwtBearerHandler` at default ASP.NET Core levels — typically Information, not surfaced as a dedicated audit channel) - **No data-access audit** (writes/deletes go directly through linq2db with no wrapper that emits an audit row) Production incident response on this service today requires grep-by-timestamp correlation against the operator UI's logs and `admin`'s issuance logs. ## 8. Threat model summary (one-paragraph) The deployment shape — closed edge network, single operator per device, suite reverse proxy enforcing TLS and origin allowlisting upstream, Watchtower restart on crash — is the **primary defence-in-depth layer** for everything not handled by HS256 JWT validation and the `FL` permission gate. The known weak points (dev fallbacks not gated on `IsDevelopment()`, no input validation, no application-level audit log, `iss`/`aud` not validated) are documented and tracked, with the most critical ones (CMMC L2 row 3, default-vehicle race) under suite-level or B-ticket Jira IDs. This Epic (rename + GPS-Denied removal) does **not** change the security posture; it preserves every current invariant. ## 9. References | Concern | File | |---------|------| | Auth registration | `Auth/JwtExtensions.cs` | | Authorization attribute usage | `Controllers/AircraftsController.cs` (post-B6: `VehiclesController.cs`), `Controllers/FlightsController.cs` (post-B6: `MissionsController.cs`) | | Error envelope (no stack-leak) | `Middleware/ErrorHandlingMiddleware.cs` | | Env var resolution + dev fallbacks | `Program.cs` | | CMMC L2 scorecard | `../../suite/_docs/05_security/cmmc_l2_scorecard.md` | | Roles & `FL` permission origin | `../../suite/_docs/00_roles_permissions.md` | | ADR-005 (Swagger + dev fallbacks) | `_docs/02_document/architecture.md` § 8 | | ADR-002 (PascalCase wire shape) | `_docs/02_document/architecture.md` § 8 | | Component identity description | `_docs/02_document/components/05_identity/description.md` | | Component http-conventions description | `_docs/02_document/components/06_http_conventions/description.md` |