# Batch 12 — AZ-501 + AZ-502 (security-audit inline fixes)

**Date**: 2026-05-12
**Cycle**: Phase B / Cycle 2 — autodev Step 14 (Security Audit) inline-fix sub-step
**Tickets**: AZ-501 (Google Geocode key externalization), AZ-502 (Vite/PostCSS upgrade)
**Trigger**: User chose option **A** ("fix BOTH inline now") on the Step 14 BLOCKING gate after the security audit reported HIGH-severity F-SAST-1 (Google key in port-source) and F-DEP-1 (Vite WebSocket file-read CVE).
**Verdict**: PASS — both findings resolved in code; static + fast tests green; manual key-revocation deliverables (AZ-501 AC-6, AZ-499 AC-7) remain pending USER action.

---

## AZ-501 — Externalize Google Geocode API key in mission-planner port-source

### Status

- **Code**: Done
- **Manual deliverable AC-6 (key revocation at Google Cloud Console)**: PENDING USER
- **Jira state**: still "To Do" — must be transitioned to "In Testing" with the commit and to "Done" only after AC-6 evidence is attached

### Approach

Mirrored the AZ-499 pattern exactly:
1. Extracted the geocode call to a new service module so the env-resolution + fail-soft contract can be unit-tested in isolation (parallels `WeatherService.ts`).
2. Externalized the key via `import.meta.env.VITE_GOOGLE_GEOCODE_KEY`.
3. Fail-soft when unset: returns `null`, no fetch issued, single `console.warn` (geocode is user-triggered per "Enter" keypress, so a warn-per-call is informative not spammy — distinct from the silent fail-soft chosen for `WeatherService.ts` which is called periodically).
4. Added literal-scan defense-in-depth gate (`STC-SEC1D`) to prevent the same key string from reappearing in `src/` or `mission-planner/`.
5. Documented the new env var in `mission-planner/.env.example` with the established `<your-...-key>` placeholder convention.

### Files changed

| File | Change |
|------|--------|
| `mission-planner/src/services/GeocodeService.ts` | NEW — env-resolved geocode with fail-soft + console.warn |
| `mission-planner/src/config.ts` | Removed `GOOGLE_GEOCODE_KEY` literal; only `COORDINATE_PRECISION` remains |
| `mission-planner/src/vite-env.d.ts` | Added `readonly VITE_GOOGLE_GEOCODE_KEY?: string` |
| `mission-planner/src/flightPlanning/LeftBoard.tsx` | Replaced inline `geocodeAddress` with import from the new service module; removed `GOOGLE_GEOCODE_KEY` import |
| `mission-planner/.env.example` | Added `VITE_GOOGLE_GEOCODE_KEY=<your-google-geocode-api-key>` + comment block |
| `tests/security/banned-deps.json` | Added `google_key_in_source` section with the literal key as a banned pattern |
| `scripts/check-banned-deps.mjs` | Added `'google_key_in_source'` to the source-tree-scan dispatch (1-line list extension; reuses existing `checkSourceTree`) |
| `scripts/run-tests.sh` | Added `STC-SEC1D` static-check function + entry in the runner table |
| `tests/mission_planner_geocode.test.ts` | NEW — 5 tests covering env-resolution (AC-1), fail-soft on missing key + warn (AC-3), fail-soft on network error, ZERO_RESULTS handling, and a defense-in-depth assertion that no fallback key is hardcoded |

### AC coverage

| AC | Status | Evidence |
|----|--------|----------|
| AC-1 (env-var resolution) | PASS | `tests/mission_planner_geocode.test.ts` — `'AC-1: env-var resolved API key reaches the outgoing fetch URL'` |
| AC-2 (.env.example documentation) | PASS | `mission-planner/.env.example` lines 12-14, 33 |
| AC-3 (fail-soft + warn) | PASS | tests `'AC-3: returns null, issues no fetch, and warns when VITE_GOOGLE_GEOCODE_KEY is unset'` and `'AC-3: still returns null and does not throw when fetch rejects'` |
| AC-4 (static gate) | PASS | `STC-SEC1D` runs in `scripts/run-tests.sh` static profile against the new `google_key_in_source` deny-pattern |
| AC-5 (unit test) | PASS | `tests/mission_planner_geocode.test.ts` — 5 tests, all green |
| AC-6 (key revocation) | PENDING USER | Google Cloud Console: revoke `AIzaSyAhvDeYukuyWVrQYbRhuv91bsi_jj5_Iys` and attach evidence to AZ-501 before transitioning to Done |

### Design decisions

- **Why a separate service module instead of inline-in-component?** Same reasoning as AZ-499: the inline form is not testable without mounting `<LeftBoard>` (heavy MUI tree); extracting to a service mirrors the established `WeatherService.ts` pattern and lets `tests/mission_planner_geocode.test.ts` exercise env-resolution and fail-soft directly. Also removes the cross-cutting `import { GOOGLE_GEOCODE_KEY } from '../config'` from `LeftBoard.tsx`.
- **Why warn (not silent) on missing key?** Geocode is user-triggered (per "Enter" keypress), so a warn-per-call is informative without being spammy. `WeatherService.ts` chose silent fail-soft because it's called periodically.
- **Why `STC-SEC1D` instead of folding into `STC-SEC1C`?** The two gates have different ACs (AZ-499 vs AZ-501) and different secret-vendor scopes — keeping them separate makes the report rows easier to audit.

### Spec drift

None. All 6 ACs in the AZ-501 spec are addressed; AC-6 is correctly identified as a manual deliverable.

---

