chore(security): bump idna >=3.15 (CVE-2026-45409) + ignore disputed PyJWT advisory

- requirements.txt: pin idna>=3.15 to clear ReDoS in idna.encode() on
    crafted Unicode payloads. Transitive via anyio/httpx/requests/yarl,
    so the explicit floor stops a future downstream loosening from
    silently downgrading us.
  - security.yml: permanently --ignore-vuln CVE-2025-45768 (PyJWT). The
    advisory is disputed by the maintainers — "key length is chosen by
    the application" — and no fix version exists. Bambuddy is safe:
    auto-generates secrets via secrets.token_urlsafe(64) and rejects
    file-loaded secrets shorter than 32 chars (auth.py:177, :184).
  - security.yml: drop the stale Pygments --ignore-vuln CVE-2026-4539.
    Pygments has been patched upstream; the ignore no longer matches
    anything.
This commit is contained in:
maziggy 2026-05-20 12:36:32 +02:00
parent ed27b27adb
commit 9d440beb80
3 changed files with 20 additions and 4 deletions

View file

@ -132,10 +132,17 @@ jobs:
- name: Run pip-audit
id: pip-audit
run: |
# CVE-2026-4539: low-severity ReDoS in Pygments AdlLexer (indirect dep via mkdocs-material/pytest/rich).
# No fix available yet. Remove --ignore-vuln once Pygments releases a patched version.
pip-audit --desc on --format json --output pip-audit-results.json --ignore-vuln CVE-2026-4539 || echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT
pip-audit --desc on --ignore-vuln CVE-2026-4539 || true
# CVE-2025-45768 (PYSEC-2025-183 / GHSA-65pc-fj4g-8rjx): disputed by PyJWT maintainers.
# Advisory says "key length is chosen by the application that uses the library" — no
# PyJWT fix exists or will exist. Bambuddy is safe: backend/app/core/auth.py:184 uses
# secrets.token_urlsafe(64) (~86 chars of entropy) for auto-generated secrets and
# rejects file-loaded secrets shorter than 32 chars at :177. Keep ignored permanently.
pip-audit --desc on --format json --output pip-audit-results.json \
--ignore-vuln CVE-2025-45768 \
|| echo "vulnerabilities_found=true" >> $GITHUB_OUTPUT
pip-audit --desc on \
--ignore-vuln CVE-2025-45768 \
|| true
- name: Upload audit results
if: always()

View file

