Security audit (5 phases) → reports under _docs/05_security/. AZ-501 (F-SAST-1, HIGH): Externalize hardcoded Google Geocode key from mission-planner/src/config.ts to VITE_GOOGLE_GEOCODE_KEY via new GeocodeService.ts; fail-soft warn when unset; STC-SEC1D static deny-list gate; +5 unit tests in tests/mission_planner_geocode.test.ts. AZ-502 (F-DEP-1, HIGH): Force vite>=6.4.2 and postcss>=8.5.10 via package.json overrides in both roots; clean reinstall clears all bun audit advisories. Test-spec sync (Step 12) + Update Docs (Step 13) deltas: AC-43, AC-44, NFT-SEC-09b, FT-P-61, FT-N-17, ripple log, batch_12 report. Pending user actions: revoke Google + OWM keys (AC-6 / AZ-499 AC-7). 229 PASS / 13 SKIP / 0 FAIL on static + fast suites. Co-authored-by: Cursor <cursoragent@cursor.com>
11 KiB
Static Analysis — Azaion UI
Date: 2026-05-12
Scope: src/ (production SPA), mission-planner/src/ (port-source — NOT shipped in production bundle but in git history), nginx.conf, .env.example files
Method: targeted ripgrep patterns + manual review, complementing the 32 existing static checks in scripts/run-tests.sh (STC-SEC*, STC-N*, STC-S*, STC-ARCH-*)
Cycle: Phase B / Cycle 2
Summary
| Severity | Count | New in this audit |
|---|---|---|
| Critical | 0 | — |
| High | 1 | F-SAST-1 (Google Geocode API key in mission-planner/) |
| Medium | 2 | F-SAST-2 (unpkg.com CDN ref in mission-planner/), F-SAST-3 (mission-planner not covered by STC-SEC2) |
| Low | 1 | F-SAST-4 (port-source still uses third-party tile fallbacks) |
No NEW Critical or High findings in src/ (production bundle). All High-severity findings are confined to mission-planner/ — the inferior port-source documented in _docs/02_document/components/05_flights/description.md as "not built; manual reference for porting work".
The 32 existing static checks (run-tests.sh) cover: no eval/Function, no dangerouslySetInnerHTML, no token logging, no innerHTML= writes, no banned ML/crypto/persistence libs, no hardcoded /api literals, TS strict mode, no target=_blank, no OWM key in src/ or mission-planner/. All passed in the Cycle 2 test run (_docs/03_implementation/test_run_report_phase_b_cycle2.md).
Findings
F-SAST-1 — Hardcoded Google Geocode API key in mission-planner/src/config.ts — HIGH
Location: mission-planner/src/config.ts:2
export const GOOGLE_GEOCODE_KEY = 'AIzaSyAhvDeYukuyWVrQYbRhuv91bsi_jj5_Iys';
Used by: mission-planner/src/flightPlanning/LeftBoard.tsx:114-115
`https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${GOOGLE_GEOCODE_KEY}`
Production-bundle exposure: NONE today. src/ does NOT import from mission-planner/ (verified via grep: zero matches for from '.*mission-planner in src/). The Dockerfile builds only the main project (bun run build produces dist/ from the src/ Vite root). mission-planner/ is a port-source kept around for reference per _docs/02_document/components/05_flights/description.md line 59.
Git-history exposure: HIGH. The key is committed and visible to anyone who clones the repository, has read access to the upstream remote, or reads any historical revision. Same threat class as the OpenWeatherMap key resolved in AZ-499 (_docs/00_problem/security_approach.md §5).
Risk:
- Quota/rate-limit theft (Google charges per geocode call past the free tier).
- Provider account abuse — whoever owns the Google Cloud billing account is liable.
- Accelerated risk if
mission-planner/is ever ported into the production SPA without this finding being remediated first.
Remediation (mirrors AZ-499 / AC-42 pattern):
- Revoke the key at https://console.cloud.google.com/google/maps-apis/credentials (manual, OUT-OF-BAND, USER ACTION). Capture evidence per the AZ-499 AC-7 protocol.
- Externalize:
import.meta.env.VITE_GOOGLE_GEOCODE_KEYinmission-planner/src/config.ts. Fail-soft if unset (mirrorWeatherService.tspattern from AZ-499). - Update
mission-planner/.env.exampleto advertise the new variable + the<your-google-geocode-api-key>placeholder. - Extend the
owm_key_in_sourcestatic-check pattern intests/security/banned-deps.jsonto also block the literalAIzaSyAhvDeYukuyWVrQYbRhuv91bsi_jj5_Iys(defense-in-depth — does not replace revocation). - Long-term: when geocoding lands in the production SPA, route via a suite-side proxy (no client-visible key — same architecture decision noted in
security_approach.md§5 for OWM).
Recommended ticket: AZ-NEW (Phase B) — Externalize Google Geocode key in mission-planner port-source (mirror AZ-499 structure).
F-SAST-2 — unpkg.com CDN reference in mission-planner/ — MEDIUM
Location: mission-planner/src/icons/PointIcons.tsx:7
iconUrl: 'https://unpkg.com/leaflet@1.7.1/dist/images/marker-icon.png',
Production-bundle exposure: NONE today (same reason as F-SAST-1).
Risk class:
- Supply-chain: a compromised unpkg.com or a take-over of
leaflet@1.7.1could replace the icon with a tracking pixel or attack payload. - Privacy: the browser leaks the user's IP + referer to a third-party CDN on every page load that uses these icons.
- Air-gap incompatibility: the suite is documented as "air-gapped friendly" (
_docs/02_document/architecture.md); a CDN dependency violates that.
The main src/ is already protected: STC-SEC2 (scripts/run-tests.sh) blocks unpkg.com in src/. mission-planner/ is currently NOT scanned by STC-SEC2 — see F-SAST-3.
Remediation:
- Replace with a relative import (the
leafletpackage is already a dependency; bundling the marker icon locally is one line). - OR move this asset into the same-origin nginx static path during the eventual port.
Recommended ticket: bundle into the same Phase B port-source cleanup task as F-SAST-1.
F-SAST-3 — STC-SEC2 (no-CDN gate) does NOT scan mission-planner/ — MEDIUM
Location: scripts/run-tests.sh (the src_grep helper passes src only for STC-SEC2)
Evidence: STC-SEC2 is currently src/-scoped only; the owm_key_in_source and alert_calls checks were widened in AZ-499/AZ-466 to scan both src/ and mission-planner/ (see scripts/check-banned-deps.mjs:204), but the unpkg.com/CDN deny-pattern was not.
Risk: a future port that copies more mission-planner/ code into src/ could re-introduce CDN URLs that the current static gate would not catch on the source side.
Remediation:
- Move the no-CDN check into
tests/security/banned-deps.jsonas a new section (e.g.cdn_in_source) and letcheck-banned-deps.mjsapply it to both roots, mirroring the AZ-499 widening pattern. - Add the new STC-ID to
_docs/02_document/tests/security-tests.md.
Recommended ticket: AZ-NEW (Phase B) — Widen no-CDN static gate to cover mission-planner/ (small, 2-3 SP).
F-SAST-4 — Port-source still uses third-party tile fallbacks — LOW
Location: mission-planner/src/constants/tileUrls.ts:2-3, mission-planner/.env.example:25
export const TILE_URLS = {
classic: 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',
satellite: import.meta.env.VITE_SATELLITE_TILE_URL || 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
};
Production-bundle exposure: NONE today (port-source is not built).
Risk: if mission-planner/ is ever shipped, AZ-498's gains are partially undone — the classic tile path goes straight to OSM with NO env override path, and the satellite fallback hits ArcGIS unauthenticated.
Remediation: deferred to whichever ticket consumes / replaces the mission-planner/ port-source in the SPA. Do NOT fix in-place — mission-planner/ is documented as inferior and slated for removal once the port is complete.
Negative findings (clean — explicitly verified)
| Pattern | src/ |
mission-planner/src/ |
Coverage |
|---|---|---|---|
eval(, new Function(, setTimeout('...') |
clean | clean | manual grep this audit |
dangerouslySetInnerHTML, innerHTML=, outerHTML=, document.write |
clean | clean | manual grep this audit |
target='_blank' (without rel='noopener') |
clean | clean | manual grep this audit |
console.log/console.error of token/bearer/password/secret/key/cookie/auth |
clean | clean | manual grep this audit |
__proto__, constructor[…], prototype[…] (prototype-pollution patterns) |
clean | clean | manual grep this audit |
localStorage/sessionStorage/indexedDB writes of bearer |
clean (only test-fixture reads in auth/AuthContext.test.tsx) |
clean | STC-SEC3 + manual grep |
credentials: 'include' on every authed fetch |
present on the 401-recovery path (src/api/client.ts:90); KNOWN MISSING on bootstrap refresh (src/auth/AuthContext.tsx:24, quarantined test acknowledges Step 4 fix) |
n/a (no auth in port-source) | KNOWN — security_approach.md §1 finding F2 |
Hardcoded OWM key 335799082893fad97fa36118b131f919 |
clean | clean | STC-SEC1 + STC-SEC1B + STC-SEC1C (AZ-499) |
| Hardcoded URLs other than the OWM endpoint | clean (only flightPlanUtils.ts:59 DEFAULT_OWM_BASE_URL — env-overridable fallback) |
F-SAST-1, F-SAST-2, F-SAST-4 above | manual grep this audit |
Other API-key formats: AIza…, sk_live_, pk_live_, xox*, ghp_, AKIA…, generic 32+ hex |
clean | F-SAST-1 only | manual grep this audit |
password = '...'/secret = '...'/api_key = '...' literals |
clean (only password field labels and AdminPage.tsx form bindings) |
clean | manual grep this audit |
Cycle-2 delta — security review of AZ-498 + AZ-499 changes
| Change | Security review |
|---|---|
src/features/flights/FlightMap.tsx, MiniMap.tsx — <TileLayer crossOrigin="use-credentials" url={getTileUrl()}/> |
OK. crossOrigin="use-credentials" only sends cookies to the SAME origin (/tiles/{z}/{x}/{y}) when production env points at the same-origin nginx path. Dev default http://localhost:5100/... is HTTP and DEV-ONLY (acknowledged in .env.example:12-16). Confirms cookie ride for tile auth without exposing the bearer. |
src/features/flights/types.ts:63 — DEFAULT_SATELLITE_TILE_URL = 'http://localhost:5100/tiles/{z}/{x}/{y}' |
OK. Dev default; production .env MUST override. The .env.example documentation is explicit. No bearer leakage path. |
mission-planner/src/services/WeatherService.ts — env-resolved key + base URL + fail-soft |
OK — matches AZ-499 spec. Key never re-introduced in source (verified by STC-SEC1C). |
STC-SEC1C static gate in scripts/run-tests.sh |
OK. Defense-in-depth as designed; widens the STC-SEC1* family to scan mission-planner/ for the literal OWM key. |
Tests src/features/flights/__tests__/satellite_tile.test.tsx, tests/mission_planner_weather.test.ts |
OK. Tests do not contain real secrets; they vi.stubEnv with placeholder strings. |
No security regressions introduced by Cycle 2.
Self-verification
- Both source trees scanned (
src/,mission-planner/src/) - Each finding has a file path + line number + extract
- Test files explicitly excluded from finding lines (only quoted as evidence of negative results)
- Cycle 2 deltas individually reviewed
- Existing static checks not duplicated — only NEW findings or coverage gaps reported