# Azaion.Annotations — Security approach (retrospective) > Inventory of the **security mechanisms actually present in code today** + the gaps that the autodev existing-code Step 14 (Security Audit) will close. Evidence anchored to source files. ADR references point to `_docs/02_document/architecture.md`. ## Authentication - **Mechanism**: JWT Bearer with **ES256 asymmetric signing**, verified against admin's JWKS endpoint (`JWT_JWKS_URL`, default `https://admin.azaion.com/.well-known/jwks.json`). - **Token validator code**: `src/Auth/JwtExtensions.cs` (verifier only — annotations does not mint tokens). - **Validation parameters**: `ValidateIssuer = true`, `ValidateAudience = true`, `ValidateLifetime = true`, `ValidateIssuerSigningKey = true`, `ValidAlgorithms = [SecurityAlgorithms.EcdsaSha256]`, `RequireSignedTokens = true`, `RequireExpirationTime = true`, `ClockSkew = 30s`. - **Anonymous endpoints**: only `GET /health` — every other endpoint requires authentication. The legacy `POST /auth/refresh` was removed; callers refresh against admin's `POST /token/refresh`. - **Token storage**: stateless. Refresh tokens live in admin's DB (revocation is enforced by admin); annotations does not persist any token material. ## Authorization - **Policies declared**: `ANN`, `DATASET`, `ADM` (in `src/Auth/JwtExtensions.cs`). - **Policy → controller mapping** (verified): - `ANN`: `AnnotationsController`, `MediaController` (annotation lifecycle + media upload — what humans on the annotation UI need). - `DATASET`: `DatasetController` (dataset exploration). - `ADM`: planned `[ADM]` writes on `/classes` (RB-06) and mutating routes on `/settings/*`. - Mixed `[Authorize]` (any authenticated): the read endpoints under `/settings/*`. - **Per-action overrides**: writes inside `/settings/*` typically require `ADM`; reads accept any authenticated user. ### Known weakness - No row-level / tenancy authorization is implemented. A user with policy `ANN` can read/mutate any annotation regardless of mission ownership. This is acceptable for the current single-tenant deployment but must be documented before any multi-tenant rollout. ## Secrets handling - **Source**: env vars resolved at `Program.cs` boot via `ConfigurationResolver.ResolveRequiredOrThrow` (env var → `IConfiguration` → throw if missing). - **Required in any environment** (no fallback — service refuses to start without them): `DATABASE_URL`, `JWT_ISSUER`, `JWT_AUDIENCE`, `JWT_JWKS_URL`. - **Soft defaults**: `RABBITMQ_*` keep their development defaults; operators MUST still override for production. - **No secret manager integration** in code today — secrets land via container env (suite-level orchestrator's responsibility). - `JWT_SECRET` was removed: annotations holds no HMAC material and no longer issues tokens. ## Transport - Inside the container: HTTP only (`ASPNETCORE_URLS=http://+:8080`). - TLS termination: outside the container (suite-level reverse proxy / orchestrator). - No HSTS or HTTPS-redirect middleware in `Program.cs`. ## CORS - Configuration: config-driven allow-list via `CorsConfig:AllowedOrigins` (`Program.cs` + `Infrastructure/CorsConfigurationValidator.cs`). - `CorsConfigurationValidator.EnsureSafeForEnvironment` refuses to start in `Production` when the allow-list is empty and `CorsConfig:AllowAnyOrigin` is not explicitly set; a `LogWarning` is emitted in lower environments when running with the permissive fallback so the drift is visible in logs. ## Input validation & sanitization - **REST DTO binding**: ASP.NET model binding only — no `FluentValidation` or custom validator visible in code. - **File upload validation**: no MIME / extension whitelist visible at the `MediaController` layer. Step 14 should confirm whether the upstream pipeline restricts upload format or whether the service itself needs to. - **SQL**: all access via Linq2DB / parameterised queries — no raw string concatenation found. - **YOLO label file write**: trusts caller-provided detections (class id, coordinates) — clamping / range checks would be a Step 14 candidate. ## Rate limiting / DoS - **None present** — there is no rate-limiting middleware (`AddRateLimiter`, `UseRateLimiter`) registered in `Program.cs`. - **Implicit limits**: SSE channel is unbounded; outbox table is unbounded; RabbitMQ stream is bounded by retention config (suite-level). - Step 14 candidate: per-IP rate limit on `POST /annotations` and `POST /media`, since they accept image bytes and write to disk. ## Auditing & logging - Console logger configured in `Program.cs` (default ASP.NET Core logging). - Authentication failures: rely on `JwtBearer` middleware default 401s — not explicitly logged with extra detail. - No audit log of mutations is written today; the `annotations_queue_records` outbox + downstream stream IS the de-facto audit trail (post RB-01 + RB-09). ## Observability boundary - `/health` is the only pre-auth endpoint that reveals state. It returns 200/non-200 only — no version or DB info — so it is safe to expose to load balancers without auth. - Swagger UI is mounted in all environments (ADR-005). It exposes the full controller surface but no secrets. Step 14 should consider gating it behind `ADM` or environment-conditional registration. ## Error response surface - All errors returned via `Middleware/ErrorHandlingMiddleware` use the suite-standard envelope (`_docs/02_document/common-helpers/01_http-error-envelope.md`). - Stack traces are not echoed back to the client in prod (verify the `IsDevelopment()` branch in the middleware during Step 14). ## Summary table — known security gaps to address in Step 14 | ID | Area | Gap | Status | |----|------|-----|--------| | SEC-01 | Auth | JWT issuer/audience not validated | **Closed** — `ValidateIssuer`/`ValidateAudience` enforced; `JWT_ISSUER` and `JWT_AUDIENCE` are required env vars. | | SEC-02 | Secrets | Dev fallback for `JWT_SECRET` in source | **Closed** — `JWT_SECRET` removed; remaining required vars fail fast on startup via `ConfigurationResolver`. | | SEC-03 | CORS | `AllowAnyOrigin` default | **Closed** — config-driven allow-list; `CorsConfigurationValidator` blocks empty list in `Production`. | | SEC-04 | Surface | Swagger UI exposed in prod | Open — gate behind `ADM` or `Development` only. | | SEC-05 | Upload | No MIME / extension whitelist on `/media` | Open — validate at controller before disk write. | | SEC-06 | DoS | No rate limiting on hot write endpoints | Open — per-IP / per-user limiter on `POST /annotations`, `POST /media`. | | SEC-07 | Tenancy | No row-level authorization | Open — document constraint; add mission-scoped check before multi-tenant rollout. | | SEC-08 | Audit | No structured audit log | Open — use post-RB-01 lifecycle stream as the audit substrate; add structured fields. |