Files
annotations/_docs/00_problem/security_approach.md
T
Oleksandr Bezdieniezhnykh 03f879206e docs+src: complete Steps 1-3 outcomes + auth re-sync baseline
This commit captures everything produced during autodev existing-code
Steps 1 (Document), 2 (Architecture Baseline Scan), and 3 (Test Spec),
together with the targeted auth + CORS re-sync triggered on 2026-05-14
when codebase drift was detected at Step 4 entry. None of this work was
previously committed.

Step 1 (Document) — 50+ _docs/02_document/ files: problem, solution,
architecture, system flows, glossary, module-layout, per-component
specs (01..06), modules, deployment, diagrams, data model, FINAL
report, verification log, discovery.

Step 2 (Architecture Baseline) — architecture_compliance_baseline.md.
Verdict PASS_WITH_WARNINGS (0 Critical, 0 High, 1 Medium, 2 Low). No
High/Critical findings; auto-chained to Step 3 per existing-code flow.

Step 3 (Test Spec) — _docs/02_document/tests/* (67 scenarios across
blackbox, security, resilience, resource-limit, performance), plus
e2e/docker-compose.test.yml, e2e/seed/run.sh, scripts/run-tests.sh,
scripts/run-performance-tests.sh. Coverage 88% over the active scope
(40 of 45 items covered, 6 RB-deferred, 5 documented-as-uncovered).

Targeted auth + CORS re-sync — replaces the deleted in-house token
issuer with a JWKS-verifier model. AuthController and TokenService
removed; JwtExtensions switched from HS256 symmetric to ES256 over
admin's JWKS. ConfigurationResolver and CorsConfigurationValidator
added under src/Infrastructure/. ADR-002 and ADR-006 retired; SEC-01,
SEC-02, SEC-03 marked Closed. One new testability risk recorded in
architecture.md Open Risks Section 6 (JWKS HTTPS gating).

Source changes:
- src/Auth/JwtExtensions.cs (modified) — ES256, JWKS, alg pinning
- src/Program.cs (modified) — DI wiring for ConfigurationResolver
  and CorsConfigurationValidator
- src/Controllers/AuthController.cs (deleted) — no in-service issuance
- src/Services/TokenService.cs (deleted) — same
- src/Infrastructure/ConfigurationResolver.cs (new)
- src/Infrastructure/CorsConfigurationValidator.cs (new)
- .env.example (new) — required env var documentation
- .gitignore (updated)

Cross-repo coordination: _docs/cross-repo/flights_h1_h2_h3_change_spec
captures the change-spec for downstream services that consumed the now
deleted /auth endpoints.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-14 20:19:05 +03:00

87 lines
6.7 KiB
Markdown

# 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. |