@ -10,6 +10,10 @@ All notable changes to Bambuddy will be documented in this file.
### Changed
- **Slice modal: cross-printer 3MFs now re-slice transparently, banner removed, modal fully i18n'd** — Previous behaviour disabled the Slice button whenever the source 3MF's bound printer model didn't match the user's picked printer profile, on the theory that the slicer CLI "cannot re-slice a 3MF for a different printer" and would silently fall back to embedded settings to produce a wrong-printer file. Step 0 empirical test on 2026-05-20 disproved that: an 18-color H2D-bound `Trent900.3mf` sliced via the X1C bundle (`POST /slice` with `bundle=cb…X1C, printerName=# Bambu Lab X1 Carbon 0.4 nozzle`) produced 2.3 MB of genuinely X1C-compatible G-code in 1.8 s — `printer_model` overridden to `Bambu Lab X1 Carbon`, `printable_area` to 256×256 (X1C bed, not H2D's 350×320), `printable_height` 250 (vs 325), `bed_exclude_area` populated with X1C's 18×28 corner zone, `nozzle_diameter` single 0.4 (vs H2D's dual `0.4,0.4`), and the full X1C `machine_start_gcode` sequence baked in. The sidecar takes printer / process / first-N filament names from the picked bundle and only inherits embedded values for unused trailing slots — bed size, kinematics, start sequence all come from the target. **Behavioural change**: dropped `!printerMismatch` from the SliceModal `isReady` predicate so the Slice button stays enabled when models differ. The amber banner was first softened to an info message, then removed entirely — re-slicing across printers is now just a normal slice, the picker UI already shows which printer was picked, no second confirmation needed. **Dead-code removal (same drop)**: with no banner, the `source_printer_model` field on the `/library/files/{id}/plates` and `/archives/{id}/plates` responses had zero consumers; the `extract_source_printer_model_from_3mf` helper in `threemf_tools.py` (which opened the 3MF zip and read `Metadata/project_settings.config` on every plate request) had zero callers. Removed both response keys, both backend extractions, both `threemf_tools` imports, the helper itself, its 6 unit tests, the `source_printer_model` field from `frontend/src/types/plates.ts` (PlateMetadata + LibraryFilePlatesResponse), and 2 obsolete SliceModal tests that exercised the now-impossible matched-printer / legacy-archive paths. **i18n discipline cleanup (same drop, per [[feedback_no_followups]] + [[feedback_translate_dont_fallback]])**: every t() callsite in SliceModal.tsx had an inline English `defaultValue:` or positional-second-arg English fallback — 22 sites in total. With 8 locales shipped, those fallbacks are dead weight at best, and an actual i18n-violation when the key is missing because non-English users would silently see English. Audit found 3 keys (`slice.bundle`, `slice.bundleNone`, `slice.bundleAllRequired`) that had **no** corresponding entry in any locale file — they were being served from the inline English fallback exclusively, meaning every non-English user was already seeing those three labels in English. Added all 3 to all 8 locales with real translations, then stripped the English fallback from every t() call in SliceModal.tsx. The `slice.printerMismatch` key was removed from all 8 locales (banner is gone). **Why this matters**: a recurring pain point for users importing MakerWorld project files where the original creator's printer often differs from the user's; previously they had to round-trip through BambuStudio's "convert project" flow to re-export. Now Bambuddy re-slices in-place with no UI friction. **Tests**: the existing SliceModal "shows mismatch warning AND disables Slice" test was rewritten to assert "does not surface any cross-printer banner AND keeps Slice enabled when models differ" (regression guard against the gate being re-added); 2 obsolete tests deleted. 32 SliceModal tests green (was 34, -2 dead tests); 49 threemf_tools tests green (was 55, -6 helper tests); 24 plates-route tests green; frontend build clean; backend ruff clean; i18n parity check passes 4858 keys × 8 locales (net +2 vs pre-fix: +3 bundle keys, -1 printerMismatch).
### Security
- **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.
- **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).
### Fixed
- **Scheduler: queue items with `force_color_match` filament overrides now produce a correct AMS mapping at dispatch (#1437, fixed by external PR #1440 from @Person2099)** — Contributor's own bug report and fix. He had a queue item with `filament_overrides: [{slot_id: 1, type: "PLA", color: "#CBC6B8", force_color_match: true}]` and `ams_mapping: null`, expecting Bambuddy to translate the override into a slot mapping at dispatch time. Instead the scheduler dispatched with `ams_mapping: null` and the P1S fell back to type-only AMS matching, picking the wrong-colour slot. **Two-layer root cause** he traced end-to-end. **(1) `backend/app/services/filament_requirements.py:69`**: `extract_filament_requirements(file_path, plate_id=None)` fell through to `_collect_filaments(root, filaments)` whose XPath `./filament` only matches direct children of `<config>`. Modern BambuStudio 3MFs wrap filaments inside `<plate>` elements, so this XPath returned `[]` on every modern multi-plate 3MF when no specific plate was targeted — which is the standard scheduler call shape for queue items without a pinned plate. The downstream "no AMS mapping" cascade ALL flowed from this empty filament_reqs result. Fix walks `<plate>` elements first, dedupes by `slot_id` (highest `used_grams` wins on ties — sane because BambuStudio slots are project-wide and the entry that extruded the most is the most representative for AMS planning), and preserves the old `./filament` XPath as a fallback when no `<plate>` elements are present, so legacy 3MFs continue to parse unchanged. **(2) `backend/app/services/print_scheduler.py:792` — defence in depth**: even with (1) in place, edge cases exist where `_get_filament_requirements` can still return None (3MF missing `slice_info.config` entirely, IO failure during ZIP extraction, etc). New `_build_override_direct_mapping(force_overrides, status)` helper kicks in at exactly that moment when `force_color_match` overrides are present — builds the requirement list directly from the overrides (`slot_id`, `type`, `color`, empty `tray_info_idx`) and delegates to the existing `_match_filaments_to_slots()` cascade against the printer's loaded AMS state. Wrong-colour slot credit via the cascade's type-only fallback is impossible-by-construction because the upstream `_get_missing_force_color_slots()` printer-eligibility gate at `:590` already requires an exact `(type, normalised colour)` pair to be loaded *before* the printer is even considered for the job, so by the time `_build_override_direct_mapping` runs the exact match is guaranteed in the loaded set and the cascade's `exact_match` branch wins (colour normalisation is identical on both sides — `tray_color.replace("#", "").lower()[:6]`). Pref-only overrides (without `force_color_match`) intentionally do NOT trigger the fallback — they keep the pre-PR "no mapping, printer picks defaults" behaviour, so the new fallback is strictly opt-in via `force_color_match: true`. **Backwards-compat triple-checked**: legacy 3MF format unchanged (preserved fallback path); `plate_id != None` branch untouched (entire fix is inside the `else` of `if plate_id is not None`); `filament_overrides=None` / `[]` / no-force-entries all preserve the existing `return None` path; malformed JSON in `filament_overrides` is caught by the existing try/except, logged, and still returns None. **Tests** (22 new across two files; all pass on `pytest -n 30`): `backend/tests/unit/services/test_filament_requirements.py` — 4 tests covering the `plate_id=None` modern-format path, multi-plate collection, slot-dedup-by-highest-grams, and single-plate-modern-format. `backend/tests/unit/test_scheduler_force_color_ams_fallback.py` — 18 tests across `TestBuildOverrideDirectMapping` (single override matches AMS slot, empty AMS returns None, no colour match still produces a mapping length, multi-override produces multi-element mapping, external spool match yields global_tray_id 254, `tray_info_idx` is cleared) and `TestComputeAmsMappingFallback` (fallback used when reqs empty + force overrides present, fallback NOT used when no force_color flag, fallback NOT used when overrides None, normal path still used when reqs available, printer-status-unavailable returns None gracefully). 5079 backend tests + ruff + frontend build all clean post-merge; #1457/#1459/#1440 verified non-interacting (different services, different code paths, different timings). External-PR-checklist (per [[feedback_pr_changelog_required]]): contributor doesn't add CHANGELOG, this entry added by Martin post-merge.

View file

@ -60,6 +60,11 @@ passlib[bcrypt]>=1.7.4
ldap3>=2.9.0
pyotp>=2.9.0
# Transitive dep pin: idna<3.15 has CVE-2026-45409 (ReDoS on encode() with
# crafted Unicode). Pulled in by anyio/httpx/requests/yarl; pin the floor
# so we don't regress when a downstream loosens its constraint.
idna>=3.15
# HTTP client (used for OIDC token exchange)
httpx>=0.26.0