chore(deps): clear every npm audit and pip-audit finding

Frontend:
- react-router/-dom 7.18.1 -> 7.18.2. The RSC-mode CSRF advisory was carried
  as a documented exception in the audit gate because its only fix was the
  8.3.0 major; upstream backported it, so the exemption lapsed on its own --
  an entry only holds while fixAvailable.isSemVerMajor is true. The allowlist
  is now empty; the machinery stays for the next one.
- dompurify 3.4.12 -> 3.4.13. Ships in the app, but the path is unreachable:
  no hooks registered, IN_PLACE never used.
- js-yaml override ^4.3.0 -> ^5.2.3 (fix not backported below 5.x, so a
  major) and nanoid override ^3.3.18. Both dev-only, via eslint and postcss.
  eslintrc calls only load(), on the legacy .eslintrc.yml path this repo does
  not use; eslint, vite build and 2861 frontend tests pass on it.

Backend:
- cryptography >=48.0.1 -> >=50.0.0, aiohttp >=3.14.0 -> >=3.14.3, pyopenssl
  >=26.3.0 -> >=26.4.0. CI resolves from scratch and was already installing
  the fixed releases; the floors cover the case CI does not, an existing venv
  where >= is satisfied and `pip install -r` upgrades nothing. pyOpenSSL has
  to move with cryptography -- each release caps it to a narrow window, so a
  stale pyOpenSSL pins cryptography below its own fix line.
This commit is contained in:
maziggy 2026-08-08 13:20:18 +02:00
parent 73eec29358
commit c8e5ecc23a
7 changed files with 54 additions and 52 deletions

View file

@ -203,15 +203,12 @@ jobs:
vulns = data.get('vulnerabilities', {})
# Documented advisory exceptions: high/critical findings whose only offered
# 'fix' is a semver-major change and which do not apply to how Bambuddy ships.
# Keyed by GHSA id; RE-REVIEW ON EVERY react-router BUMP.
# GHSA-qwww-vcr4-c8h2 - React Router RSC-mode CSRF. Bambuddy is a Vite SPA
# using BrowserRouter with no RSC runtime (@react-router/server is NOT
# installed), so the vulnerable code path is unreachable. No non-major fix
# exists (7.18.1 is the most-patched 7.x - it clears 14 other advisories that
# older 7.x carry - and the RSC fix landed only in the 8.3.0 major). react-router
# /-dom are pinned to 7.18.1 in package.json. If a non-major fix ships, this stops
# being exempt (major-only guard below) and the gate fails until we take it.
ALLOWLIST = {'GHSA-qwww-vcr4-c8h2'}
# Keyed by GHSA id. An entry only holds while the fix stays major-only (see
# fix_is_major below) - once upstream backports, the gate fails until we take
# the patch. That is what retired the one entry this list used to carry:
# GHSA-qwww-vcr4-c8h2 (React Router RSC-mode CSRF) shipped in 7.18.2, so the
# pin moved rather than the exception staying.
ALLOWLIST = set()
def advisory_ids(name, seen=None):
seen = seen if seen is not None else set()
if name in seen:

View file

