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>
15 KiB
Phase 3 — OWASP Top 10:2025 Review
Date: 2026-05-11
OWASP version: OWASP Top 10:2025 (verified at audit start)
Project context: Self-hosted .NET 8 backend service. Documented as an "internal/trusted network service — no auth layer" (_docs/02_document/architecture.md §7). Deployed via Docker behind another network boundary (per _docs/02_document/deployment/). The audit is scoped to the codebase as it stands; categories whose findings depend on a missing trust-boundary control are flagged accordingly.
| # | Category | Status | Findings | Notes |
|---|---|---|---|---|
| A01 | Broken Access Control | N/A (with caveat) | — | The service intentionally exposes ALL endpoints without authentication or authorization — documented design (architecture.md §7). No IDOR analysis applies because there is no user concept. Caveat: this is only safe if the deployment puts the API behind a network-level gatekeeper (VPN, mTLS, internal-only LB). If the deploy ever moves to a public network, this category becomes the #1 risk and EVERY endpoint becomes an unauthenticated execution surface. |
| A02 | Security Misconfiguration | FAIL | S1, S2, I1, I2 | Default Postgres credentials in both appsettings.json and docker-compose.yml; Postgres port bound to 0.0.0.0; container runs as root; no security headers middleware. |
| A03 | Software Supply Chain Failures | PASS_WITH_WARNINGS | D1, D2 | Two known transitive CVEs (D1 — ASP.NET Core 8.0.21 SignalR DoS, not exploitable here; D2 — Microsoft.NET.Test.Sdk 17.8.0 → NuGet.Frameworks info disclosure, test-only). No use of unsigned NuGet packages; no auto-update of dependencies in production. |
| A04 | Cryptographic Failures | N/A | — | No password storage (no users), no encryption at rest, no in-app crypto. The Google Maps integration uses HTTPS (default Npgsql/HttpClient stacks). At-rest tile storage is plain JPEG by design — these are public satellite images, not confidential data. |
| A05 | Injection | PASS | — | All Dapper queries use parameter objects (new { Id = id } etc.); no string-built or interpolated user input flows into SQL. No Process.Start, no shell exec, no eval. JSON deserialization uses System.Text.Json defaults (no type-name handling). XSS / template injection N/A — JSON-only API. |
| A06 | Insecure Design | FAIL | S3, I3 | No rate limiting on any endpoint despite the existence of an outbound rate-limited dependency (Google Maps). Latitude / longitude inputs are not range-validated at the API boundary (S3). No quota / throttling on region-request creation, which can multiply outbound calls and disk writes. |
| A07 | Authentication Failures | N/A (with caveat) | — | Same caveat as A01 — there is no authentication system to fail. |
| A08 | Software or Data Integrity Failures | PASS | — | DbUp migrations are idempotent and tracked in schemaversions; rollback is forward-only by design. No auto-update path. CI artifacts go through .woodpecker/02-build-push.yml with from_secret: registry_token (not in plaintext). No unsigned external scripts executed at build/deploy. |
| A09 | Security Logging and Alerting Failures | PASS_WITH_WARNINGS | I4 | Serilog writes structured logs with file rotation; GlobalExceptionHandler correlates server logs to client responses via correlationId (good). However: no security-event logging (e.g., bad-input bursts, repeated 4xx from same source) and no alerting on log patterns. Acceptable for an internal service; would need attention if exposed publicly. |
| A10 | Mishandling of Exceptional Conditions | PASS | — | GlobalExceptionHandler returns RFC 7807 ProblemDetails with a generic body and a correlationId — no exception text leaks to clients. GlobalExceptionHandlerTests.cs includes a positive control that confirms a "leakySecret"-shaped exception message is NOT echoed. |
Cross-reference to Phase 1 / Phase 2 findings
| OWASP Cat | Tied finding | Severity | Source phase |
|---|---|---|---|
| A02 | S1 — default password in appsettings.json | Medium | Phase 2 |
| A02 | S2 — weak Postgres creds + 0.0.0.0 binding in compose | Medium | Phase 2 |
| A02 | I1 — Dockerfile runs as root | Low | Phase 4 (next) |
| A02 | I2 — no security headers middleware | Low | Phase 4 (next) |
| A03 | D1 — CVE-2026-26130 in ASP.NET Core 8.0.21 (SignalR; not reachable) | Medium (paper) / Low (real) | Phase 1 |
| A03 | D2 — CVE-2022-30184 transitive via test SDK | Low (test-only) | Phase 1 |
| A06 | S3 — lat/lon not range-validated at API boundary | Low | Phase 2 |
| A06 | I3 — no rate limiting on any endpoint | Medium | Phase 4 (next) |
| A06 | S4 — Google Maps API key handling (no .env.example, no rotation hygiene) | Medium | Phase 2 |
| A09 | I4 — no security-event logs, no alerting | Low | Phase 4 (next) |
Self-verification
- All current OWASP Top 10:2025 categories assessed
- Each FAIL has at least one specific finding with evidence
- N/A categories have justification + caveat
- No
security_approach.mdexists in_docs/00_problem/to cross-reference (project has not declared explicit security requirements; this audit treats the architecture-vision statement "internal/trusted network service" as the de-facto requirement)
Cycle 2 Refresh (AZ-487 + AZ-488)
Cycle 1's A01 / A07 verdicts were N/A (with caveat) because the service shipped without authentication. AZ-487 (JWT validation baseline) and AZ-488 (UAV upload permission policy) materially change those verdicts. The table below supersedes the cycle-1 row for A01 and A07; all other rows remain as cycle 1 left them, with cycle-2 findings appended where applicable.
| # | Category | Cycle 1 Status | Cycle 2 Status | Cycle-2 evidence |
|---|---|---|---|---|
| A01 | Broken Access Control | N/A (with caveat) | PASS_WITH_WARNINGS | Every endpoint requires RequireAuthorization() (AZ-487); POST /api/satellite/upload requires the GPS permission via PermissionsRequirement (AZ-488). No IDOR analysis is needed because the service has no per-user data partitioning — every authenticated principal can read every tile. Warning: per-tenant authorization (e.g. "this UAV may only upload over its assigned region") is not enforced. If a future contract demands it, A01 immediately re-opens. |
| A02 | Security Misconfiguration | FAIL (S1, S2, I1, I2) | FAIL (unchanged + F-AUTH-1) | Cycle-2 ships a clearly-labelled DEV-ONLY JWT secret in appsettings.Development.json. Production override path is correct (env-var wins); deploy gate must check JWT_SECRET. No new cycle-1 findings resolved. |
| A03 | Supply Chain Failures | PASS_WITH_WARNINGS (D1, D2) | PASS_WITH_WARNINGS (+ D3, F-DEPS-UAV) | New JwtBearer 8.0.21 package shares the D1 patch line; new ImageSharp call site widens decoder exposure (mitigations sufficient — see static_analysis.md F-UAV-1). |
| A04 | Cryptographic Failures | N/A | PASS | HS256 token validation uses Microsoft.IdentityModel's SymmetricSecurityKey with RequireSignedTokens = true and RequireExpirationTime = true. The alg=none bypass is blocked by RequireSignedTokens; algorithm-confusion is bounded because only one signing key is registered. Secret length ≥ 32 bytes enforced at startup. |
| A05 | Injection | PASS | PASS | No new SQL / shell / template surfaces. The new JSON parse (PermissionsAuthorizationHandler) runs on signature-validated token bytes — see F-UAV-2 disposition. |
| A06 | Insecure Design | FAIL (S3, S4, I3) | FAIL (+ F-AUTH-3, F-UAV-3) | Rate limiting still absent (now also a 401-flood vector). UAV reject reasons disclose gate structure — accepted UX trade-off, flagged for operator awareness. |
| A07 | Identification & Authentication Failures | N/A (with caveat) | PASS_WITH_WARNINGS → PASS_WITH_WARNINGS (cycle 3: F-AUTH-2 resolved by AZ-494) | HS256 with secret ≥ 32 bytes; lifetime + signature validation; ClockSkew = 30 s. Cycle 3 (AZ-494): ValidateIssuer / ValidateAudience now true; values sourced from JWT_ISSUER / JWT_AUDIENCE env vars with fail-fast contract. Production iss/aud values are admin-team-confirmed at deploy time. Remaining warning: no token revocation list — leaked tokens stay valid until exp. |
| A08 | Software or Data Integrity Failures | PASS | PASS | AZ-488 file-first-then-row write order documented; same migration / CI discipline as cycle 1. |
| A09 | Security Logging Failures | PASS_WITH_WARNINGS (I4) | PASS_WITH_WARNINGS (unchanged) | No new logging changes; 401 responses are not currently aggregated for alerting (out of scope for internal service). |
| A10 | Mishandling of Exceptional Conditions | PASS | PASS | UAV decode failures wrapped in scoped try/catch for UnknownImageFormatException / InvalidImageContentException — produce structured INVALID_FORMAT rejects, no stack-trace leak. SEC-11 test verifies reject details have no path / exception-type leakage. |
Cycle-2 cross-reference
| OWASP Cat | Cycle-2 finding | Severity | Source phase |
|---|---|---|---|
| A01 | A01 status now PASS_WITH_WARNINGS (per-tenant authz absent) | — (status note) | Phase 3 |
| A02 | F-AUTH-1 — DEV-ONLY secret in appsettings.Development.json |
Low (accepted) | Phase 2 |
| A03 | D3 — JwtBearer 8.0.21 shares D1 patch line |
Low | Phase 1 |
| A03 | F-DEPS-UAV — ImageSharp decode exposure widened | Medium | Phase 1 |
| A06 | F-AUTH-3 — rate-limit gap now also covers 401 floods | Low (recurrence of I3) | Phase 2 |
| A06 | F-UAV-3 — reject reasons disclose gate structure | Informational (accepted) | Phase 2 |
| 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 |
| (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 |