Steps 12-15 closure for cycle 4 (AZ-512 admin class inline edit):
- Step 12 (Test-Spec Sync): traceability O9 -> Covered; new FT-P-62
+ FT-N-18 in blackbox-tests.md.
- Step 13 (Update Docs): AdminPage module doc gains the inline-edit
state slots, four new handlers, PATCH integrations row, expanded
i18n key list, tests section. architecture.md row 272 now lists
PATCH /api/admin/classes/{id} with AZ-513 deploy-gate caveat.
- Step 14 (Security Audit): cycle-4 delta report records one new
LOW finding (F-SAST-CY4-1 lost-update / mid-air-collision on
PATCH, by design per spec); verdict carries PASS_WITH_WARNINGS;
bun audit re-run clean.
- Step 15 (Performance Test): NFT-PERF-01 bundle = 291 332 B
(+757 B / +0.26% vs cycle 3; ~13.89% of 2 MB budget); PASS.
Tests 243 passed / 13 skipped / 0 failed (+12 AZ-512 cases).
Co-authored-by: Cursor <cursoragent@cursor.com>
12 KiB
Security Audit Report — Azaion UI
AMENDMENT 2026-05-13 — verdict superseded by cycle-3 delta report. See
_docs/05_security/security_report_cycle3_delta.md. Current verdict (post AZ-510 + cycle-2-tailbun update vite): PASS_WITH_WARNINGS (was FAIL). All HIGH-severity dependency advisories closed; OWASP A06 → PASS, A07 → PASS. The HIGH-severity F-SAST-1 (mission-planner/Google Geocode API key in git history) remains open but does not affect the production browser bundle. The cycle-2 evidence below is preserved verbatim as the audit history of record.AMENDMENT 2026-05-13 (cycle 4 — AZ-512) — see
_docs/05_security/security_report_cycle4_delta.md. Verdict carries: PASS_WITH_WARNINGS (unchanged). One new LOW finding (F-SAST-CY4-1 — lost-update / mid-air-collision admission onPATCH /api/admin/classes/{id}, by design per AZ-512 spec). No new dependencies;bun auditre-run clean. Implementation shipped against MSW stubs under user-authorized Option B; deploy gate to live admin/ stays open until AZ-513 lands.
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:
- The port-source key is a real secret in real git history → must be revoked + externalized following the AZ-499 pattern.
- 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 bymission-planner/src/flightPlanning/LeftBoard.tsx:114for address-to-coords lookups. - Production-bundle exposure: NONE today.
src/does not import frommission-planner/;Dockerfilebuilds onlysrc/-rooted Vite. The key is NOT indist/. - 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):
- Revoke the key at https://console.cloud.google.com/google/maps-apis/credentials (manual, OUT-OF-BAND, USER ACTION). Capture evidence.
- Externalize:
import.meta.env.VITE_GOOGLE_GEOCODE_KEYinmission-planner/src/config.tswith fail-soft if unset. - Update
mission-planner/.env.examplewith placeholder. - Extend
tests/security/banned-deps.jsonowm_key_in_source(or add a siblinggoogle_key_in_source) section to also block the literal Google key. - Long-term: route geocoding via suite-side proxy when the SPA needs it.
- See:
static_analysis.mdF-SAST-1.
F-DEP-1 — Vite Arbitrary File Read via Dev Server WebSocket — HIGH
- Location:
vite@6.4.1(resolved inbun.lock, bothui/andmission-planner/roots) - Advisory: GHSA-p9ff-h696-f583
- Description: WebSocket endpoint exposed by
vite devallows arbitrary local-file read via path traversal. - Production-bundle exposure: NONE. The Vite dev server is never present in production (
Dockerfilefinal stage isnginx:alpineserving staticdist/). - Developer-machine exposure: HIGH if
bun run dev --hostis ever used (binding to0.0.0.0); MODERATE for the defaultlocalhostbinding (still a browser-side script attack vector via DNS rebinding). - Remediation:
bun update vitein both roots →vite >= 6.4.2. Verify build + fast tests still pass. - See:
dependency_scan.mdF-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 | HIGH | 6.4.1 | >= 6.4.2 (bun update vite) |
vite |
GHSA-4w7w-66w2-5vf9 | MODERATE | 6.4.1 | >= 6.4.2 (same upgrade) |
postcss |
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 viteinui/andmission-planner/. Re-runbun auditto 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 highstep to.woodpecker/build-arm.ymlso 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.ymlafterdocker build(2 SP). - F-SAST-2: Bundle Leaflet marker icon locally instead of
unpkg.comCDN 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 intotests/security/banned-deps.jsonand use the existingcheck-banned-deps.mjswidening (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-unprivilegedmigration +HEALTHCHECKdirective (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 —
/adminroute 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:
- 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.
- 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
- All findings from Phases 1–4 included
- No duplicate findings (cross-references used instead)
- Every finding has remediation guidance
- Verdict matches severity logic (FAIL on any HIGH)
- Production-vs-dev impact distinguished for each HIGH finding
- Cycle 2 deltas (AZ-498, AZ-499) explicitly reviewed for regressions