@ -128,8 +128,10 @@ All notable changes to Bambuddy will be documented in this file.
- **Force color match dispatched a print onto the wrong PLA variant — Matte jobs went to Basic and Silk printers alike, and the wrong AMS slot on a printer holding two same-colour variants (#2650, reporter @MartinNYHC)** — With **Force color match** on, a job sliced for **White PLA Matte** was dispatched to every printer that had *any* white PLA loaded — the ones holding White PLA **Basic** and White PLA **Silk+** included — so a matte model came out glossy on the wrong machine. **Root cause.** Bambu's MQTT status reports every PLA sub-variant as `tray_type == "PLA"`; the Basic/Matte/Silk distinction is carried only in `tray_info_idx` (`GFA00` = Basic, `GFA01` = Matte, `GFA06` = Silk, …), which the 3MF's `slice_info.config` also records per filament. Three places dropped it: the Virtual-Printer queue built each force override as `{slot_id, type, color, force_color_match}` without the parsed `tray_info_idx`; the scheduler's eligibility check (`_get_missing_force_color_slots`) compared loaded trays on `(type, colour)` only — so `(PLA, #FFFFFF)` matched Basic, Matte and Silk indiscriminately and all three printers looked eligible; and the AMS slot mapper cleared `tray_info_idx` when applying the override, so even on the correct printer it could pick a different-variant tray of the same colour. **Fix.** The force override now carries the 3MF's `tray_info_idx`; a slot counts as satisfied only when a loaded tray matches type **and** colour **and** the variant (identical `tray_info_idx`, *or* either side lacks one); and the slot mapper now keeps the variant for force-colour overrides so it pins the matching tray. A blank idx on either side (custom/third-party spools report none, and older 3MFs carry none) falls back to the historical type+colour behaviour, so those setups are unaffected, and a manual filament *swap* (a preference override) still clears the idx so it matches the swapped-in spool rather than the old one. A job sliced for GFA01 now goes only to a printer with GFA01 loaded, and lands on that printer's GFA01 tray. The printer-card queue-compatibility hint (which printers show a pending job as runnable) now applies the same variant rule. Covered by scheduler tests (Matte requirement rejects Basic/Silk, accepts Matte, blank loaded idx falls back, requirement without an idx unchanged; the mapper pins the GFA01 tray over a same-colour GFA00 on both the 3MF and no-3MF paths; a preference swap still matches by colour), a Virtual-Printer test asserting the override carries `tray_info_idx`, and frontend tests for the variant-aware queue hint (rejects other variants, accepts the match, blank-idx and no-variant-data fall back).
### Security
- **Patched two build-time frontend dependencies flagged by `npm audit` (GHSA-r28c-9q8g-f849, GHSA-mh99-v99m-4gvg, GHSA-rgw5-rvv9-x895)**`postcss` 8.5.15 → 8.5.23 fixes a path traversal in its source-map auto-loader (`sourceMappingURL`) that could disclose arbitrary `.map` files, and `brace-expansion` (pulled in transitively by `eslint` via `minimatch`) is bumped through the existing `overrides` block (`^5.0.7``^5.0.9`) for a denial-of-service via unbounded expansion. The first `brace-expansion` advisory was answered in 5.0.8 by capping the length of the combined result, but that cap covered only the accumulator the results are merged into and not the two intermediate arrays that feed it — so a small brace pattern could still exhaust the heap, fatally and beyond the reach of a `try`/`catch`, or stall the event loop for minutes. 5.0.9 bounds both arrays as they are built. Both packages are build/lint-time tooling only — neither is part of the shipped app, so no running Bambuddy install was exposed. `postcss` moved within its existing range; `brace-expansion` needed the pin because `npm audit fix` can't lift `eslint` to the patched transitive on its own.
- **Pinned `react-router` to its most-patched 7.x (7.18.1) and documented the one remaining, unreachable advisory (GHSA-qwww-vcr4-c8h2)** — Staying current on the 7.x line matters: 7.18.1 clears 14 advisories that older 7.x releases carry, several reachable in a browser SPA (open-redirect XSS in `<Link>`/`useNavigate`, route-matching DoS). The single advisory that still flags 7.18.1 — a CSRF bypass — applies only to React Router's **RSC mode**, which requires the server runtime (`@react-router/server`, not installed); Bambuddy is a Vite SPA using `BrowserRouter`, so the vulnerable path is unreachable. There is no non-major fix (the patch landed only in the 8.3.0 major, and `react-router-dom` has no 8.x — adopting it would mean migrating every import to `react-router` plus a React peer bump), so `react-router`/`react-router-dom` are pinned to 7.18.1 and the finding is carried as a documented, fail-closed exception in the CI audit gate: a *different* react-router advisory still fails CI, and the exemption is dropped automatically the moment a non-major fix ships. `npm audit fix --force` is deliberately avoided — its suggested "fix" is a downgrade to 7.11.0, which reintroduces those 14 advisories.
- **Patched the build-time frontend dependencies flagged by `npm audit` (GHSA-r28c-9q8g-f849, GHSA-mh99-v99m-4gvg, GHSA-rgw5-rvv9-x895, GHSA-5p4m-2wfm-xmqj, GHSA-2v37-7h3g-55p8)**`postcss` 8.5.15 → 8.5.23 fixes a path traversal in its source-map auto-loader (`sourceMappingURL`) that could disclose arbitrary `.map` files, and `brace-expansion` (pulled in transitively by `eslint` via `minimatch`) is bumped through the existing `overrides` block (`^5.0.7``^5.0.9`) for a denial-of-service via unbounded expansion. The first `brace-expansion` advisory was answered in 5.0.8 by capping the length of the combined result, but that cap covered only the accumulator the results are merged into and not the two intermediate arrays that feed it — so a small brace pattern could still exhaust the heap, fatally and beyond the reach of a `try`/`catch`, or stall the event loop for minutes. 5.0.9 bounds both arrays as they are built. Two more followed: `js-yaml` (quadratic CPU consumption resolving `!!omap`) and `nanoid` (a custom generator loops forever when asked for size zero), reached via `eslint``@eslint/eslintrc` and `postcss` respectively. `nanoid` took a patch inside 3.x (`^3.3.18`), but the `js-yaml` fix was deliberately not backported to 3.x or 4.x, so the override moves to `^5.2.3` — a major, which is why it was checked rather than assumed: `@eslint/eslintrc` calls exactly one js-yaml API, `load()`, on the legacy `.eslintrc.yml` path this repo does not use (it is on flat config), and `eslint`, `vite build` and the full 2861-test frontend suite all pass on it. Every package here is build/lint-time tooling — none is part of the shipped app, so no running Bambuddy install was exposed. `postcss` moved within its existing range; the others needed pins because `npm audit fix` cannot lift a transitive of `eslint`/`postcss` on its own.
- **Pinned `react-router` to its most-patched 7.x — now 7.18.2, which clears the last outstanding advisory (GHSA-qwww-vcr4-c8h2)** — Staying current on the 7.x line matters: it clears 14 advisories that older 7.x releases carry, several reachable in a browser SPA (open-redirect XSS in `<Link>`/`useNavigate`, route-matching DoS). One further advisory — a CSRF bypass — flagged 7.18.1 but applied only to React Router's **RSC mode**, which requires the server runtime (`@react-router/server`, not installed); Bambuddy is a Vite SPA using `BrowserRouter`, so the vulnerable path was never reachable here. At the time the only fix was the 8.3.0 major (`react-router-dom` has no 8.x — adopting it would mean migrating every import to `react-router` plus a React peer bump), so the finding was carried as a documented, fail-closed exception in the CI audit gate. Upstream has since backported the patch to **7.18.2**, so `react-router`/`react-router-dom` move to it and the exception is gone, leaving the gate's allowlist empty. That happened on its own: an exemption holds only while the offered fix is semver-major, so the gate failed the moment the backport shipped rather than quietly carrying a now-fixable advisory. `npm audit fix --force` remains deliberately avoided — its suggested "fix" is a downgrade to 7.11.0, which reintroduces those 14 advisories.
- **`dompurify` 3.4.12 → 3.4.13 (GHSA-55q2-fjhq-7xh7)** — Removing a hook mid-sanitisation could leave a detached subtree executable in DOMPurify's `IN_PLACE` mode, an XSS. Unlike the build-time bumps above, DOMPurify does ship in the app — it sanitises MakerWorld-supplied design summaries and project notes before they are rendered — so it is worth being explicit that this particular path was not reachable: Bambuddy registers no DOMPurify hooks and never uses `IN_PLACE`, calling only the string-returning `sanitize()` with an explicit tag and attribute allowlist. The patched release is inside the existing `^3.4.10` range, so this is a lockfile move rather than a new pin.
- **Raised the `cryptography`, `pyOpenSSL` and `aiohttp` floors so a resolve cannot pick a vulnerable-but-satisfying version (PYSEC-2026-3552, PYSEC-2026-3545/3546/3547)**`cryptography>=48.0.1``>=50.0.0` and `aiohttp>=3.14.0``>=3.14.3`. Neither had gone stale in CI, which resolves from scratch and so was already installing the fixed releases; the floors matter for the case CI does not cover, an existing environment where `>=` is already satisfied and `pip install -r` therefore upgrades nothing. `pyOpenSSL` moves `>=26.3.0``>=26.4.0` for a subtler reason worth writing down: every pyOpenSSL release caps `cryptography` to a narrow window (26.3.0 permits `<50`, 26.4.0 permits `<51`), so a stale pyOpenSSL silently holds `cryptography` below its own fix line and pip cannot climb past the cap even when asked for it directly. The two floors have to move together, which the comment in `requirements.txt` now says. Bambuddy's `cryptography` surface is indirect throughout — asyncssh, pyOpenSSL, py-vapid, http_ece, pywebpush — and the 49 → 50 major was verified rather than assumed: the X.509/PKCS#7/EC/RSA entry points and pyftpdlib's `TLS_FTPHandler` all import, `ruff` is clean, and the full backend suite passes unchanged at 9204 passed / 1 skipped.
- **Security hardening (security-issue #9)**

View file

@ -33,7 +33,7 @@
"react-dom": "^19.2.0",
"react-i18next": "^16.3.5",
"react-markdown": "^9.1.0",
"react-router-dom": "7.18.1",
"react-router-dom": "7.18.2",
"react-simple-keyboard": "^3.8.164",
"recharts": "^3.5.1",
"remark-gfm": "^4.0.1",
@ -3778,9 +3778,9 @@
"peer": true
},
"node_modules/dompurify": {
"version": "3.4.12",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.12.tgz",
"integrity": "sha512-zQvGet8Z2sWbQhCmfFz/T5QWH2oBmjnqK3qvOjaqaNLrLEF912WamU+ohnTp0TCep/MFVHpdJuCZEdFOdTnEFg==",
"version": "3.4.13",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.13.tgz",
"integrity": "sha512-2vmYIoqjze2d+kakP8S/nS5shfsl587kzwEjcGlTdiksUVgFHnFCsLYDVj/JNqJVOQZGSYBTmuycv0PodwmnMQ==",
"optionalDependencies": {
"@types/trusted-types": "^2.0.7"
}
@ -4913,9 +4913,9 @@
"license": "MIT"
},
"node_modules/js-yaml": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz",
"integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==",
"version": "5.2.3",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.3.tgz",
"integrity": "sha512-n+mUVyUX5bVv7G/G2zyIHOhdxfuU1dY2NOFzTQUWiMUbFss8b57NFlgCCaggU78wSw5KVS9cllzeLyzyR+n5nw==",
"dev": true,
"funding": [
{
@ -4927,12 +4927,11 @@
"url": "https://github.com/sponsors/nodeca"
}
],
"license": "MIT",
"dependencies": {
"argparse": "^2.0.1"
},
"bin": {
"js-yaml": "bin/js-yaml.js"
"js-yaml": "bin/js-yaml.mjs"
}
},
"node_modules/jsdom": {
@ -6481,9 +6480,9 @@
}
},
"node_modules/nanoid": {
"version": "3.3.16",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz",
"integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==",
"version": "3.3.18",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz",
"integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==",
"dev": true,
"funding": [
{
@ -7135,9 +7134,9 @@
}
},
"node_modules/react-router": {
"version": "7.18.1",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.1.tgz",
"integrity": "sha512-GDLgg3i3uM0aeJO3Fm+TCS+sDQ7gu12T6x0qdTEzcwqEfleci7JwugVNIF3U//0FWKnJT7ptG+20B2jfDqnZAg==",
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz",
"integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==",
"dependencies": {
"cookie": "^1.0.1",
"set-cookie-parser": "^2.6.0"
@ -7156,11 +7155,11 @@
}
},
"node_modules/react-router-dom": {
"version": "7.18.1",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.1.tgz",
"integrity": "sha512-KaZh+X/6UtEp28x51AUYZDMg9NGoz2ja3dNHa+ta/tk40vCzKhQ/RypCWBMLbmDr6//E24Vv5uPsrqXFozdkAg==",
"version": "7.18.2",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz",
"integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==",
"dependencies": {
"react-router": "7.18.1"
"react-router": "7.18.2"
},
"engines": {
"node": ">=20.0.0"

View file

@ -40,7 +40,7 @@
"react-dom": "^19.2.0",
"react-i18next": "^16.3.5",
"react-markdown": "^9.1.0",
"react-router-dom": "7.18.1",
"react-router-dom": "7.18.2",
"react-simple-keyboard": "^3.8.164",
"recharts": "^3.5.1",
"remark-gfm": "^4.0.1",
@ -49,8 +49,9 @@
"overrides": {
"minimatch": "^10.2.1",
"brace-expansion": "^5.0.9",
"js-yaml": "^4.3.0",
"react-router": "7.18.1"
"js-yaml": "^5.2.3",
"nanoid": "^3.3.18",
"react-router": "7.18.2"
},
"devDependencies": {
"@eslint/js": "^9.39.1",

View file

@ -40,14 +40,17 @@ aioftp>=0.22.0
# Virtual Printer (emulates Bambu printer for slicer uploads)
pyftpdlib>=2.0.0
# 46.x line has GHSA-537c-gmf6-5ccf; 48.0.1 is the fix release. Upstream's
# X.509 / PKCS#7 surface is in our trust path via asyncssh, pyOpenSSL,
# py-vapid, http_ece, pywebpush.
cryptography>=48.0.1
# Transitive of asyncssh / pywebpush. pyopenssl<26.3.0 caps `cryptography<47`
# so without this floor the resolver either downgrades cryptography below
# the GHSA-537c-gmf6-5ccf fix line or installs an inconsistent pair.
pyopenssl>=26.3.0
# Upstream's X.509 / PKCS#7 surface is in our trust path via asyncssh,
# pyOpenSSL, py-vapid, http_ece, pywebpush, so this floor tracks the current
# fix release: 46.x had GHSA-537c-gmf6-5ccf (fixed in 48.0.1), and 49.0.0 has
# PYSEC-2026-3552 (fixed in 50.0.0).
cryptography>=50.0.0
# Transitive of asyncssh / pywebpush, and the gate on the line above: each
# pyOpenSSL release caps `cryptography` to a narrow window (26.3.0 allows
# <50, 26.4.0 allows <51), so a stale pyOpenSSL silently pins cryptography
# below its fix line -- pip cannot upgrade past the cap even when asked.
# Raise this floor in the same commit as any cryptography floor.
pyopenssl>=26.4.0
# SpoolBuddy remote SSH updates (pure-Python SSH client; avoids the
# OpenSSH `ssh` binary which calls getpwuid() and fails in Docker when
@ -143,13 +146,13 @@ urllib3>=2.7.0
# resolver from picking them.
starlette>=1.3.1
# Transitive of pywebpush (unpinned `aiohttp` requirement). aiohttp 3.13.5
# has CVE-2026-34993 and CVE-2026-47265, both fixed in 3.14.0. pywebpush
# doesn't declare an upper bound either way, so without this pin the
# resolver keeps installing the vulnerable 3.13.x line. Our direct usage
# in services/external_camera.py (ClientSession, ClientTimeout, ClientError,
# iter_chunked) is unaffected by 3.14.0.
aiohttp>=3.14.0
# Transitive of pywebpush (unpinned `aiohttp` requirement). pywebpush declares
# no bound in either direction, so without this floor the resolver happily
# installs a vulnerable line: 3.13.5 has CVE-2026-34993 and CVE-2026-47265
# (fixed in 3.14.0), and 3.14.1 has PYSEC-2026-3545/3546/3547 (3.14.3 clears
# all three). Our direct usage in services/external_camera.py (ClientSession,
# ClientTimeout, ClientError, iter_chunked) is unaffected by either bump.
aiohttp>=3.14.3
# Plate Detection (optional - enables build plate empty detection)
opencv-python-headless>=4.8.0

File diff suppressed because one or more lines are too long

View file

@ -26,7 +26,7 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-0YeqkzMt.js"></script>
<script type="module" crossorigin src="/assets/index-B1ugBOm2.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-DZYWm6I1.css">
</head>
<body>