Files
ui/_docs/05_security/static_analysis.md
Oleksandr Bezdieniezhnykh f7dd6c98d8
ci/woodpecker/push/build-arm Pipeline failed
[AZ-501] [AZ-502] Cycle 2 Step 14 security audit + inline fixes
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>
2026-05-12 05:31:11 +03:00

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):

  1. 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.
  2. Externalize: import.meta.env.VITE_GOOGLE_GEOCODE_KEY in mission-planner/src/config.ts. Fail-soft if unset (mirror WeatherService.ts pattern from AZ-499).
  3. Update mission-planner/.env.example to advertise the new variable + the <your-google-geocode-api-key> placeholder.
  4. Extend the owm_key_in_source static-check pattern in tests/security/banned-deps.json to also block the literal AIzaSyAhvDeYukuyWVrQYbRhuv91bsi_jj5_Iys (defense-in-depth — does not replace revocation).
  5. 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.1 could 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 leaflet package 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.json as a new section (e.g. cdn_in_source) and let check-banned-deps.mjs apply 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:63DEFAULT_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