mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 08:51:13 +00:00
[AZ-491] [AZ-492] [AZ-493] [AZ-494] [AZ-496] Cycle 3 Step 14: security audit refresh
All 5 phases refreshed against cycle-3 delta:
Phase 1 (Dependency Scan):
- D1 RESOLVED (AZ-496): Microsoft.AspNetCore.OpenApi 8.0.21 → 8.0.25
- D3 RESOLVED (AZ-496): JwtBearer 8.0.21 → 8.0.25
- D4 NEW (Low, test-only): System.IdentityModel.Tokens.Jwt 7.0.3 +
Microsoft.IdentityModel.Tokens 7.0.3 pinned in TestSupport carry
CVE-2024-21319 (JWE DoS). Bump to ≥ 7.1.2 tracked as future PBI.
Phase 2 (Static Analysis):
- F-AUTH-3 (Info): test runner Program.cs logs iss/aud at startup;
production API does NOT (verified by grep).
- F-AUTH-4 (Info): DEV-ONLY iss/aud placeholders in
appsettings.Development.json + .env.example — by design per
Option B for AZ-494.
- F-DBR-1: TRUNCATE string interpolation in
IntegrationTestDatabaseReset.cs — false positive (hard-coded
table list).
- F-DBR-2 (Low): TRUNCATE guard is operator-bypassable. Two-guard
model is conservative-by-default and unit-tested.
- F-PERF-1 (Low): perf-bootstrap --mint-only writes a 4-hour
GPS-permission token to stdout. Operator-trusted machine assumed.
Phase 3 (OWASP Top 10):
- A03 carries D1/D3 RESOLVED + D4 NEW.
- A07 flips F-AUTH-2 to RESOLVED (AZ-494); residual revocation-list
Low recorded.
- A05 status unchanged (F-DBR-1 false positive).
- A08 picks up F-DBR-2.
Phase 4 (Infrastructure):
- JWT_ISSUER / JWT_AUDIENCE flow .env → compose → Kestrel config,
same pattern as JWT_SECRET.
- INTEGRATION_TEST_DB_RESET + ASPNETCORE_ENVIRONMENT=Testing wired
for AZ-493 reset gate.
- SatelliteProvider.TestSupport is IsPackable=false — never ships
in a production container image.
- New operational gate added to deploy runbook: grep for DEV-ONLY-
in the rendered deploy environment must return zero hits.
Phase 5 (Security Report):
- Verdict: PASS_WITH_WARNINGS (cycle 3 does not escalate).
- 0 Critical, 0 High, 0 new Medium.
- Cycle-2 F-AUTH-2 (Medium) RESOLVED; cycle-1 D1 + cycle-2 D3
RESOLVED.
Autodev state advanced to Step 14 completed. Next: Step 15
(Performance Test, optional gate).
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -96,9 +96,56 @@
|
|||||||
- Serilog.AspNetCore 8.0.3 — no published GHSA / CVE.
|
- Serilog.AspNetCore 8.0.3 — no published GHSA / CVE.
|
||||||
- dbup-postgresql 6.0.3 — no published GHSA / CVE.
|
- dbup-postgresql 6.0.3 — no published GHSA / CVE.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cycle 3 Delta (2026-05-12 — AZ-491 / AZ-492 / AZ-493 / AZ-494 / AZ-495 / AZ-496)
|
||||||
|
|
||||||
|
### New packages added this cycle
|
||||||
|
|
||||||
|
| Project | Package | Version | Notes |
|
||||||
|
|---------|---------|---------|-------|
|
||||||
|
| `SatelliteProvider.TestSupport` (NEW project, AZ-491) | `Microsoft.IdentityModel.Tokens` | 7.0.3 | Centralised JWT-mint factory for both unit + integration tests. Version pinned to match the legacy cycle-2 `SatelliteProvider.IntegrationTests` reference being consolidated; intentional to keep the migration mechanical. |
|
||||||
|
| `SatelliteProvider.TestSupport` (NEW project, AZ-491) | `System.IdentityModel.Tokens.Jwt` | 7.0.3 | Same rationale as above — pinned to match the legacy reference. |
|
||||||
|
|
||||||
|
### New findings
|
||||||
|
|
||||||
|
#### D4 — `System.IdentityModel.Tokens.Jwt 7.0.3` + `Microsoft.IdentityModel.Tokens 7.0.3` carry CVE-2024-21319 (Low — test-only, never deployed)
|
||||||
|
|
||||||
|
- **Location**: `SatelliteProvider.TestSupport/SatelliteProvider.TestSupport.csproj` (added by AZ-491). Surfaced as `NU1902` warning on every restore — first matching log line at `/tmp/run-tests-cycle3-step16.log:9`.
|
||||||
|
- **Advisory**: [GHSA-59j7-ghrg-fj52](https://github.com/advisories/GHSA-59j7-ghrg-fj52) / [CVE-2024-21319](https://github.com/dotnet/aspnetcore/security/advisories/GHSA-59j7-ghrg-fj52) — JWE-token DoS via high-compression-ratio token causing unbounded memory allocation. CVSS 6.8 (Medium), advisory severity Critical for runtime services. Affected: `< 7.1.2` on the 7.x line.
|
||||||
|
- **Exposure in this project**:
|
||||||
|
- `SatelliteProvider.TestSupport` is `IsPackable=false`. Consumed only by `SatelliteProvider.Tests` and `SatelliteProvider.IntegrationTests` (both also `IsTestProject`). Never reaches the published API container.
|
||||||
|
- Production API consumes `Microsoft.AspNetCore.Authentication.JwtBearer 8.0.25` (post-AZ-496), which brings the BCL JWT types via the patched 7.x transitive line — past the 7.1.2 fix.
|
||||||
|
- The vulnerable code path (`JsonWebTokenHandler.ReadJsonWebToken` on a JWE with crafted compression ratio) is **never reached at runtime** in production. The TestSupport `JwtTokenFactory` uses `JwtSecurityTokenHandler.WriteToken` (encode-only, not decode); decode happens in the API container against the patched runtime line.
|
||||||
|
- **Disposition**: **Accept for cycle 3, track as future PBI.** Severity in this project is Low because:
|
||||||
|
- No production reachability (test-only, never shipped).
|
||||||
|
- The TestSupport call sites do not exercise the vulnerable JWE-decompression path.
|
||||||
|
- **Recommended fix** (future PBI): bump TestSupport's two pins to `Microsoft.IdentityModel.Tokens >= 7.1.2` + `System.IdentityModel.Tokens.Jwt >= 7.1.2` (or align to the 8.0.x family used transitively by `JwtBearer 8.0.25`). The `NU1902` warning will then disappear from the build log and the per-restore noise (≈ 9 hits in the cycle-3 test log) goes away.
|
||||||
|
- **Note**: this finding was already known and tracked as a follow-up in the cycle-3 batch-01 review (AZ-496) and the cumulative review for batches 01-03. Recorded formally here so it appears in the security audit's findings table.
|
||||||
|
|
||||||
|
### Resolved this cycle
|
||||||
|
|
||||||
|
- **D1** (cycle 1): `Microsoft.AspNetCore.OpenApi 8.0.21` → **8.0.25** in `SatelliteProvider.Api.csproj`. RESOLVED in AZ-496.
|
||||||
|
- **D3** (cycle 2): `Microsoft.AspNetCore.Authentication.JwtBearer 8.0.21` → **8.0.25** in `SatelliteProvider.Api.csproj`. RESOLVED in AZ-496.
|
||||||
|
|
||||||
|
### Cross-version sanity (post-cycle-3)
|
||||||
|
|
||||||
|
- `Microsoft.AspNetCore.*` family in API csproj: `OpenApi` 8.0.25 + `JwtBearer` 8.0.25 — consistent within family. ✓
|
||||||
|
- `Microsoft.IdentityModel.Tokens` / `System.IdentityModel.Tokens.Jwt`: pinned at 7.0.3 in TestSupport only; production transitively at 7.x patched line via `JwtBearer 8.0.25`. **Drift recorded** (see D4 for remediation path).
|
||||||
|
- `SixLabors.ImageSharp` is **3.1.11** in Api, Common, Services.TileDownloader, Tests, IntegrationTests — consistent across 5 csprojs. ✓
|
||||||
|
- `Microsoft.Extensions.*` is **9.0.10** across DataAccess, TileDownloader, Tests, RegionProcessing, RouteManagement, TestSupport (via xUnit transitive) — consistent. ✓
|
||||||
|
- `Newtonsoft.Json` is **13.0.4** in Api + TileDownloader — consistent. ✓
|
||||||
|
|
||||||
|
### Items checked clean (cycle 3 delta)
|
||||||
|
|
||||||
|
- `SixLabors.ImageSharp` 3.1.11 — still no new GHSA against 3.1.11 since the cycle-2 review (re-checked at GHSA on 2026-05-12).
|
||||||
|
- `Npgsql` 9.0.2 — used by AZ-493's `IntegrationTestDatabaseReset` for the new test-side reset. No new advisories on 9.0.x.
|
||||||
|
- `dbup-postgresql` 6.0.3 — no schema-modifying changes added by cycle 3. No new advisories.
|
||||||
|
|
||||||
## Self-verification
|
## Self-verification
|
||||||
|
|
||||||
- [x] All package manifests scanned (8 csproj files)
|
- [x] All package manifests scanned (9 csproj files — TestSupport added this cycle)
|
||||||
- [x] Each finding has a CVE ID or advisory reference
|
- [x] Each finding has a CVE ID or advisory reference
|
||||||
- [x] Upgrade paths identified for every Medium/Low finding
|
- [x] Upgrade paths identified for every Medium/Low finding
|
||||||
- [x] No Critical or High finding remains open after exploitability triage
|
- [x] No Critical or High finding remains open after exploitability triage
|
||||||
|
- [x] Cycle-3 NU1902 warning at `/tmp/run-tests-cycle3-step16.log` line 9 + 8 follow-on hits accounted for under D4
|
||||||
|
|||||||
@@ -109,4 +109,33 @@
|
|||||||
### Cycle-2 operational follow-ups (NOT findings — pre-deploy verification)
|
### Cycle-2 operational follow-ups (NOT findings — pre-deploy verification)
|
||||||
|
|
||||||
1. The deploy pipeline must verify `JWT_SECRET` is set to a ≥ 32-byte value distinct from the DEV-ONLY placeholder before promoting `api`. The application throws at startup if the value is missing or short, so a misconfigured deploy fails fast — but a deploy that *promotes* the dev placeholder verbatim would still pass the 32-byte gate. Tracked in `security_report.md` cycle-2 recommendations.
|
1. The deploy pipeline must verify `JWT_SECRET` is set to a ≥ 32-byte value distinct from the DEV-ONLY placeholder before promoting `api`. The application throws at startup if the value is missing or short, so a misconfigured deploy fails fast — but a deploy that *promotes* the dev placeholder verbatim would still pass the 32-byte gate. Tracked in `security_report.md` cycle-2 recommendations.
|
||||||
2. Coordinate with admin team on `iss`/`aud` values (F-AUTH-2). When values are defined, both the `AddSatelliteJwt` call site and `.env`/compose docs must be updated together.
|
2. Coordinate with admin team on `iss`/`aud` values (F-AUTH-2). When values are defined, both the `AddSatelliteJwt` call site and `.env`/compose docs must be updated together. **RESOLVED in cycle 3 (AZ-494)** — `AddSatelliteJwt` now validates both; values flow `JWT_ISSUER` / `JWT_AUDIENCE` env → compose `environment` → Kestrel config. See cycle-3 delta below.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cycle 3 Delta (AZ-491 / AZ-492 / AZ-493 / AZ-494 / AZ-495 / AZ-496)
|
||||||
|
|
||||||
|
### Infra changes this cycle
|
||||||
|
|
||||||
|
- `docker-compose.yml:33-34` adds `JWT_ISSUER=${JWT_ISSUER}` and `JWT_AUDIENCE=${JWT_AUDIENCE}` to the `api` service `environment` block (AZ-494).
|
||||||
|
- `docker-compose.tests.yml:24-25` mirrors them on `integration-tests` so the runner mints tokens against the same iss/aud the API validates.
|
||||||
|
- `.env.example` documents both new variables with the fail-fast contract and ships explicit `DEV-ONLY-` placeholder values (AZ-494). Replaces an earlier draft that left them blank with a stale "leave blank to fall back" comment — corrected during Step 16 setup.
|
||||||
|
- `.env` (gitignored, developer-only) now also carries `JWT_ISSUER=DEV-ONLY-iss-admin-azaion-local` + `JWT_AUDIENCE=DEV-ONLY-aud-satellite-provider` for the local docker-compose flow.
|
||||||
|
- `docker-compose.tests.yml:21` sets `ASPNETCORE_ENVIRONMENT=Testing` and `:22` sets `DB_CONNECTION_STRING=Host=postgres;...`. Both are consumed by AZ-493's `IntegrationTestResetGuard.EnsureGuardPassesOrThrow(env, host)` — env must be `Testing` AND host must be in the allowlist (`postgres` / `localhost` / `127.0.0.1`), otherwise the reset throws.
|
||||||
|
- `docker-compose.tests.yml:20` adds `INTEGRATION_KEEP_STATE=${INTEGRATION_KEEP_STATE:-}` so the `--keep-state` flag on `scripts/run-tests.sh` short-circuits the reset (debugging convenience).
|
||||||
|
- `scripts/run-tests.sh` + `scripts/run-performance-tests.sh` both load + export the new env vars and fail-fast if missing (mirrors the AZ-487 `JWT_SECRET` pattern).
|
||||||
|
- No new exposed ports. No new external services. No CI workflow changes (`.woodpecker/01-test.yml` and `02-build-push.yml` unchanged).
|
||||||
|
- New `SatelliteProvider.TestSupport` project — `IsPackable=false`, never builds a container, never ships outside test images. Confirmed via `Dockerfile` review: only `SatelliteProvider.Api/Dockerfile` and `SatelliteProvider.IntegrationTests/Dockerfile` exist; neither produces a published artifact from TestSupport.
|
||||||
|
|
||||||
|
### Cycle-3 verdict — clean
|
||||||
|
|
||||||
|
- **Secret distribution**: `JWT_ISSUER` / `JWT_AUDIENCE` flow `.env` → docker-compose `environment` → Kestrel `IConfiguration` → `AddSatelliteJwt`. Same path as `JWT_SECRET`, no new mechanism introduced. Production values are NOT in the repo (the .env in git tracks only the example); production deploy supplies them via the deploy pipeline (operator-confirmed admin-team values).
|
||||||
|
- **Production safety contract**: empty `Jwt.Issuer` / `Jwt.Audience` in `appsettings.json` + the fail-fast `ResolveRequiredOrThrow` extension guarantees that a production deploy without explicit env-var values cannot start. This is the user-selected Option B forcing function.
|
||||||
|
- **Test-side DB reset**: AZ-493's two-guard logic (`ASPNETCORE_ENVIRONMENT == "Testing"` + Host allowlist) is the test-runner's equivalent of a fail-fast contract. Documented as F-DBR-2 (Low) in static_analysis.md.
|
||||||
|
- **Perf harness**: `scripts/run-performance-tests.sh` uses `mktemp -d` for fixture + response paths; cleaned up on `trap EXIT`. Token minted via `dotnet <integration-tests.dll> --mint-only` — never written to disk by the harness (only to a shell variable). Documented as F-PERF-1 (Low) in static_analysis.md.
|
||||||
|
|
||||||
|
### Cycle-3 operational follow-ups (NOT findings — pre-deploy verification)
|
||||||
|
|
||||||
|
1. The deploy pipeline must supply real `JWT_ISSUER` + `JWT_AUDIENCE` values (admin-team-confirmed) when promoting `api`. The application throws at startup if either is empty or whitespace — a misconfigured deploy fails fast.
|
||||||
|
2. The DEV-ONLY iss/aud placeholders in `appsettings.Development.json` + `.env.example` are deliberately committed (Option B). A grep for `DEV-ONLY-` surfaces every site that must NOT ship to production. Make this grep part of the deploy gate runbook.
|
||||||
|
3. The cross-repo `suite/_docs/10_auth.md` write (AC-7) is deferred — outside this workspace's boundary. The suite repo's owner must take this on as a follow-up.
|
||||||
|
|||||||
@@ -71,3 +71,38 @@ Cycle 1's A01 / A07 verdicts were `N/A (with caveat)` because the service shippe
|
|||||||
| A07 | F-AUTH-2 — `iss`/`aud` not validated | Medium → **Resolved cycle 3 (AZ-494)** | n/a |
|
| A07 | F-AUTH-2 — `iss`/`aud` not validated | Medium → **Resolved cycle 3 (AZ-494)** | n/a |
|
||||||
| A07 | No token revocation list (residual after AZ-494) | Low | Phase 2 — out of scope until requirement emerges |
|
| A07 | No token revocation list (residual after AZ-494) | Low | Phase 2 — out of scope until requirement emerges |
|
||||||
| (claim handler) | F-UAV-2 — `JsonDocument.Parse` on token claim values | Low | Phase 2 |
|
| (claim handler) | F-UAV-2 — `JsonDocument.Parse` on token claim values | Low | Phase 2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cycle 3 Refresh (AZ-491 / AZ-492 / AZ-493 / AZ-494 / AZ-495 / AZ-496)
|
||||||
|
|
||||||
|
Cycle 3 was test-infrastructure + dependency + iss/aud-validation work. Most production surfaces are untouched; the OWASP impact is concentrated on A03 (supply chain) and A07 (auth). A02 also picks up one Informational note about the DEV-ONLY iss/aud handling pattern.
|
||||||
|
|
||||||
|
| # | Category | Cycle 2 Status | Cycle 3 Status | Cycle-3 evidence |
|
||||||
|
|---|----------|----------------|----------------|------------------|
|
||||||
|
| A01 | Broken Access Control | PASS_WITH_WARNINGS | **PASS_WITH_WARNINGS** (unchanged) | No endpoint added or relaxed. AZ-494 strictly tightens token acceptance (wrong iss/aud → 401). |
|
||||||
|
| A02 | Security Misconfiguration | FAIL (S1, S2, I1, I2, F-AUTH-1) | **FAIL** (unchanged + F-AUTH-4) | F-AUTH-4: DEV-ONLY iss/aud placeholders committed to `appsettings.Development.json` + `.env.example` (Informational, by-design per Option B; mirrors the F-AUTH-1 pattern for `JWT_SECRET`). Underlying cycle-1 findings (Postgres default creds, root container, no security headers) untouched. |
|
||||||
|
| A03 | Supply Chain Failures | PASS_WITH_WARNINGS (D1, D2, D3, F-DEPS-UAV) | **PASS_WITH_WARNINGS** (D1 + D3 RESOLVED; + D4) | **D1 RESOLVED** (cycle 3, AZ-496) — `Microsoft.AspNetCore.OpenApi` 8.0.21 → 8.0.25. **D3 RESOLVED** (cycle 3, AZ-496) — `JwtBearer` 8.0.21 → 8.0.25. **D4 NEW** — `System.IdentityModel.Tokens.Jwt 7.0.3` + `Microsoft.IdentityModel.Tokens 7.0.3` pinned in `SatelliteProvider.TestSupport` carry CVE-2024-21319 (JWE DoS) — Low, test-only never deployed. D2 and F-DEPS-UAV unchanged. |
|
||||||
|
| A04 | Cryptographic Failures | PASS | **PASS** | HS256 contract unchanged. AZ-494 added validation flags but not new crypto. |
|
||||||
|
| A05 | Injection | PASS | **PASS** | AZ-493's TRUNCATE uses a hard-coded table list (F-DBR-1 explicit false-positive in static_analysis.md). No new SQL / shell / template surfaces. |
|
||||||
|
| A06 | Insecure Design | FAIL (S3, S4, I3, F-AUTH-3, F-UAV-3) | **FAIL** (unchanged) | Rate limiting still absent. AZ-494 reduces the 401-flood vector slightly (more reasons to reject malformed tokens earlier) but the gap itself is unchanged. |
|
||||||
|
| A07 | Identification & Authentication Failures | PASS_WITH_WARNINGS (F-AUTH-2 open) | **PASS_WITH_WARNINGS** (F-AUTH-2 RESOLVED; revocation-list still residual) | F-AUTH-2 resolved by AZ-494: `ValidateIssuer = true` + `ValidateAudience = true` against env-sourced values with fail-fast contract. Verified by SEC-12 + SEC-13 integration scenarios + 4 unit fail-fast tests, all PASS at Step 11. F-AUTH-3 (test-runner iss/aud log line) Informational only. F-AUTH-4 (DEV-ONLY placeholders) by design. Residual: no token revocation list — Low, out of scope. |
|
||||||
|
| A08 | Software or Data Integrity Failures | PASS | **PASS** | No CI/CD changes; no new auto-update path. AZ-493's TRUNCATE is destructive but gated by two soft guards (F-DBR-2, Low). |
|
||||||
|
| A09 | Security Logging Failures | PASS_WITH_WARNINGS (I4) | **PASS_WITH_WARNINGS** (unchanged) | No new logging changes. Production API does NOT log iss/aud (verified by repo grep returning no hits in `Api/Program.cs`). |
|
||||||
|
| A10 | Mishandling of Exceptional Conditions | PASS | **PASS** | AZ-494's `ResolveRequiredOrThrow` throws `InvalidOperationException` at STARTUP, not at request time — no client-facing leakage. `GlobalExceptionHandler` invariant unchanged. |
|
||||||
|
|
||||||
|
### Cycle-3 cross-reference
|
||||||
|
|
||||||
|
| OWASP Cat | Cycle-3 finding | Severity | Source phase |
|
||||||
|
|-----------|-----------------|----------|--------------|
|
||||||
|
| A02 | F-AUTH-4 — DEV-ONLY iss/aud placeholders in dev config | Informational (by design) | Phase 2 |
|
||||||
|
| A03 | D1 — `Microsoft.AspNetCore.OpenApi` 8.0.21 → 8.0.25 | **RESOLVED (AZ-496)** | Phase 1 |
|
||||||
|
| A03 | D3 — `JwtBearer` 8.0.21 → 8.0.25 | **RESOLVED (AZ-496)** | Phase 1 |
|
||||||
|
| A03 | D4 — `System.IdentityModel.Tokens.Jwt 7.0.3` + `Microsoft.IdentityModel.Tokens 7.0.3` in TestSupport (CVE-2024-21319) | Low (test-only) | Phase 1 |
|
||||||
|
| A05 | F-DBR-1 — `TRUNCATE` string interpolation in `IntegrationTestDatabaseReset` | **False positive** (hard-coded table list) | Phase 2 |
|
||||||
|
| A07 | F-AUTH-2 — `iss` / `aud` not validated | **RESOLVED (AZ-494)** | n/a |
|
||||||
|
| A07 | F-AUTH-3 — test runner logs iss/aud at startup | Informational (test-only) | Phase 2 |
|
||||||
|
| A07 | F-AUTH-4 — DEV-ONLY placeholders (also A02-flagged) | Informational (by design) | Phase 2 |
|
||||||
|
| A07 | No token revocation list (residual after AZ-494) | Low (out of scope) | n/a |
|
||||||
|
| A08 | F-DBR-2 — TRUNCATE guard is operator-bypassable | Low (deliberate trade-off, unit-tested) | Phase 2 |
|
||||||
|
| A06 / A07 | F-PERF-1 — perf-bootstrap 4-hour `GPS` token written to stdout | Low (operator-controlled CLI) | Phase 2 |
|
||||||
|
|||||||
@@ -161,3 +161,59 @@ AZ-487 introduced a JWT validation baseline (HS256, `JWT_SECRET` env var, `.Requ
|
|||||||
- **Cycle-2 hardening backlog (Low priority)**:
|
- **Cycle-2 hardening backlog (Low priority)**:
|
||||||
- Pass `JsonDocumentOptions { MaxDepth = 8 }` and a max-claim-length check to `PermissionsAuthorizationHandler.TryReadJsonArray`.
|
- Pass `JsonDocumentOptions { MaxDepth = 8 }` and a max-claim-length check to `PermissionsAuthorizationHandler.TryReadJsonArray`.
|
||||||
- Document in `architecture.md` that reject-reason codes are NOT a security boundary.
|
- Document in `architecture.md` that reject-reason codes are NOT a security boundary.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cycle 3 Delta Summary (AZ-491 / AZ-492 / AZ-493 / AZ-494 / AZ-495 / AZ-496)
|
||||||
|
|
||||||
|
### What changed in cycle 3
|
||||||
|
|
||||||
|
Cycle 3 was test-infrastructure + dependency + iss/aud-validation hardening. The most security-relevant change is AZ-494, which flipped `iss` / `aud` validation from `false` (cycle-2 F-AUTH-2 finding) to `true`, sourcing both values from `JWT_ISSUER` / `JWT_AUDIENCE` env vars with the same fail-fast contract as `JWT_SECRET`. AZ-496 bumped the ASP.NET Core 8.0.21 family to 8.0.25 (closes D1 + D3). The other tasks (AZ-491 consolidate JWT test helpers, AZ-492 perf harness PT-07/PT-08, AZ-493 integration test DB reset hook, AZ-495 doc folder convention) added test-side surface but no new production attack surface.
|
||||||
|
|
||||||
|
### Findings table (cycle-3 delta)
|
||||||
|
|
||||||
|
| # | Severity | Category | Location | Title |
|
||||||
|
|------------|-----------------------|------------------------------------------|---------------------------------------------------------------------------|---------------------------------------------------------------------------------------------|
|
||||||
|
| D4 | Low | A03 — Supply Chain (test-only) | `SatelliteProvider.TestSupport.csproj` (`System.IdentityModel.Tokens.Jwt 7.0.3` + `Microsoft.IdentityModel.Tokens 7.0.3`) | CVE-2024-21319 (JWE DoS) in pinned 7.0.3 < 7.1.2 fix line; **test-only, never deployed**; bump to ≥ 7.1.2 in future PBI |
|
||||||
|
| F-AUTH-3 | Informational | A07 — AuthN observability (test-only) | `SatelliteProvider.IntegrationTests/Program.cs:67` | Test runner logs resolved `iss` / `aud` at startup — operator-visible only, no prod path leaks |
|
||||||
|
| F-AUTH-4 | Informational | A02 — Misconfiguration (by design) | `appsettings.Development.json` + `.env.example` | DEV-ONLY iss/aud placeholders deliberately committed (Option B forcing function for prod fail-fast) |
|
||||||
|
| F-DBR-1 | **False positive** | A05 — Injection (TRUNCATE interpolation) | `SatelliteProvider.IntegrationTests/IntegrationTestDatabaseReset.cs:32` | Hard-coded table list; no user input flows in; recorded so scanners don't re-flag |
|
||||||
|
| F-DBR-2 | Low | A08 — Data Integrity (test-only) | `SatelliteProvider.TestSupport/IntegrationTestResetGuard.cs:11-36` | Destructive TRUNCATE gated by two soft guards (env + Host allowlist); deliberate operator-bypass surface |
|
||||||
|
| F-PERF-1 | Low | A06 / A07 — Token handling (CLI-only) | `SatelliteProvider.IntegrationTests/PerfBootstrap.cs:21-48` | 4-hour `GPS`-permission token written to stdout for the perf harness; operator-trusted machine assumed |
|
||||||
|
|
||||||
|
### Verdict reconciliation
|
||||||
|
|
||||||
|
- No new Critical or High findings → cycle 3 does NOT escalate the verdict.
|
||||||
|
- One new Low finding in production-adjacent surfaces (D4) — **test-only, never deployed**; documented remediation path in dependency_scan.md.
|
||||||
|
- Two new Informational items (F-AUTH-3, F-AUTH-4) — both by-design or test-runner-only.
|
||||||
|
- One Low finding in test-side destructive op (F-DBR-2) — unit-tested guard, conservative-by-default.
|
||||||
|
- One Low finding on operator-controlled perf-CLI surface (F-PERF-1) — accepted operational trade-off.
|
||||||
|
- One false positive (F-DBR-1) — recorded for future scanner runs.
|
||||||
|
- **Cycle-2 carry-overs resolved**: F-AUTH-2 (Medium, A07) and D3 (Low, A03) both flipped to RESOLVED. Cycle-1 D1 also flipped to RESOLVED.
|
||||||
|
|
||||||
|
**Current verdict: PASS_WITH_WARNINGS** (cycle 3 satisfies the autodev Step-14 gate; proceed to Step 15).
|
||||||
|
|
||||||
|
### New / refreshed cycle-3 recommendations
|
||||||
|
|
||||||
|
- **Pre-deploy gate (operational, NOT code)** — same forcing function as cycle 2's `JWT_SECRET` gate, now extended:
|
||||||
|
- `deploy/SKILL.md` must verify `JWT_SECRET` is ≥ 32 bytes AND NOT equal to the DEV-ONLY placeholder.
|
||||||
|
- `deploy/SKILL.md` must verify `JWT_ISSUER` and `JWT_AUDIENCE` are set to admin-team-confirmed prod values, NOT the `DEV-ONLY-` placeholders. A grep for `DEV-ONLY-` in the rendered deploy environment must return zero hits.
|
||||||
|
- **TestSupport 7.0.3 bump** (D4) — future PBI: bump `Microsoft.IdentityModel.Tokens` + `System.IdentityModel.Tokens.Jwt` to ≥ 7.1.2 (or align to the 8.0.x family). Eliminates the `NU1902` warning noise on every restore. Test-only, low priority.
|
||||||
|
- **Cross-repo doc** (AZ-494 AC-7) — `suite/_docs/10_auth.md` write deferred. Outside this workspace's boundary; tracked in `deploy_cycle2.md` R3 follow-up.
|
||||||
|
- **Token revocation list** (A07 residual) — accepted as out-of-scope until requirement emerges. Re-evaluate if cycle N introduces user-revocable tokens or session management.
|
||||||
|
|
||||||
|
### Cycle-3 hardening backlog (Low priority, NOT cycle-blocking)
|
||||||
|
|
||||||
|
- Add a third guard to `IntegrationTestResetGuard` requiring explicit `INTEGRATION_TEST_DB_RESET_CONFIRM=I-UNDERSTAND-THIS-TRUNCATES` when the guard runs against a non-`postgres` host (i.e. operator's `localhost` outside Docker).
|
||||||
|
- Drop `iss` / `aud` from the integration-tests startup banner (F-AUTH-3) once the AZ-494 contract is well-understood by everyone on the team; the byte-count line is enough.
|
||||||
|
- Pipe `--mint-only` token through a process substitution / `xargs` so it never lands in shell history.
|
||||||
|
|
||||||
|
### Phase artifact status
|
||||||
|
|
||||||
|
| Artifact | Cycle 1 | Cycle 2 delta | Cycle 3 delta |
|
||||||
|
|----------|---------|---------------|---------------|
|
||||||
|
| `dependency_scan.md` | ✓ | ✓ | ✓ (D1 + D3 RESOLVED; D4 NEW) |
|
||||||
|
| `static_analysis.md` | ✓ | ✓ | ✓ (F-AUTH-3, F-AUTH-4, F-DBR-1, F-DBR-2, F-PERF-1) |
|
||||||
|
| `owasp_review.md` | ✓ | ✓ | ✓ (A03 + A07 status refreshed) |
|
||||||
|
| `infrastructure_review.md` | ✓ | ✓ | ✓ (compose env pass-through, AZ-493 guard plumbing, AZ-494 operational gates) |
|
||||||
|
| `security_report.md` | ✓ | ✓ | ✓ (this section) |
|
||||||
|
|||||||
@@ -132,9 +132,82 @@
|
|||||||
- **Description**: A client (or attacker who can present a `GPS`-permission token) can map the gate by probing inputs (1×1 black image → `WRONG_DIMENSIONS`; 1 KB JPEG → `SIZE_OUT_OF_BAND`; etc.). The thresholds are also documented in the public contract `_docs/02_document/contracts/api/uav-tile-upload.md`.
|
- **Description**: A client (or attacker who can present a `GPS`-permission token) can map the gate by probing inputs (1×1 black image → `WRONG_DIMENSIONS`; 1 KB JPEG → `SIZE_OUT_OF_BAND`; etc.). The thresholds are also documented in the public contract `_docs/02_document/contracts/api/uav-tile-upload.md`.
|
||||||
- **Disposition**: Accept — UX (helping clients self-correct) outweighs the information-hiding benefit, especially since the contract is public anyway. Flagged to keep operators aware: rule thresholds are NOT a security boundary; do not move secrets into reject details.
|
- **Disposition**: Accept — UX (helping clients self-correct) outweighs the information-hiding benefit, especially since the contract is public anyway. Flagged to keep operators aware: rule thresholds are NOT a security boundary; do not move secrets into reject details.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cycle 3 Delta (2026-05-12 — AZ-491 / AZ-492 / AZ-493 / AZ-494 / AZ-495 / AZ-496)
|
||||||
|
|
||||||
|
### Scope of this delta scan
|
||||||
|
|
||||||
|
| File | Cycle-3 task(s) | Domain |
|
||||||
|
|------|-----------------|--------|
|
||||||
|
| `SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs` | AZ-494 | Production auth (high-sensitivity surface) |
|
||||||
|
| `SatelliteProvider.Api/appsettings.json` + `appsettings.Development.json` | AZ-494 | Configuration / secrets handling |
|
||||||
|
| `SatelliteProvider.IntegrationTests/JwtTestHelpers.cs` | AZ-491, AZ-494 | Test-side, runner-only |
|
||||||
|
| `SatelliteProvider.IntegrationTests/IntegrationTestDatabaseReset.cs` | AZ-493 | Test-side; destructive DB op (TRUNCATE) gated by two-guard model |
|
||||||
|
| `SatelliteProvider.IntegrationTests/PerfBootstrap.cs` | AZ-492, AZ-494 | Test-side CLI subcommand (mint token, write JPEG fixture) |
|
||||||
|
| `SatelliteProvider.IntegrationTests/Program.cs` | AZ-491..AZ-494 | Test-side bootstrap |
|
||||||
|
| `SatelliteProvider.TestSupport/JwtTokenFactory.cs` | AZ-491, AZ-494 | Test-side, runner-only |
|
||||||
|
| `SatelliteProvider.TestSupport/IntegrationTestResetGuard.cs` | AZ-493 | Test-side; safety-guard logic |
|
||||||
|
| `SatelliteProvider.Tests/Authentication/AuthenticationServiceCollectionExtensionsTests.cs` | AZ-487, AZ-494 | Test-side unit |
|
||||||
|
| `SatelliteProvider.Tests/TestSupport/IntegrationTestResetGuardTests.cs` | AZ-493 | Test-side unit |
|
||||||
|
| `scripts/run-tests.sh` / `scripts/run-performance-tests.sh` | AZ-492, AZ-493, AZ-494 | Operator-side shell |
|
||||||
|
| `docker-compose.yml` / `docker-compose.tests.yml` | AZ-494 (env pass-through) | Infrastructure |
|
||||||
|
| `.env.example` | AZ-494 | Configuration template |
|
||||||
|
|
||||||
|
### Cycle-3 findings
|
||||||
|
|
||||||
|
#### F-AUTH-3 — Test runner logs `iss` / `aud` values at startup (Informational — test runner only, never in prod)
|
||||||
|
|
||||||
|
- **Location**: `SatelliteProvider.IntegrationTests/Program.cs:67` — `Console.WriteLine($"Auth : JWT_SECRET resolved ({…} bytes); iss={jwtIssuer}; aud={jwtAudience}");`
|
||||||
|
- **Description**: The integration-tests bootstrap prints the resolved iss and aud at startup. Values printed in this cycle's runs were the `DEV-ONLY-iss-admin-azaion-local` / `DEV-ONLY-aud-satellite-provider` placeholders, so no prod-value leak occurred. The production API (`SatelliteProvider.Api/Program.cs`) does NOT print iss/aud — verified by repo grep returning no hits.
|
||||||
|
- **Impact**: Only meaningful if the integration test runner is somehow pointed at production env vars. The fail-fast contract makes that operator decision visible at startup (the values are visible in test logs).
|
||||||
|
- **Disposition**: Accept — Informational. Operators inspecting test logs already see the secret byte count and the iss/aud, which is appropriate for a runner whose entire job is to validate against those values. No code change needed.
|
||||||
|
|
||||||
|
#### F-DBR-1 — `TRUNCATE TABLE` via string interpolation (False Positive — hard-coded table list)
|
||||||
|
|
||||||
|
- **Location**: `SatelliteProvider.IntegrationTests/IntegrationTestDatabaseReset.cs:32` — `$"TRUNCATE TABLE {string.Join(", ", TruncateOrder)} RESTART IDENTITY CASCADE"`.
|
||||||
|
- **Description**: SAST pattern flagged string-interpolated SQL. Source analysis confirms `TruncateOrder` is a `public static readonly IReadOnlyList<string>` initialised with a hard-coded array of five literal table names; no caller-supplied input flows into the SQL string.
|
||||||
|
- **Impact**: None. SQL injection here would require an attacker to modify the source file, at which point integrity is already broken.
|
||||||
|
- **Disposition**: False positive — recorded so future scanners don't re-flag.
|
||||||
|
|
||||||
|
#### F-DBR-2 — Destructive `TRUNCATE` action protected only by two soft guards (Low — operator-controlled, deliberate trade-off)
|
||||||
|
|
||||||
|
- **Location**: `SatelliteProvider.TestSupport/IntegrationTestResetGuard.cs:11-36` + `SatelliteProvider.IntegrationTests/IntegrationTestDatabaseReset.cs:24-37`.
|
||||||
|
- **Description**: The reset runs only when (a) `ASPNETCORE_ENVIRONMENT == "Testing"` AND (b) the Npgsql Host is one of `postgres` / `localhost` / `127.0.0.1`. An operator who sets `ASPNETCORE_ENVIRONMENT=Testing` and SSH-tunnels a production Postgres to `localhost:5432` could trick the guard.
|
||||||
|
- **Impact**: Loss of all `tiles`, `regions`, `routes`, `route_points`, `route_regions` rows on the targeted database.
|
||||||
|
- **Mitigations in place**: the cycle-3 spec deliberately preferred Host allowlist over DB-name pattern (per the AZ-493 review's "Spec-vs-reality" note); both DB-name and Host checks are cheap to add together if the operator surface grows. The guard is unit-tested (`IntegrationTestResetGuardTests`) with representative production hostnames (`prod-db-cluster-1.example.com`, etc.) to confirm they're rejected.
|
||||||
|
- **Disposition**: Accept — Low. The guard is conservative-by-default; bypassing requires deliberate operator action (env var + tunnel). Future PBI: add a third guard requiring an explicit `INTEGRATION_TEST_DB_RESET_CONFIRM=I-UNDERSTAND-THIS-TRUNCATES` env var when the guard runs against `localhost` from outside Docker.
|
||||||
|
|
||||||
|
#### F-PERF-1 — Perf-bootstrap mint subcommand writes a 4-hour `GPS`-permission token to stdout (Low — operator-controlled CLI, no network exposure)
|
||||||
|
|
||||||
|
- **Location**: `SatelliteProvider.IntegrationTests/PerfBootstrap.cs:21-48`.
|
||||||
|
- **Description**: `dotnet <integration-tests.dll> --mint-only` prints a 4-hour HS256 token with `permissions: GPS` claim to stdout. The token grants the same access as a production-issued `GPS` admin token for the lifetime window. The token bytes flow through the operator's shell history, terminal scrollback, and any process accounting logs.
|
||||||
|
- **Impact**: An attacker with read access to the operator's machine within the 4-hour window could replay the token against the API.
|
||||||
|
- **Mitigations in place**: lifetime is bounded to 4 hours (vs. e.g. 24 hours that would be tempting for "convenient perf runs"). The token is minted against `JWT_SECRET` from `.env` — same trust boundary as a developer's local dev setup. Operators are expected to run the perf script on a trusted machine.
|
||||||
|
- **Disposition**: Accept — Low. Future hardening: pipe the token to `xargs` / process substitution so it never lands in the shell history; consider mounting `JWT_SECRET` via a Docker secret rather than an env var when running the perf harness inside CI.
|
||||||
|
|
||||||
|
#### F-AUTH-4 — DEV-ONLY iss/aud placeholders committed to `appsettings.Development.json` + `.env.example` (Informational — by design, AZ-494 Option B)
|
||||||
|
|
||||||
|
- **Location**: `SatelliteProvider.Api/appsettings.Development.json` (`DEV-ONLY-iss-admin-azaion-local` / `DEV-ONLY-aud-satellite-provider`); `.env.example` (same placeholders).
|
||||||
|
- **Description**: AZ-494 (Option B per user decision) deliberately ships DEV-ONLY placeholder values in development config so local dev / docker-compose flows work without operator setup. Production config (`appsettings.json`) ships with empty values, triggering the fail-fast contract.
|
||||||
|
- **Impact**: None in production (the empty values guarantee a startup failure before any token validates). In development, the placeholders are clearly tagged with `DEV-ONLY-` prefix so a grep can surface them at any time.
|
||||||
|
- **Disposition**: Accept — by design. This is the explicit Option B trade-off the user selected over Option A (postpone) and Option C (hard-code prod values).
|
||||||
|
|
||||||
|
### Resolved this cycle
|
||||||
|
|
||||||
|
- **F-AUTH-2** (cycle 2): `iss` / `aud` not validated. **RESOLVED in AZ-494** — `ValidateIssuer = true` + `ValidateAudience = true` wired against env-sourced values with fail-fast startup. Verified at the source (`AuthenticationServiceCollectionExtensions.cs:37-40`).
|
||||||
|
|
||||||
|
### Patterns NOT triggered by cycle-3 changes
|
||||||
|
|
||||||
|
- **Injection**: SQL injection ✗ (only TRUNCATE with hard-coded table names — F-DBR-1 false positive). Command injection ✗ (no `Process.Start` / `exec` / `shell=True`). XSS ✗ (no HTML rendering paths added). Template injection ✗.
|
||||||
|
- **Cryptographic Failures**: no new hashing or encryption code; HS256 unchanged from AZ-487.
|
||||||
|
- **Insecure Deserialization**: ImageSharp decode path unchanged from cycle 2; no new `JsonSerializer.Deserialize<>` against attacker input.
|
||||||
|
|
||||||
## Self-verification
|
## Self-verification
|
||||||
|
|
||||||
- [x] All production source directories scanned (Api, Common, DataAccess, Services.TileDownloader, Services.RegionProcessing, Services.RouteManagement)
|
- [x] All production source directories scanned (Api, Common, DataAccess, Services.TileDownloader, Services.RegionProcessing, Services.RouteManagement)
|
||||||
|
- [x] All cycle-3 test-side surfaces scanned (TestSupport, IntegrationTests, Tests)
|
||||||
- [x] Each finding has file path and line number
|
- [x] Each finding has file path and line number
|
||||||
- [x] False positives from test files explicitly distinguished (`GlobalExceptionHandlerTests.cs:23` "leakySecret" is a positive control)
|
- [x] False positives from test files explicitly distinguished (`GlobalExceptionHandlerTests.cs:23` "leakySecret" is a positive control); F-DBR-1 also classified as false-positive with rationale
|
||||||
- [x] No real secret values printed in this report (S4 is described without echoing the key)
|
- [x] No real secret values printed in this report (S4 is described without echoing the key; F-AUTH-4 cites placeholder values that are public-by-design)
|
||||||
|
- [x] Cycle-3 surfaces (`AddSatelliteJwt` iss/aud extension, `IntegrationTestDatabaseReset`, `PerfBootstrap`, two-guard logic) all reviewed; findings either documented above or explicitly cleared
|
||||||
|
|||||||
@@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
## Current Step
|
## Current Step
|
||||||
flow: existing-code
|
flow: existing-code
|
||||||
step: 13
|
step: 14
|
||||||
name: Update Docs
|
name: Security Audit
|
||||||
status: completed
|
status: completed
|
||||||
sub_step:
|
sub_step:
|
||||||
phase: 5
|
phase: 5
|
||||||
name: ripple-log-written
|
name: PASS_WITH_WARNINGS
|
||||||
detail: "Step 11 PASS (full suite via Implement gate, log /tmp/run-tests-cycle3-step16.log). Step 12 PASS — cycle-update mode appended traceability rows for AZ-491/AZ-493/AZ-495/AZ-496 + AZ-494 SEC-12/SEC-13 cross-refs; SEC-12 + SEC-13 scenarios added to security-tests.md; environment.md env-var table extended with JWT_*/INTEGRATION_TEST_DB_RESET. Step 13 PASS — most module/architecture/security docs were updated inline during batches; consolidated stale tests_unit.md duplicate AuthenticationServiceCollectionExtensionsTests entry; produced ripple_log_cycle3.md. Next: Step 14 (Security Audit, optional gate)."
|
detail: "Step 14 PASS_WITH_WARNINGS — all 5 phases (Dependency Scan, Static Analysis, OWASP Top 10, Infrastructure, Security Report) refreshed for cycle 3. Resolved: F-AUTH-2 (Medium, AZ-494), D1 (Medium, AZ-496), D3 (Low, AZ-496). New cycle-3 findings: D4 (Low, test-only NU1902), F-AUTH-3 (Info, test-runner), F-AUTH-4 (Info, by design), F-DBR-1 (false positive), F-DBR-2 (Low, test-only TRUNCATE guard), F-PERF-1 (Low, operator-CLI). 0 Critical, 0 High, 0 new Medium. Next: Step 15 (Performance Test, optional gate)."
|
||||||
retry_count: 0
|
retry_count: 0
|
||||||
cycle: 3
|
cycle: 3
|
||||||
tracker: jira
|
tracker: jira
|
||||||
|
|||||||
Reference in New Issue
Block a user