diff --git a/CHANGELOG.md b/CHANGELOG.md index cdcb283b4..916a0c0c5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ All notable changes to Bambuddy will be documented in this file. - **idna: bump to `>=3.15` to clear CVE-2026-45409 (ReDoS in `idna.encode()` with crafted Unicode payloads, e.g. `"٠" * N` or `"・" * N + "漢"`)** — Transitive dep pulled in by anyio / httpx / requests / yarl; not directly pinned, which is why it lingered at 3.13. Added an explicit `idna>=3.15` floor in `requirements.txt` between Authentication and HTTP-client blocks with a comment explaining why it's pinned (so a future downstream loosening doesn't silently downgrade us). Verified via `pip-audit` clean post-upgrade. - **starlette: bump floor to `>=1.0.1` to clear PYSEC-2026-161** — `starlette` is a transitive dep pulled in by fastapi, whose range still admits the vulnerable 1.0.0 build, so a fresh `pip install` would silently pick it back up. Added an explicit `starlette>=1.0.1` floor in `requirements.txt` under the urllib3 pin with a why-comment matching the same pattern as the idna/urllib3 entries. Release-notes reviewed for both 1.0.1 (single fix: ignore malformed `Host` header when constructing `request.url`) and 1.1.0 (the resolver actually picked up 1.1.0): three behavioural changes — `FileResponse` falls back to `application/octet-stream` when `mimetypes.guess_type()` can't resolve (Bambuddy has 2 `FileResponse` calls without explicit `media_type`, both serving `index.html` where guess_type still resolves to `text/html`, plus custom-icon serving in `external_links.py:261` where the new fallback is a security improvement), `HTTPEndpoint` only dispatches standard HTTP verbs (`grep` found zero `HTTPEndpoint` usages in Bambuddy — pure FastAPI router code), `StaticFiles.lookup_path` rejects absolute paths in *requests* (the 4 mounts in `main.py:5503-5525` pass absolute *base directories* to the constructor, which is unaffected — only path-traversal-style request paths get rejected). Full backend test suite green (5300/5301; the 1 failure is a pre-existing `-n 30` parallelism flake unrelated to starlette and passes in isolation). Verified clean via `pip-audit` post-upgrade. - **PyJWT CVE-2025-45768 (PYSEC-2025-183 / GHSA-65pc-fj4g-8rjx): permanently ignored in pip-audit** — Advisory is disputed by the PyJWT maintainers, with the advisory description literally noting *"this is disputed by the Supplier because the key length is chosen by the application that uses the library."* `fix_versions=[]` on the advisory confirms no PyJWT patch exists or will exist. Bambuddy is not affected: `backend/app/core/auth.py:184` auto-generates secrets via `secrets.token_urlsafe(64)` (~86 chars of entropy, far above any sane minimum) and the file-loaded path at `:177` rejects secrets shorter than 32 chars. Added a permanent `--ignore-vuln CVE-2025-45768` to `.github/workflows/security.yml` with an inline comment citing the file:line evidence so a future maintainer reviewing the ignore list sees why it's load-bearing. Also dropped the stale `--ignore-vuln CVE-2026-4539` for Pygments — Pygments has since shipped a patched version and the ignore is no longer load-bearing (verified: `pip-audit --ignore-vuln CVE-2025-45768` alone reports clean). +- **Trivy DS-0026 (`Dockerfile.test` missing HEALTHCHECK): silenced via `HEALTHCHECK NONE`** — The test image runs `pytest` and exits; there is no long-running service to probe, so any HEALTHCHECK we added would be cargo-cult noise. `HEALTHCHECK NONE` is the documented Docker directive to explicitly opt out of any inherited healthcheck and is the way Trivy expects projects to signal "this image is not a service." Closes code-scanning alert #813. ### Fixed - **Support bundle + bug-report submission now include the live diagnostic snapshot** — Three diagnostics (Connection Diagnostic per printer, Virtual Printer Setup Diagnostic per enabled VP, Log Health Scanner) have shipped on the System page and inline in the bug-report bubble since 6bc6a1d6 / e222a0ef / ed31b8f4, but the results were only ever shown to the *user* — never persisted into the downloadable support ZIP or the submitted GitHub issue. A report saying "looks broken in Bambuddy" arrived with no actionable signal beyond raw logs. **Fix**: new `services/diagnostic_snapshot.collect_diagnostic_snapshot` runs all three concurrently with an outer per-probe 15 s wall-clock cap (so a hung interface adds at most ~15 s to bundle generation regardless of fleet size — `asyncio.gather`, total ≈ max(per-cap) not sum). Fail-soft per probe: a crash inside one printer's check emits `{"printer_id": N, "error": "..."}` for that entry rather than nuking the whole snapshot — partial result beats a 500. Wired into `_collect_support_info()` so both flows (`POST /support/bundle` and `POST /bug-report/submit` via `support_info=...`) pick up the new `diagnostics` top-level key without their own changes. **Private-data sanitization** — the diagnostic schemas embed raw IPv4 in three places (`PrinterDiagnosticResult.ip_address`, network-mode check's `params.{printer_ip, host_ip}`, VP diagnostic's `params.bind_ip`), and the snapshot adds printer names. None of those should leak. The snapshot now runs a recursive sanitizer on the full result tree before returning: known DB-listed values (printer name, IP, serial, access code) get the same `[PRINTER]/[IP]/[SERIAL]/[ACCESS_CODE]` labels the log sanitizer already applies (via the shared `collect_sensitive_strings`), and an IPv4-regex fallback catches IPs the DB doesn't know about — most importantly the Bambuddy host IP returned by `_get_host_ip()` and any VP `bind_ip` the user picked at setup. Live-DB smoke test confirms zero raw IPv4 instances in the serialized snapshot output. **Progress indicators**: the bubble's "submitting" view and the System page's Download button now render a static four-line checklist showing what's running (printer connectivity → VP setup → log scan → submit/build ZIP) — communicates the longer wait honestly without faking server-side phase progress we can't actually track. **Tests**: 6 new in `test_diagnostic_snapshot.py` — empty-input shape stable, per-printer / per-VP result coverage, fail-soft on a single-probe crash, `timed_out` marker when a probe exceeds the per-probe cap (test patches the cap to 0.05 s), end-to-end IP sanitization across all five field shapes (top-level `ip_address`, `printer_ip`, `host_ip`, `bind_ip`, plus IPs embedded in log-health sample lines) with a final regex sweep over the JSON-serialized result asserting zero raw IPv4 escapes, concurrent execution proof (4 × 0.2 s probes complete in < 0.5 s, would be 0.8 s sequential). Existing 27 BugReportBubble + SystemInfoPage frontend tests still pass; 9-locale i18n parity check clean (4993 leaves per locale, 9 new keys added with real translations everywhere — no English fallback). Backend ruff clean. diff --git a/Dockerfile.test b/Dockerfile.test index 2ff586185..cf782eb51 100644 --- a/Dockerfile.test +++ b/Dockerfile.test @@ -37,6 +37,11 @@ ENV PYTHONUNBUFFERED=1 ENV DATA_DIR=/app/data ENV TESTING=1 +# Test image runs pytest and exits — there is no long-running service +# to probe. HEALTHCHECK NONE is the documented Docker opt-out and +# silences Trivy DS-0026 without adding meaningless probe logic. +HEALTHCHECK NONE + # Default command runs pytest (excluding docker integration tests). # -v dropped: 5300+ "PASSED foo::bar" lines per worker eat noticeable # stdout I/O time and clutter test_docker.sh output. --tb=short still