mirror of
https://github.com/azaion/satellite-provider.git
synced 2026-06-21 16:11:14 +00:00
314d1dec39
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>
25 KiB
25 KiB
Phase 2 — Static Analysis (SAST)
Date: 2026-05-11
Scope: All *.cs files in production projects (Api, Common, DataAccess, Services.*) plus Tests for false-positive triage. Configuration files (appsettings*.json, docker-compose*.yml, Dockerfile, .env).
Method: Pattern-based grep + targeted file review.
Patterns checked
| Category | Pattern(s) | Verdict |
|---|---|---|
| SQL injection | $"SELECT…", + "WHERE", raw CommandText, manual SQL string assembly |
Clean |
| Command/process injection | Process.Start, ProcessStartInfo, cmd.exe, /bin/sh, UseShellExecute, eval-equivalent |
Clean |
| XSS | unsanitized user input flowed to HTML or Response.Write |
N/A — JSON-only API, no HTML rendering |
| Template injection | Razor / scriban / handlebars on user input | N/A — none used |
| Hardcoded credentials | password = "…", secret = "…", token = "…", apikey = "…" in source |
See findings S1, S2 |
| Weak crypto | MD5/SHA1 for passwords, RNGCryptoServiceProvider (deprecated), hardcoded keys |
N/A — no password storage, no crypto code in app |
| Insecure deserialization | BinaryFormatter, pickle, untrusted JSON with type-name handling |
Clean — System.Text.Json with default settings; Newtonsoft.Json 13.0.4 used only for outbound serialization to Google session-creation endpoint (line GoogleMapsDownloaderV2.cs), no deserialization of untrusted inbound JSON |
| Path traversal | user input flowed into File.Open, Path.Combine |
Clean — file paths are computed server-side from validated tile coordinates; no user-supplied path component reaches the filesystem |
| Sensitive data in logs | passwords, API keys, tokens, PII in log statements | Clean — GlobalExceptionHandler.cs logs only Method, Path, correlationId; client gets a generic 500 + correlationId. CorsConfigurationValidator warning (PermissiveDefaultWarning) does not include secrets. There is a deliberate test fixture GlobalExceptionHandlerTests.cs:23 that uses "Connection string Host=secret-db;Password=hunter2 failed at line 42" to verify the handler does NOT echo exception messages back — this is a positive control, not a finding |
| Verbose error responses | stack traces or internal details returned to clients | Clean — GlobalExceptionHandler returns RFC 7807 ProblemDetails with Detail = "An unexpected error occurred. Use the correlationId to look up the server log entry." |
| Input validation | numeric ranges, geo coordinates, enum-like strings | See finding S3 |
| Hardcoded credentials (cycle 2 delta) | Jwt:Secret value in appsettings*.json |
appsettings.Development.json ships a clearly-tagged DEV-ONLY placeholder; appsettings.json ships "". JWT_SECRET env-var overrides both. See cycle-2 finding F-AUTH-1. |
| Authentication / authorization (cycle 2 delta) | endpoint-level Authorize, custom requirement handlers, claim parsing | Program.cs applies .RequireAuthorization() on every existing endpoint and the GPS-permission policy on the new /api/satellite/upload. PermissionsAuthorizationHandler uses string.Equals(..., Ordinal) — no substring / case-confusion bypass. See cycle-2 findings F-AUTH-2 .. F-AUTH-4. |
| Multipart binary input (cycle 2 delta) | uploaded bytes flowing into image decode / file write | UavTileQualityGate runs magic-byte check before ImageSharp, wraps decode in scoped try/catch for UnknownImageFormatException / InvalidImageContentException. File path is built from integer coords only via UavTileUploadHandler.BuildUavTileFilePath. See cycle-2 finding F-UAV-1. |
| Untrusted JSON via claims (cycle 2 delta) | JsonDocument.Parse(claim.Value) in PermissionsAuthorizationHandler |
Tokens are signature-validated before the handler runs, so the JSON parsed here is already framework-validated bytes from a verified token. Token size is bounded by Kestrel header limits. See cycle-2 finding F-UAV-2. |
Findings
S1 — Default DB password committed in appsettings.json (Medium)
- Location:
SatelliteProvider.Api/appsettings.json:24 - Vulnerable code:
"DefaultConnection": "Host=localhost;Database=satelliteprovider;Username=postgres;Password=postgres" - Description: The default (non-Development) appsettings file ships with a weak, well-known password (
postgres/postgres). In production this string is overridden byConnectionStrings__DefaultConnectionindocker-compose.yml/env, but the file itself becomes the fallback if env-var injection ever fails or is misconfigured (silent connect-as-default behaviour). - Impact: If a deployment misconfiguration drops the env override, the app silently falls back to attempting
postgres:postgres@localhost. On a developer workstation this connects to the local Postgres container with full superuser; in production it would fail loudly only if the prod DB has different creds. Combined with finding S2 below (matching weak creds in compose file), this normalises a credential pattern that real production deployments may inherit. - Remediation:
- Replace the default value with a deliberately-invalid placeholder such as
Host=__set-via-env__;Database=__;Username=__;Password=__so a misconfiguration fails fast at startup instead of silently falling through. - OR remove the
ConnectionStrings:DefaultConnectionkey fromappsettings.jsonentirely and require the env var;Program.csline 23–24 already throws when missing — keep that behaviour.
- Replace the default value with a deliberately-invalid placeholder such as
S2 — Weak Postgres credentials in docker-compose.yml (Medium, dev-only as written)
- Location:
docker-compose.yml:6-7, 30 - Vulnerable code:
POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres … - ConnectionStrings__DefaultConnection=Host=postgres;Port=5432;Database=satelliteprovider;Username=postgres;Password=postgres - Description: Same
postgres/postgrescredentials as S1. The compose file is labelledDevelopment(ASPNETCORE_ENVIRONMENT=Development), so this is contained — but the file is the only compose artifact in the repo, which means anyone runningdocker-compose upon a network-reachable host immediately exposes a Postgres-with-default-creds. - Impact: Postgres on
0.0.0.0:5432(port"5432:5432"mapping) withpostgres/postgresis one of the most-scanned credential pairs on the public internet. If a developer runs this on a non-laptop host (cloud VM, shared lab, etc.) the DB is trivially compromised within minutes. - Remediation:
- Bind
5432to127.0.0.1:5432rather than0.0.0.0:5432so the host firewall isn't the only protection. (Replace"5432:5432"with"127.0.0.1:5432:5432".) - Source
POSTGRES_USER/POSTGRES_PASSWORDfrom the same.envfile that already suppliesGOOGLE_MAPS_API_KEY(line 31 already shows the pattern). Provide an.env.examplewith placeholder values and document the required vars in the README. - The deploy/observability docs at
_docs/02_document/deployment/already describe a secret-manager strategy for staging/prod — fold the same pattern into the dev compose.
- Bind
S3 — Latitude / longitude inputs not range-validated at the API boundary (Low)
- Locations:
SatelliteProvider.Api/Program.cs:169—GetTileByLatLon([FromQuery] double Latitude, [FromQuery] double Longitude, [FromQuery] int ZoomLevel, …)SatelliteProvider.Api/Program.cs:207—RequestRegionvalidatesSizeMetersonly;request.Latitude/request.Longitudeare uncheckedSatelliteProvider.Api/Program.cs:237—CreateRoutedelegates toRouteServicewhich validates names but does not range-check waypoint coordinates
- Description:
Latitude,Longitude, and (for region requests) the implicitMaxRoutePointSpacingMetersboundary are accepted without enforcing valid geographic ranges (-90 ≤ lat ≤ 90,-180 ≤ lon ≤ 180).ZoomLevelIS validated downstream byGoogleMapsDownloaderV2againstMapConfig.AllowedZoomLevels— so it is fine. - Impact:
- Garbage inputs (e.g.
lat=999) propagate throughGeoUtils.WorldToTilePosand the slippy-map math, eventually producing nonsensical tile coordinates that are persisted totilesandregions. This is a data-quality issue, not a code-execution issue. - No DoS amplification: every tile-download endpoint already enforces zoom against
AllowedZoomLevels, so an attacker cannot use lat/lon abuse to multiply outbound Google Maps traffic beyond what zoom already bounds.
- Garbage inputs (e.g.
- Remediation: Add explicit guard clauses at the API boundary (matches the existing
SizeMeters100-10000 pattern):Apply uniformly toif (Latitude < -90 || Latitude > 90) return Results.BadRequest(new { error = "Latitude must be between -90 and 90" }); if (Longitude < -180 || Longitude > 180) return Results.BadRequest(new { error = "Longitude must be between -180 and 180" });GetTileByLatLon,RequestRegion, and to each waypoint insideCreateRoute.
S4 — .env file on developer filesystem contains an apparently real Google Maps API key (Medium — exposure depends on key reach)
- Location:
.env(workspace root, not tracked — confirmed viagit ls-filesand.gitignore:10) - Description: The local
.envcontains a 39-characterAIzaSy…value matching the Google Maps API key format. The file is correctly excluded from git (line 10 of.gitignore) andgit log -- .envreturns no history, so the key was never committed to this repository. - Impact: No repository exposure. However:
- If the same key is shared across developers via Slack / email / other repos, it has likely already leaked elsewhere.
- There is no
.env.exampletemplate in the repo, which means new contributors typically request the real key via insecure channels rather than generating a fresh one. - The key has no per-call attribution; abuse cannot be traced back to a specific developer.
- Remediation:
- Rotate the key in the Google Cloud console (out of scope for this audit — the key value is intentionally not echoed into this report).
- Add
.env.exampleto the repo withGOOGLE_MAPS_API_KEY=replace-with-your-own-key-from-cloud-consoleand reference it in the README setup section. - Configure Google Cloud key restrictions: HTTP referrer allowlist (for browser keys) or IP allowlist (for server keys), and per-API quotas. Optional: per-developer keys.
Cycle 2 Delta Findings (AZ-487 + AZ-488)
F-AUTH-1 — Dev JWT secret is committed to appsettings.Development.json (Low — accepted by design)
- Location:
SatelliteProvider.Api/appsettings.Development.json:14—"Secret": "DEV-ONLY-DO-NOT-USE-IN-PROD-replace-with-real-secret-via-JWT_SECRET-env-var". - Description: A 73-byte placeholder labelled DEV-ONLY ships in the repo. The value is clearly tagged;
ResolveSecretOrThrowinAuthenticationServiceCollectionExtensions.cs:43readsJWT_SECRETfrom the environment first and only falls back to config when it is unset, so a production deploy withJWT_SECRETset overrides it. - Impact: Cosmetic only — the placeholder is not a usable production secret (it is published on every git clone and would be rejected by any token verifier already in the wild). A careless operator who copies the file verbatim into prod and forgets to set
JWT_SECRETwould still pass the ≥32-byte gate, so the secret would work locally — that is the dependency to monitor. - Disposition: Accept. Mitigation: the
DEV-ONLY-DO-NOT-USE-IN-PRODprefix is the operator-readable warning; the deploy skill must verifyJWT_SECRETis set before promotion.
F-AUTH-2 — JWT issuer / audience are not validated (Medium — by design, until admin team defines values)
- Location:
SatelliteProvider.Api/Authentication/AuthenticationServiceCollectionExtensions.cs:31-32—ValidateIssuer = false,ValidateAudience = false. - Description: Per the suite contract
suite/_docs/10_auth.md, expectediss/audvalues are not yet defined. The validator therefore accepts any HS256 token signed with the correct shared secret — including tokens minted by other services in the suite that share the secret. This is a horizontal-trust risk: any service that holdsJWT_SECRETcan mint tokens accepted by satellite-provider as if they came from the admin API. - Impact: Bounded by the secret-distribution policy. Within the trust boundary documented in cycle 1's A01 caveat ("internal/trusted-network service") this is acceptable.
- Remediation (follow-up, NOT this cycle): When the admin team publishes
iss/audvalues, flipValidateIssuer = true+ValidIssuer = "<admin-iss>"and the audience equivalent inAddSatelliteJwt. AZ-487 § Constraints already flags this as a small follow-up.
F-AUTH-3 — No rate limiting on 401-producing paths (Low — recurrence of cycle-1 I3)
- Location: every
/api/satellite/*endpoint after the AZ-487.RequireAuthorization()middleware. - Description: An attacker can flood
Authorization: Bearer <random>requests; each one triggers an HMAC verification (cheap, but non-zero) and an HTTP 401 response. This re-uses the cycle-1 I3 finding ("no inbound rate limiting on any HTTP endpoint") — the JWT layer didn't introduce a new vulnerability, but it did add a new cheap-to-trigger 401 surface that magnifies I3. - Disposition: Track under existing I3 remediation (wire
Microsoft.AspNetCore.RateLimiting). No separate Jira.
F-UAV-1 — ImageSharp decode on attacker-controlled bytes (Medium — exposure increase, mitigations sufficient today)
- Location:
SatelliteProvider.Services.TileDownloader/UavTileQualityGate.cs:60-95—Image.Identify(Rule 3) andImage.Load<L8>+Mutate(ctx => ctx.Resize)(Rule 5). - Description: Pre-AZ-488, ImageSharp only decoded responses from the Google Maps tile CDN (trusted origin). AZ-488 added a second call site that decodes arbitrary
POST /api/satellite/uploadpayloads. Current ImageSharp 3.1.11 is patched (see cycle-2 dependency-scan finding F-DEPS-UAV); the change here is exposure, not a present vulnerability. - Mitigations in place:
- Rule 1 magic-byte gate runs before any ImageSharp call (
FF D8 FFprefix required). - Rule 2 caps per-item size at 5 MiB; Kestrel + FormOptions cap the envelope at
MaxBatchSize × MaxBytes. - Decode is wrapped in
try { … } catch (UnknownImageFormatException) { … } catch (InvalidImageContentException) { … }— malformed JPEGs produce a structuredINVALID_FORMATreject; no unhandled exception reaches the client.
- Rule 1 magic-byte gate runs before any ImageSharp call (
- Remediation: Subscribe to
SixLabors.ImageSharpGHSA advisories; bump within 7 days of a patch. Sandboxing (separate process / libvips + seccomp) is not warranted at the current trust boundary but should be reconsidered if the endpoint is exposed publicly. Recorded as recurring follow-up.
F-UAV-2 — JsonDocument.Parse invoked on token-supplied claim values (Low — bounded by Kestrel header limits)
- Location:
SatelliteProvider.Api/Authentication/PermissionsRequirement.cs:84-111—JsonDocument.Parse(claim.Value)when thepermissionsclaim arrives as a JSON-array string. - Description:
JsonDocument.Parsehas no built-in depth or size limit. A maliciously-shaped permissions claim (e.g. deeply-nested array) would consume CPU/heap during parsing. The token has already passed HS256 signature validation by the time the handler runs, so this is only exploitable by a party that holdsJWT_SECRET— i.e. another suite service or an admin-team principal — and only inside the issued-token-size window (bounded by Kestrel'sMaxRequestHeadersTotalSize, default 32 KiB). - Disposition: Accept. The combination of
RequireSignedTokens = true+ header-size cap + ordinal-only string comparison makes a practical exploit prohibitive. Future hardening: passJsonDocumentOptions { MaxDepth = 8 }toJsonDocument.Parseand reject claims longer than e.g. 8 KiB before parsing.
F-UAV-3 — Reject reasons disclose gate structure (Informational — accepted trade-off)
- Location:
SatelliteProvider.Services.TileDownloader/UavTileQualityGate.cs— each rule returns a distinct enum code. - 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.
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-providerplaceholders, 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
TruncateOrderis apublic 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 ofpostgres/localhost/127.0.0.1. An operator who setsASPNETCORE_ENVIRONMENT=Testingand SSH-tunnels a production Postgres tolocalhost:5432could trick the guard. - Impact: Loss of all
tiles,regions,routes,route_points,route_regionsrows 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-TRUNCATESenv var when the guard runs againstlocalhostfrom 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-onlyprints a 4-hour HS256 token withpermissions: GPSclaim to stdout. The token grants the same access as a production-issuedGPSadmin 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_SECRETfrom.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 mountingJWT_SECRETvia 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/audnot validated. RESOLVED in AZ-494 —ValidateIssuer = true+ValidateAudience = truewired 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
- All production source directories scanned (Api, Common, DataAccess, Services.TileDownloader, Services.RegionProcessing, Services.RouteManagement)
- All cycle-3 test-side surfaces scanned (TestSupport, IntegrationTests, Tests)
- Each finding has file path and line number
- 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 - 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)
- Cycle-3 surfaces (
AddSatelliteJwtiss/aud extension,IntegrationTestDatabaseReset,PerfBootstrap, two-guard logic) all reviewed; findings either documented above or explicitly cleared