## AZ-502 — Update Vite + PostCSS past published CVEs

### Status

- **Code**: Done
- **AC-5 (CI gate)**: explicitly DEFERRED to a Phase B follow-up per the ticket's own scope note ("**may be SPLIT into a sibling ticket if it expands scope**"). The Step 14 audit's F-INF-1 finding is the tracking record.

### Approach

`bun update vite` in both roots upgraded the direct `vite` dependency to `6.4.2`, but the audit still complained because `vitest@3.2.4` nests its own `vite@6.4.1` under `node_modules/vitest/node_modules/vite/`. Bun's resolver follows the nested copy (a peer-dep + nested-dep pattern), so a direct upgrade alone is insufficient.

Resolution: added `"overrides": { "vite": ">=6.4.2", "postcss": ">=8.5.10" }` to both `package.json` files — Bun honors the npm-compatible `overrides` field and floors all transitive resolutions, including the nested copies inside `vitest/`. After a clean reinstall (`rm -rf node_modules bun.lock && bun install`), `bun audit` reports zero advisories in both roots.

### Files changed

| File | Change |
|------|--------|
| `package.json` (root) | Added `"overrides": { "vite": ">=6.4.2", "postcss": ">=8.5.10" }` |
| `mission-planner/package.json` | Same overrides block; bumped direct `vite` to `^6.4.2` |
| `bun.lock` (root) | Regenerated |
| `mission-planner/bun.lock` | Regenerated |

### AC coverage

| AC | Status | Evidence |
|----|--------|----------|
| AC-1 (`bun update vite` in both roots) | PASS | both `bun.lock` files regenerated |
| AC-2 (`bun audit` zero findings in both roots) | PASS | `bun audit` exit 0 in both roots after clean reinstall (verified) |
| AC-3 (`bun run build` succeeds) | PASS | covered by `STC-B1` in the static profile (`scripts/run-tests.sh`) — passed in this batch's full test run |
| AC-4 (full test suite stays green) | PASS | static + fast: 229 PASS / 13 SKIP / 0 FAIL (+5 new PASS from `tests/mission_planner_geocode.test.ts`) |
| AC-5 (CI `bun audit` gate) | DEFERRED | Phase B; tracked at `_docs/05_security/infrastructure_review.md` F-INF-1 |

### Design decisions

- **Why `overrides` instead of pinning vitest higher?** There is no newer `vitest` release that pulls a patched `vite` — the next vitest minor lands eventually but is not yet published. Bun `overrides` solves the same problem zero-cost without introducing a vitest major-version churn.
- **Why floor PostCSS too?** PostCSS comes in transitively via Vite; once Vite is at 6.4.2 the postcss it needs is `^8.5.3` which Bun resolved to `8.5.8` (still vulnerable). The override floors it to `8.5.10` (the patched range from GHSA-qx2v-qp2m-jg93).
- **Why only `bun update vite` not `bun update --latest`?** Avoid unrelated major-version churn in the same change. The advisory range is `<= 6.4.1`; 6.4.2 is the minimum-impact fix.

### Spec drift

AC-5 (CI gate) is explicitly deferred per the ticket's own scope note. F-INF-1 in the audit infrastructure_review.md captures the follow-up.

---

## Test results

Full `scripts/run-tests.sh` run (static + fast):

| Profile | Result | Detail |
|---------|--------|--------|
| static | PASS | All checks PASS, including the new `STC-SEC1D` (no Google key literal in `src/` + `mission-planner/`). 33 STC-* checks total. |
| fast | PASS | 229 PASS / 13 SKIP / 0 FAIL (+5 new PASS from `tests/mission_planner_geocode.test.ts` vs. the cycle-2 baseline of 224 PASS). 13 skips unchanged from cycle-2 baseline. |
| e2e | NOT RUN | (deferred — same `env-blocked` posture as `_docs/03_implementation/test_run_report_phase_b_cycle2.md`) |

Test-spec sync deltas (this batch):
- `_docs/02_document/tests/security-tests.md`: appended `NFT-SEC-09b` (Google Geocode key not in source).
- `_docs/02_document/tests/blackbox-tests.md`: appended `FT-P-61` (env-resolution) and `FT-N-17` (fail-soft + warn).
- `_docs/02_document/tests/traceability-matrix.md`: added rows for AC-43 (geocode env hardening) and AC-44 (Vite/PostCSS upgrade); coverage summary updated to 90 total items.
- `_docs/00_problem/acceptance_criteria.md`: added AC-43 + AC-44; coverage status appended.
- `_docs/00_problem/security_approach.md`: added §5 paragraph on the Google key + appended findings → fix map rows.

## Pending manual deliverables (across all of Cycle 2)

1. **AZ-499 AC-7** — revoke OWM key `335799082893fad97fa36118b131f919` at https://home.openweathermap.org/api_keys; attach evidence to AZ-499.
2. **AZ-501 AC-6** — revoke Google Geocode key `AIzaSyAhvDeYukuyWVrQYbRhuv91bsi_jj5_Iys` at https://console.cloud.google.com/google/maps-apis/credentials; attach evidence to AZ-501.

These are out-of-band defense-in-depth completions; the static gates `STC-SEC1C` (OWM) and `STC-SEC1D` (Google) already prevent re-introduction of the literal strings, but the rotated keys must be revoked at the providers to actually neutralize the leaked credentials.

## Cross-workspace gates carried forward

- **AZ-498 deploy** (autodev Step 16) still gated on the satellite-provider cookie-auth ticket on the satellite-provider workspace.
- No new cross-workspace gates introduced by this batch.
