mirror of
https://github.com/azaion/ui.git
synced 2026-06-21 09:41:11 +00:00
[AZ-501] [AZ-502] Cycle 2 Step 14 security audit + inline fixes
ci/woodpecker/push/build-arm Pipeline failed
ci/woodpecker/push/build-arm Pipeline failed
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>
This commit is contained in:
@@ -36,7 +36,7 @@ Every criterion must have a measurable value. Each row carries a unique ID
|
||||
| AC-17 | a11y — `ProtectedRoute` spinner | `role=status` + accessible label; loading state has a timeout | Component test asserting a11y attributes; integration test asserting timeout fallback | finding |
|
||||
| AC-18 | Browser support | Chromium-based + Firefox latest 2 versions render the SPA correctly | Manual smoke (no `browserslist` enforcement today) | `architecture.md` § 6 — **manual / aspirational** |
|
||||
| AC-19 | Mobile responsiveness | Header bottom-nav variant renders at < 768 px; main pages render at ≥ 768 px | Manual smoke at the two breakpoints | `Header.tsx:113-129`; `architecture.md` § 6 |
|
||||
| AC-20 | OpenWeatherMap key NOT in source | `import.meta.env.VITE_OPENWEATHERMAP_API_KEY` (or proxied via suite); zero hardcoded keys in any `src/` or `mission-planner/` module | Static check (regex against the current literal); CI step | P10; `mission-planner/src/utils/flightPlanUtils.ts:60` (current violation, Step 4 fix) |
|
||||
| AC-20 | OpenWeatherMap key NOT in source | `import.meta.env.VITE_OWM_API_KEY` (and `VITE_OWM_BASE_URL`); zero hardcoded keys in any `src/` or `mission-planner/` module | Static check (regex against the previously-committed literal — `STC-SEC1`, `STC-SEC1B`, `STC-SEC1C`); CI step | P10; closed cycle 2 / 2026-05-12 by AZ-448 (main SPA), AZ-499 (mission-planner); see also AC-42 |
|
||||
| AC-21 | UserSettings persistence — panel widths | Panel-width changes via `useResizablePanel` write back to `PUT /api/annotations/settings/user`; reload restores widths | Integration test: change width → reload → assert restored | P11; `src/hooks/useResizablePanel.ts` (current violation) |
|
||||
| AC-22 | RBAC client-side route gates | `/admin` and `/settings` redirect non-privileged users to `/flights` (or `/login` if not authenticated). Server-side 403 is the authoritative gate; UI gate is convenience | Integration test: log in as non-admin → navigate to `/admin` → assert redirect | finding (`/admin` route lacks role-gate — security PRIORITY) |
|
||||
| AC-23 | Auth refresh transparency | One refresh = one network round trip; **no UI re-render past `<ProtectedRoute>`** | Integration test asserting `<ProtectedRoute>` does not unmount during refresh | `architecture.md` § 6 NFR row "Auth refresh"; `04_verification_log.md` F2 |
|
||||
@@ -57,6 +57,10 @@ Every criterion must have a measurable value. Each row carries a unique ID
|
||||
| AC-38 | PhotoMode switcher (Regular / Winter / Night) | PhotoMode buttons emit values from the set `{0, 20, 40}` (Regular=0, Winter=+20, Night=+40). Switching mode: (a) re-filters the class list to entries whose `photoMode` equals the new mode; (b) if the previously-selected `classNum` is not in the new filtered set, auto-selects the first class of the new mode and emits `onSelect`. On annotation save, the wire `Detection.classNum` (a.k.a. *yoloId*) equals `classId + photoModeOffset`. | Component test on the mode-switch effect + integration test on the save payload | `modules/src__components__DetectionClasses.md` §22, §31-43; `data_model.md:84`; `components/11_class-colors/description.md:31-35`; `ui_design/README.md:127-128`; `ui_design/annotations.html:84-93` |
|
||||
| AC-39 | Tile-splitting endpoint + wire shape | `POST /api/annotations/dataset/{id}/split` exists and is callable from the dataset surface; success response is JSON with HTTP 200. `AnnotationListItem.isSplit: boolean` and `AnnotationListItem.splitTile: string \| null` (YOLO label `<class> <cx> <cy> <w> <h>`) are honored on read. When `isSplit === true` and `splitTile` is non-null, the client parses the 5-token YOLO label without throwing; malformed `splitTile` surfaces a user-visible error (no silent swallow). `DatasetItem.isSplit?: boolean` is read on the dataset list path (parent-suite-doc fix applied — see `_docs/_process_leftovers/2026-05-10_parent-suite-doc-fixes.md`). | Integration test against a fixture response; unit test on the YOLO-label parser with valid + malformed inputs | `components/07_dataset/description.md:28`; `data_model.md:104-105,130,164`; `modules/src__features__annotations.md:31,75`; `modules/src__types__index.md:24-28` |
|
||||
| AC-40 | Tile-zoom auto-zoom on split-image annotation open | When the user opens a `splitTile`-bearing annotation (double-click in `AnnotationsSidebar` or seek via the annotation list), `CanvasEditor` auto-zooms to the tile region encoded by `splitTile` (parsed per AC-39). The visible viewport rectangle equals the tile rectangle within ±1 px on each edge. A small visual tile-zoom indicator (icon / badge) is rendered while the tile zoom is active so the operator knows the view is constrained. **Currently MISSING** — finding #24 in `modules/src__features__annotations.md`; Step 4 / Phase B fix. | Component test on `CanvasEditor` with a `splitTile`-bearing annotation; assert viewport rect + presence of the tile-zoom indicator | `components/06_annotations/description.md:62, 103`; `modules/src__features__annotations.md:75` finding #24; `legacy/wpf-era.md` (OpenAnnotationResult seek + ZoomTo) |
|
||||
| AC-41 | Map tiles served by self-hosted `satellite-provider` via cookie auth | (a) `<TileLayer>` `url` prop equals `import.meta.env.VITE_SATELLITE_TILE_URL` (or the dev default `http://localhost:5100/tiles/{z}/{x}/{y}` when unset). (b) Every `<TileLayer>` the SPA renders carries `crossOrigin="use-credentials"` so the browser attaches the satellite-provider auth cookie on same-origin requests. (c) The classic/satellite map-type toggle, the `mapType` state, and the `MiniMap.Props.mapType` prop are absent. (d) A 401 / 503 from the tile endpoint MUST NOT crash the map; broken-tile placeholder is rendered for the failing cell. | Fast component tests (`src/features/flights/__tests__/satellite_tile.test.tsx`) + e2e infrastructure check (`e2e/tests/infrastructure.e2e.ts` AC-2) + STC-T1 typecheck + STC-FP22 i18n parity (post-key removal). Cycle-2 spec rows: FT-P-56, FT-P-57, FT-P-58, FT-P-59, NFT-RES-11. | Closed cycle 2 / 2026-05-12 by AZ-498 (epic AZ-497). `_docs/02_document/contracts/satellite-provider/tiles.md` v1.0.0 owns the wire shape. Cross-workspace prereq for production deploy: satellite-provider cookie-auth on `GET /tiles/{z}/{x}/{y}` (gated at autodev Step 16). |
|
||||
| AC-42 | mission-planner OpenWeatherMap config externalized; fail-soft on missing key | (a) `mission-planner/src/services/WeatherService.ts::getWeatherData(lat, lon)` builds the outbound URL from `import.meta.env.VITE_OWM_API_KEY` + `VITE_OWM_BASE_URL` (falls back to `https://api.openweathermap.org/data/2.5` when the base URL is unset; trailing slash on the base URL is stripped). (b) When `VITE_OWM_API_KEY` is unset/empty, `getWeatherData` returns `null` and issues NO outbound `fetch`. (c) Static check `STC-SEC1C` (`scripts/check-banned-deps.mjs --kind=owm_key_in_source`) FAILS on any future re-introduction of the previously-committed literal under `src/` or `mission-planner/`. (d) The previously-committed key MUST be revoked at the OpenWeatherMap dashboard (manual deliverable — defense-in-depth). | Fast tests (`tests/mission_planner_weather.test.ts`) + STC-SEC1C static check + STC-T1 typecheck. Cycle-2 spec rows: FT-P-60, FT-N-16, NFT-SEC-09 step 3. | Closed cycle 2 / 2026-05-12 by AZ-499 (epic AZ-497). Closes the AZ-482 source-scan gap (which previously only checked `src/` for the regex shape and `dist/` for the literal — `mission-planner/` stays out of `dist/` per AC-31, so the dist scan alone could not catch it). |
|
||||
| AC-43 | mission-planner Google Geocode config externalized; fail-soft on missing key | (a) The previously-hardcoded Google Geocode API key has been EXTRACTED from `mission-planner/src/config.ts` to a new `mission-planner/src/services/GeocodeService.ts` module that builds the outbound URL from `import.meta.env.VITE_GOOGLE_GEOCODE_KEY`. (b) When the env var is unset/empty, `geocodeAddress(address)` returns `null`, issues NO outbound `fetch`, and emits exactly one `console.warn` mentioning `VITE_GOOGLE_GEOCODE_KEY`. (c) Static check `STC-SEC1D` (`scripts/check-banned-deps.mjs --kind=google_key_in_source`) FAILS on any future re-introduction of the previously-committed literal under `src/` or `mission-planner/`. (d) The previously-committed key MUST be revoked at the Google Cloud Console (manual deliverable — defense-in-depth). (e) `LeftBoard.tsx` imports `geocodeAddress` from the service module; the inline geocode function and the `GOOGLE_GEOCODE_KEY` import are removed. | Fast tests (`tests/mission_planner_geocode.test.ts`) + STC-SEC1D static check + STC-T1 typecheck. Cycle-2 spec rows: FT-P-61, FT-N-17, NFT-SEC-09b. | Closed cycle 2 / 2026-05-12 by AZ-501 (filed during the security audit, `_docs/05_security/`). Mirrors the AZ-499 pattern (env var + fail-soft + literal-scan static gate + manual revocation). Manual deliverable AZ-501 AC-6 (key revocation at Google Cloud Console) PENDING USER. |
|
||||
| AC-44 | Vite + PostCSS supply chain past published CVEs | `bun audit` in BOTH `ui/` and `mission-planner/` reports zero advisories. Achieved by `bun update vite` plus `package.json` `overrides` flooring `vite >= 6.4.2` and `postcss >= 8.5.10` in both roots — required because `vitest@3.2.4` nests its own `vite` copy that the direct upgrade alone does not lift past the `<= 6.4.1` advisory range. | `bun audit` exit code 0 in both roots after `bun install` from a clean `node_modules`. CI gate (`bun audit --severity high` in `.woodpecker/build-arm.yml`) is a Phase B follow-up tracked at `_docs/05_security/infrastructure_review.md` F-INF-1. | Closed cycle 2 / 2026-05-12 by AZ-502 (filed during the security audit). Affected advisories: GHSA-p9ff-h696-f583 (HIGH — Vite WebSocket file-read), GHSA-4w7w-66w2-5vf9 (MODERATE — Vite path traversal), GHSA-qx2v-qp2m-jg93 (MODERATE — PostCSS XSS). Production-bundle exposure was NONE before the upgrade (Vite is dev-server-only); the upgrade closes the developer-machine exposure and the audit-tool noise. |
|
||||
|
||||
## Anti-criteria — explicit non-goals
|
||||
|
||||
@@ -70,7 +74,7 @@ Every criterion must have a measurable value. Each row carries a unique ID
|
||||
|
||||
## Coverage status
|
||||
|
||||
- **Currently met & enforced**: AC-02 (no token storage), AC-05 (annotation save URL — body shape pending), AC-06, AC-07, AC-08, AC-09, AC-10 (server cap; UI surface is a finding), AC-25 (sync path; async path is target-only), AC-31, AC-33, AC-34.
|
||||
- **Currently met & enforced**: AC-02 (no token storage), AC-05 (annotation save URL — body shape pending), AC-06, AC-07, AC-08, AC-09, AC-10 (server cap; UI surface is a finding), AC-20 (OWM key — closed cycle 2 by AZ-448 + AZ-499; STC-SEC1/SEC1B/SEC1C all green), AC-25 (sync path; async path is target-only), AC-31, AC-33, AC-34, AC-41 (self-hosted satellite tiles + cookie auth — closed cycle 2 by AZ-498; production deploy still gated on cross-workspace satellite-provider cookie-auth ticket), AC-42 (mission-planner OWM env-var hardening — closed cycle 2 by AZ-499; manual key revocation pending), AC-43 (mission-planner Google Geocode env-var hardening — closed cycle 2 by AZ-501; manual key revocation pending), AC-44 (Vite + PostCSS supply chain — closed cycle 2 by AZ-502; CI audit gate is a Phase B follow-up).
|
||||
- **Currently met but not enforced by CI**: AC-04 (enum values), AC-12 (i18n parity), AC-29 (typed `mediaType`), AC-35 (manual bbox draw), AC-37 (class picker — pending Step 4 backend-ordering verification), AC-38 (PhotoMode switcher).
|
||||
- **Currently violated — Step 4 fix candidates**: AC-01 (bootstrap refresh), AC-13 (i18n detector), AC-14 / AC-30 (class-delete dialog; `alert()` use), AC-15–AC-17 (a11y), AC-20 (OWM key), AC-21 (panel widths), AC-22 (route role-gate), AC-23 (refresh re-render — code-path correct, but bootstrap-refresh fix needed), AC-26 (numeric input hygiene), AC-27 (save error surfacing), AC-28 (overlay window), AC-36 (Ctrl-multi-select / Ctrl-wheel zoom / Ctrl-drag pan flagged "Partially missing"), AC-40 (tile-zoom auto-zoom — finding #24, no consumer of `splitTile` today).
|
||||
- **Currently violated — Step 4 fix candidates**: AC-01 (bootstrap refresh), AC-13 (i18n detector), AC-14 / AC-30 (class-delete dialog; `alert()` use), AC-15–AC-17 (a11y), AC-21 (panel widths), AC-22 (route role-gate), AC-23 (refresh re-render — code-path correct, but bootstrap-refresh fix needed), AC-26 (numeric input hygiene), AC-27 (save error surfacing), AC-28 (overlay window), AC-36 (Ctrl-multi-select / Ctrl-wheel zoom / Ctrl-drag pan flagged "Partially missing"), AC-40 (tile-zoom auto-zoom — finding #24, no consumer of `splitTile` today).
|
||||
- **Phase B targets (not currently in scope of `/document` Step 6)**: AC-11 (bundle gate), AC-18 (browser-list), AC-19 (mobile floor), AC-24 (SSE refresh re-subscribe), AC-25 async path, AC-32 (CI label assertions), AC-39 (tile-split endpoint — parent-suite-doc fix applied for `isSplit`; the YOLO-label parser hardening lands when the splitTile consumer is wired in Phase B).
|
||||
|
||||
@@ -159,14 +159,33 @@ Source: `src/api/sse.ts`; `ADR-008`; `architecture.md` § 7.
|
||||
`flights/` so no key ever reaches the browser (preferred; per
|
||||
`architecture.md` § Architecture Vision Open Questions item 8).
|
||||
|
||||
### Hardcoded Google Geocode API key — discovered cycle 2 audit (AZ-501)
|
||||
|
||||
- **File**: `mission-planner/src/config.ts:2` (originally — extracted to
|
||||
`mission-planner/src/services/GeocodeService.ts` by AZ-501).
|
||||
- **Production-bundle exposure**: NONE. `mission-planner/` is a port-source
|
||||
not built into `dist/` (`AC-31` / `STC-S5`).
|
||||
- **Git-history exposure**: HIGH — same threat class as the OWM key.
|
||||
- **Closed cycle 2** by AZ-501: env-resolved via `VITE_GOOGLE_GEOCODE_KEY`,
|
||||
fail-soft + single `console.warn` when unset, defended by `STC-SEC1D`
|
||||
(literal scan across `src/` + `mission-planner/`). The `/document` Step 6e
|
||||
retrospective missed this because mission-planner/ was treated as out-of-
|
||||
scope (port-source) — the security audit (`_docs/05_security/`) caught it
|
||||
via a broader source-tree grep, demonstrating the value of a separate
|
||||
audit pass.
|
||||
- **Manual deliverable PENDING USER**: revoke the key at the Google Cloud
|
||||
Console (AZ-501 AC-6).
|
||||
|
||||
### Other secrets
|
||||
|
||||
- **No other hardcoded keys** in `src/` per Grep audit at Step 4.
|
||||
- **No other hardcoded keys** in `src/` per Grep audit at Step 4 +
|
||||
cycle-2 security-audit (`_docs/05_security/static_analysis.md`).
|
||||
- Suite service URLs are not secrets (they are docker-network hostnames).
|
||||
- The bearer is the only sensitive value in browser memory, and it is
|
||||
short-lived.
|
||||
|
||||
Source: P10; `architecture.md` § Architecture Vision; finding (security).
|
||||
Source: P10; `architecture.md` § Architecture Vision; finding (security);
|
||||
`_docs/05_security/security_report.md` F-SAST-1.
|
||||
|
||||
---
|
||||
|
||||
@@ -304,8 +323,10 @@ pipeline today".
|
||||
| Annotation save body missing `Source`, `WaypointId`, wrong `time` field | AC-05 | Step 4 |
|
||||
| `X-Refresh-Token` not sent on long-video detect (#29) | — | Step 4 |
|
||||
| Numeric enum drift (`AnnotationStatus`, `MediaStatus`, `Affiliation`, `CombatReadiness`) | AC-04 | Step 4 (P9 alignment) |
|
||||
| No CSP / hardening headers in `nginx.conf` | — | Step 6 — track at suite level |
|
||||
| No vulnerability scan / SBOM / image signing in CI | — | Phase B |
|
||||
| No CSP / hardening headers in `nginx.conf` | — | Step 6 — track at suite level (cycle-2 audit F-INF-2 → Phase B) |
|
||||
| No vulnerability scan / SBOM / image signing in CI | — | Phase B (cycle-2 audit F-INF-3 / F-INF-4) |
|
||||
| Vite ≤ 6.4.1 + PostCSS < 8.5.10 — published CVEs (HIGH/MOD) | AC-44 | Closed cycle 2 by AZ-502 (`bun update vite` + `package.json` overrides) |
|
||||
| Hardcoded Google Geocode API key in `mission-planner/` port-source | AC-43 | Closed cycle 2 by AZ-501; manual key revocation PENDING USER |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user