Merge branch 'main' into 1.2.5.3

This commit is contained in:
MartinNYHC
2026-08-15 15:39:03 +02:00
committed by GitHub
3 changed files with 8769 additions and 2 deletions
+6
View File
@@ -141,6 +141,12 @@ All notable changes to Bambuddy will be documented in this file.
- **Ukrainian was listed above Russian in the language picker** — Locales appear in the picker in the order they were added, but `uk` had been inserted ahead of `ru` in the import block, the resources map and the list the picker renders. Moved to the end of all three; the alphabetically sorted supported-language list already had it in the right place. Frontend-only, with no behaviour change beyond the row order.
- **Setting Spoolman options over the API with a true/false value returned a server error** — `PUT /settings/spoolman` accepts a free-form body, and sending the natural JSON form for a switch — `{"spoolman_enabled": true}` rather than `{"spoolman_enabled": "true"}` — came back as a 500 with nothing useful in it. The shipped UI always sends strings, so this only affected people driving Bambuddy from a script or a Home Assistant `rest_command`, which is exactly where a real boolean is the obvious thing to send. **Root cause.** Settings are stored as text and every reader compares them as text, but the submitted value went in untouched. Deciding whether Spoolman had just been switched on called a string operation on it, which a boolean does not have; and the raw boolean was also written straight to a text column, which SQLite quietly turns into 1/0 while PostgreSQL refuses it outright — so the stored result depended on which database the install used. **Fix.** Boolean-ish settings are now converted to a canonical `true`/`false` on the way in, accepting real booleans, `1`/`0`, and the usual spellings (`True`, `yes`, `on`) case-insensitively, since this is a documented API that scripts talk to. A value with no sensible reading, such as `"banana"`, now returns a 400 naming the field instead of being stored as-is and silently treated as off. Two details are preserved deliberately: a blank value still means "use the default" for the two options that default to on, and reading a stored value stays as strict as it has always been elsewhere in the codebase, so no existing row changes meaning. Text options are checked too, so a JSON object can no longer be stored as its own printed form. One incidental improvement: a value stored as `True` by an earlier API call showed as off in the UI, which compares case-sensitively, while the backend treated it as on — canonical storage removes that disagreement. Covered by tests across the accepted spellings, the rejected values, the blank-means-default behaviour, and the read path.
### Security
- **Patched the build-time frontend dependencies flagged by `npm audit` (GHSA-rgw5-rvv9-x895, GHSA-5p4m-2wfm-xmqj, GHSA-2v37-7h3g-55p8)** — Three transitive dependencies of `eslint` and `postcss`, bumped through the existing `overrides` block. `brace-expansion` goes `^5.0.8` → `^5.0.9` for a denial-of-service via unbounded expansion: the first 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. `js-yaml` goes `^4.3.0` → `^5.2.3` for quadratic CPU consumption while resolving `!!omap`; that fix was deliberately not backported to 3.x or 4.x, which is what makes this a major, so it was checked rather than assumed — `@eslint/eslintrc` calls exactly one js-yaml API, `load()`, and only on the legacy `.eslintrc.yml` path this repo does not use, and `eslint`, `vite build` and the full frontend test suite all pass on it. `nanoid` is newly pinned at `^3.3.18`, where a custom generator asked for size zero loops forever; the patch stays inside 3.x. All three are build and lint-time tooling — none is part of the shipped app, so no running Bambuddy install was exposed. The pins are needed because `npm audit fix` cannot lift a transitive of `eslint` or `postcss` on its own.
- **Took the backported React Router fix and retired the audit exception (GHSA-qwww-vcr4-c8h2)** — `react-router`/`react-router-dom` move from 7.18.1 to **7.18.2**. The RSC-mode CSRF advisory noted in 1.2.5.1 was carried as a documented, fail-closed exception in the CI audit gate, because at the time its only fix was the 8.3.0 major and `react-router-dom` has no 8.x — adopting it would have meant migrating every import to `react-router` plus a React peer bump. Upstream has since backported the patch to the 7.x line, so the pin moves and the exception is gone, leaving the gate's allowlist empty. That happened on its own rather than by anyone remembering to check: an entry only holds while the offered fix is semver-major, so the gate failed the moment the backport shipped instead of quietly carrying a now-fixable advisory. The finding was never reachable here in any case — Bambuddy is a Vite SPA using `BrowserRouter` with no RSC runtime installed. `npm audit fix --force` remains deliberately avoided; its suggested "fix" is a downgrade to 7.11.0, which reintroduces the 14 advisories older 7.x releases carry.
- **`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.
## [1.2.5.1] - 2026-07-27
### Added
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -26,8 +26,8 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-DwuX91sA.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-1Ya6fAmN.css">
<script type="module" crossorigin src="/assets/index-DjrhopFm.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-C_6BSgrK.css">
</head>
<body>
<div id="root"></div>