Files
ui/_docs/05_security/security_report.md
T
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

155 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Security Audit Report — Azaion UI
**Date**: 2026-05-12
**Scope**: `src/` (production SPA), `mission-planner/src/` (port-source — in git history but NOT in production bundle), `nginx.conf`, `Dockerfile`, `.woodpecker/build-arm.yml`, `e2e/` harness, `.env.example` files
**Cycle**: Phase B / Cycle 2 (post AZ-498, AZ-499)
**Verdict**: **FAIL** — 1 HIGH-severity secret leak in port-source (`F-SAST-1` Google Geocode API key) plus 1 HIGH-severity dependency advisory (`F-DEP-1` vite — dev-server only, no prod exposure)
---
## Summary
| Severity | Count | Notes |
|----------|-------|-------|
| Critical | 0 | — |
| High | 2 | F-SAST-1 (production-bundle exposure: NONE today; git-history exposure: HIGH); F-DEP-1 (production exposure: NONE; dev-server: HIGH) |
| Medium | 7 | F-SAST-2, F-SAST-3, F-DEP-2, F-DEP-3, F-INF-1, F-INF-2, F-INF-3, F-INF-4 |
| Low | 2 | F-SAST-4, F-INF-5 |
**Production browser bundle is clean** — no exploitable findings. All HIGH-severity items are concentrated in (a) port-source code that does not ship and (b) dev-time tooling (Vite dev server). The audit's FAIL verdict reflects:
1. The port-source key is a real secret in real git history → must be revoked + externalized following the AZ-499 pattern.
2. CI does not run `bun audit`, so the High Vite advisory shipped through Cycle 2 unflagged → procedural gap to close.
## OWASP Top 10 (2021) Assessment
| # | Category | Status | Findings |
|---|----------|--------|----------|
| A01 | Broken Access Control | PASS_WITH_KNOWN | 1 known UX gap (`/admin` route, F2/AC-22 — pre-existing) |
| A02 | Cryptographic Failures | PASS_WITH_KNOWN | 1 accepted trade-off (SSE bearer-in-query, ADR-008) |
| A03 | Injection | PASS | — |
| A04 | Insecure Design | PASS | — |
| A05 | Security Misconfiguration | FAIL | F-INF-2 (nginx headers + log redaction missing) |
| A06 | Vulnerable & Outdated Components | FAIL | F-DEP-1, F-DEP-2, F-DEP-3 |
| A07 | Identification & Authentication Failures | PASS_WITH_KNOWN | 1 known cold-load refresh bug (F2 — pre-existing) |
| A08 | Software & Data Integrity Failures | FAIL | F-INF-1, F-INF-3, F-INF-4 |
| A09 | Security Logging & Monitoring Failures | N/A | Server-side concern (operator-internal SPA) |
| A10 | Server-Side Request Forgery | N/A | Browser SPA has no server-side request surface |
## Findings (severity-ranked)
| # | Severity | Category | Location | Title |
|---|----------|----------|----------|-------|
| F-SAST-1 | **HIGH** | Secrets in code | `mission-planner/src/config.ts:2` | Hardcoded Google Geocode API key in port-source |
| F-DEP-1 | **HIGH** | Vulnerable component | `vite@6.4.1` (both roots) | Vite Arbitrary File Read via Dev Server WebSocket (GHSA-p9ff-h696-f583) — dev-server only |
| F-INF-1 | MEDIUM | CI/CD | `.woodpecker/build-arm.yml` | `bun audit` not gated in CI pipeline |
| F-INF-2 | MEDIUM | Misconfiguration | `nginx.conf` | Missing CSP, X-Frame-Options, HSTS, Referrer-Policy, X-Content-Type-Options, log-redaction |
| F-INF-3 | MEDIUM | Supply chain | `.woodpecker/build-arm.yml` | No image vulnerability scan (Trivy/Grype) |
| F-INF-4 | MEDIUM | Supply chain | `.woodpecker/build-arm.yml` | No SBOM emission, no image signing (cosign) |
| F-DEP-2 | MEDIUM | Vulnerable component | `vite@6.4.1` | Vite Path Traversal in Optimized Deps `.map` (GHSA-4w7w-66w2-5vf9) — dev-server only |
| F-DEP-3 | MEDIUM | Vulnerable component | `postcss@8.5.8` (transitive) | PostCSS XSS via Unescaped `</style>` (GHSA-qx2v-qp2m-jg93) — low surface |
| F-SAST-2 | MEDIUM | Supply chain | `mission-planner/src/icons/PointIcons.tsx:7` | `unpkg.com` CDN reference in port-source |
| F-SAST-3 | MEDIUM | Coverage gap | `scripts/run-tests.sh` (`STC-SEC2`) | No-CDN gate does not scan `mission-planner/` |
| F-SAST-4 | LOW | Future risk | `mission-planner/src/constants/tileUrls.ts:2-3` | Port-source still uses third-party tile fallbacks |
| F-INF-5 | LOW | Container hardening | `Dockerfile` | nginx runs as root master process; no `HEALTHCHECK` directive |
### Finding Details
#### F-SAST-1 — Hardcoded Google Geocode API key — HIGH
- **Location**: `mission-planner/src/config.ts:2`
- **Value**: `AIzaSyAhvDeYukuyWVrQYbRhuv91bsi_jj5_Iys`
- **Description**: The Google Geocode API key is committed in `mission-planner/` (port-source). Used by `mission-planner/src/flightPlanning/LeftBoard.tsx:114` for address-to-coords lookups.
- **Production-bundle exposure**: NONE today. `src/` does not import from `mission-planner/`; `Dockerfile` builds only `src/`-rooted Vite. The key is NOT in `dist/`.
- **Git-history exposure**: HIGH. Anyone with repo read access can extract the key. Same threat class as the OWM key resolved by AZ-499.
- **Impact**: Quota theft, billing-account abuse, accelerated risk if `mission-planner/` is later ported into the SPA without remediation.
- **Remediation** (mirror 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.
2. Externalize: `import.meta.env.VITE_GOOGLE_GEOCODE_KEY` in `mission-planner/src/config.ts` with fail-soft if unset.
3. Update `mission-planner/.env.example` with placeholder.
4. Extend `tests/security/banned-deps.json` `owm_key_in_source` (or add a sibling `google_key_in_source`) section to also block the literal Google key.
5. Long-term: route geocoding via suite-side proxy when the SPA needs it.
- See: `static_analysis.md` F-SAST-1.
#### F-DEP-1 — Vite Arbitrary File Read via Dev Server WebSocket — HIGH
- **Location**: `vite@6.4.1` (resolved in `bun.lock`, both `ui/` and `mission-planner/` roots)
- **Advisory**: [GHSA-p9ff-h696-f583](https://github.com/advisories/GHSA-p9ff-h696-f583)
- **Description**: WebSocket endpoint exposed by `vite dev` allows arbitrary local-file read via path traversal.
- **Production-bundle exposure**: NONE. The Vite dev server is never present in production (`Dockerfile` final stage is `nginx:alpine` serving static `dist/`).
- **Developer-machine exposure**: HIGH if `bun run dev --host` is ever used (binding to `0.0.0.0`); MODERATE for the default `localhost` binding (still a browser-side script attack vector via DNS rebinding).
- **Remediation**: `bun update vite` in both roots → `vite >= 6.4.2`. Verify build + fast tests still pass.
- See: `dependency_scan.md` F-DEP-1.
(Full detail for F-INF-1 .. F-INF-5 in `infrastructure_review.md`; for F-DEP-2/F-DEP-3 in `dependency_scan.md`; for F-SAST-2/F-SAST-3/F-SAST-4 in `static_analysis.md`. Not duplicated here.)
---
## Dependency Vulnerabilities
| Package | GHSA / Advisory | Severity | Installed | Fix |
|---------|-----------------|----------|-----------|-----|
| `vite` | [GHSA-p9ff-h696-f583](https://github.com/advisories/GHSA-p9ff-h696-f583) | HIGH | 6.4.1 | `>= 6.4.2` (bun update vite) |
| `vite` | [GHSA-4w7w-66w2-5vf9](https://github.com/advisories/GHSA-4w7w-66w2-5vf9) | MODERATE | 6.4.1 | `>= 6.4.2` (same upgrade) |
| `postcss` | [GHSA-qx2v-qp2m-jg93](https://github.com/advisories/GHSA-qx2v-qp2m-jg93) | MODERATE | 8.5.8 | `>= 8.5.10` (transitive — flows through Vite upgrade) |
A single `bun update vite` in each root fixes all three.
---
## Recommendations
### Immediate (HIGH — block deploys until done)
- [ ] **F-SAST-1 (USER ACTION + CODE)**: Revoke the Google Geocode API key at the Google Cloud Console, then externalize per AZ-499 pattern. Mirror the manual evidence-capture protocol used for AZ-499 AC-7. Recommended ticket: `AZ-NEW — Externalize Google Geocode key in mission-planner port-source` (3 SP — same shape as AZ-499 minus AC-8 misattribution).
- [ ] **F-DEP-1 / F-DEP-2 / F-DEP-3 (CODE)**: `bun update vite` in `ui/` and `mission-planner/`. Re-run `bun audit` to confirm zero findings. Recommended ticket: `AZ-NEW — Update Vite to fix CVE-2026 advisories` (1 SP).
### Short-term (MEDIUM — Phase B)
- [ ] **F-INF-1**: Add `bun audit --severity high` step to `.woodpecker/build-arm.yml` so future advisory regressions fail CI (1 SP).
- [ ] **F-INF-2**: Add CSP, X-Frame-Options, Referrer-Policy, X-Content-Type-Options + bearer-redaction log format to `nginx.conf` (2 SP). Coordinate HSTS decision with suite ingress.
- [ ] **F-INF-3**: Add Trivy image-scan step to `.woodpecker/build-arm.yml` after `docker build` (2 SP).
- [ ] **F-SAST-2**: Bundle Leaflet marker icon locally instead of `unpkg.com` CDN reference (covered by the same port-source cleanup as F-SAST-1).
- [ ] **F-SAST-3**: Widen no-CDN static gate to scan `mission-planner/` — move pattern into `tests/security/banned-deps.json` and use the existing `check-banned-deps.mjs` widening (2 SP).
### Long-term (Suite-wide / Hardening)
- [ ] **F-INF-4**: SBOM (Syft/cyclonedx) + cosign image signing — coordinate registry capability with suite team (3-5 SP).
- [ ] **F-SAST-4**: Mission-planner port-source modernization will resolve the third-party tile fallbacks naturally — no separate ticket needed.
- [ ] **F-INF-5**: `nginxinc/nginx-unprivileged` migration + `HEALTHCHECK` directive (1 SP, low priority).
### Pre-existing (not introduced by this audit; tracked elsewhere)
- F2 / AC-01 — bootstrap refresh missing `credentials:'include'` (`src/auth/AuthContext.tsx:24`). Quarantined-test acknowledged. Phase B fix.
- AC-22 — `/admin` route lacks client-side role-gate. Server-authoritative, no exploit. Phase B UX fix.
- ADR-008 — SSE bearer-in-query-string. Accepted trade-off; mitigation lives in F-INF-2 (nginx log redaction).
- AZ-499 AC-7 — OWM key revocation manual deliverable. **Pending USER action.**
---
## Cycle 2 — security regression check
No security regressions introduced by AZ-498 or AZ-499. Both changes pass static + fast test suites; the cookie-credentialed tile fetch is correctly scoped to `SameSite=Strict` and same-origin; the OWM env hardening closes the previously quarantined `NFT-SEC-09` source check.
`STC-SEC1C` is now part of the static gate and would catch any future re-introduction of the literal OWM key in either `src/` or `mission-planner/`.
---
## Verdict justification
The verdict is **FAIL** because:
1. F-SAST-1 is a real third-party API key in real git history. The same finding class as AZ-499 — same remediation pattern, same urgency, same need for out-of-band revocation.
2. F-DEP-1 is a HIGH advisory against a current direct dependency. Even with no production exposure, OWASP A06 categorically fails on any actionable HIGH advisory.
Both findings have one-line remediations. Once F-SAST-1 is revoked + externalized and F-DEP-1 is upgraded, a follow-up audit cycle should re-rate the verdict to PASS_WITH_WARNINGS pending the MEDIUM infrastructure tickets.
The production browser bundle itself is **not vulnerable** — the SPA is well-architected (server-authoritative auth, bearer-in-memory + HttpOnly cookie, no eval/injection surface, no client-side persistence). The deficiencies are at the supply-chain, infrastructure, and port-source layers.
## Self-verification
- [x] All findings from Phases 14 included
- [x] No duplicate findings (cross-references used instead)
- [x] Every finding has remediation guidance
- [x] Verdict matches severity logic (FAIL on any HIGH)
- [x] Production-vs-dev impact distinguished for each HIGH finding
- [x] Cycle 2 deltas (AZ-498, AZ-499) explicitly reviewed for regressions