diff --git a/CHANGELOG.md b/CHANGELOG.md index 5bcb5e5..c33ec88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,46 @@ # Changelog -## [Unreleased] — 2026-03-31 +## [Unreleased] — 2026-04-01 + +### Security fixes (second-pass audit) + +- **Sport value not validated before YAML write** (`edit/server.py`) — `sport` field now validated against `SPORTS` allowlist before being written to the sidecar +- **No image content-type validation** (`edit/server.py`) — arbitrary file uploads rejected; only `image/*` content types accepted +- **XSS via unescaped filename in `innerHTML`** (`edit/server.py`) — `escapeHtml()` applied to filenames in `renderImageList` before interpolation +- **No upload size limit** (`edit/server.py`) — 50 MB limit enforced before writing to disk; returns HTTP 413 on oversize uploads +- **Exception message leaks internal paths** (`edit/server.py`) — 422 response now returns `type(exc).__name__` only, not `str(exc)` which could expose filesystem paths + +### Bug fixes — data (second-pass audit) + +- **Disambiguated ID not written into JSON body** (`writer.py`) — collision suffix was added to the filename but `detail["id"]` still held the original ID; fixed to update `detail["id"]` after disambiguation +- **`write_activity` return value ignored** (`cli.py`) — caller was using the pre-collision ID to build the index summary; now captures the canonical return value from `write_activity` +- **TOCTOU race in collision guard** (`writer.py`, `cli.py`) — concurrent workers could both see no existing file and overwrite each other; workers now write to unique `.pending.json` files and the main process arbitrates by quality score via `finalize_pending()` +- **`athlete.yaml` merge has no field allowlist** (`render/merge.py`) — `merge_all()` now applies only `_ATHLETE_EDITABLE` keys (`max_hr`, `ftp_w`, `hr_zones`, `power_zones`, `seasons`, `gear`) from the sidecar +- **Timezone offsets without colon** (`parsers/tcx.py`) — regex updated to `[+-]\d{2}:?\d{2}` so `+0200` is handled alongside `+02:00` +- **Power data in GPX extensions not parsed** (`parsers/gpx.py`) — extension tags `pwr`, `power`, and `watts` now parsed; MMP no longer always `None` for GPX files with power meters +- **`speeds` array misaligned with `coordinates`** (`simplify.py`) — speeds array now uses the same lat/lon null filter as coordinates + +### Bug fixes — frontend (second-pass audit) + +- **Hardcoded nav links ignore `BASE_URL`** (`Base.astro`) — `/`, `/stats/`, `/athlete/` nav hrefs now use `baseUrl` from `import.meta.env.BASE_URL` +- **Undeclared `error` variable in `uploadImages`** (`EditDrawer.svelte`) — catch block now uses `saveStatus`/`saveOk` (existing error state) instead of undeclared `error` +- **`ResizeObserver` stale closure in MmpChart** (`MmpChart.svelte`) — reactive variables keep the closure current so resize re-renders with correct data after range selection changes +- **`resetTrim` guard always true** (`ActivityCharts.svelte`) — tracks `lastResetTab` to force trim reset on every tab switch regardless of whether min/max happen to match +- **No `onDestroy` cleanup in ActivityCharts** (`ActivityCharts.svelte`) — chart SVG now removed on component unmount to prevent memory leaks +- **Invalid URL `tab` parameter shows blank content** (`AthleteView.svelte`) — `tab` query param validated against `TABS` array; invalid values fall back to `'power'` + +### Schema (second-pass audit) + +- **`activity_summary` missing `custom` property** (`bas-v1.schema.json`) — `merge.py` always adds `custom` to summaries but it wasn't declared; added to schema +- **`skiing` missing from SCHEMA.md sport enum** — added alongside sub-sport enum updates +- **Summary fields table incomplete** (`SCHEMA.md`) — `sub_sport`, `mmp`, `best_efforts`, `best_climb_m`, `preview_coords` all added to the summary fields table + +### Tests (second-pass audit) + +- **`test_id_utc_conversion`** (`test_writer.py`) — verifies non-UTC timestamps are converted to UTC in generated IDs +- **`test_build_summary_required_fields`** (`test_writer.py`) — verifies all schema-required fields present in `build_summary` output +- **Skiing and swimming sport variants** (`test_sport.py`) — `test_skiing_variants` and `test_swimming_variants` added +- **Non-canonical IDs in test fixtures** (`test_merge.py`) — fixture IDs updated to canonical `2024-01-01T080000Z-morning-ride` format ### Security fixes