diff --git a/.release-notes.md b/.release-notes.md new file mode 100644 index 00000000..8dec9a5f --- /dev/null +++ b/.release-notes.md @@ -0,0 +1,135 @@ +# SnapOtter v2.2.0 + +Most of 2.2 is hardening. A non-admin holding a delegated `users:manage` role could take over an instance, and every tool endpoint turned out to be missing its permission check. Upgrade if you run more than one user. + +The rest is the long tail of self-hosting: downloads that started and never finished, presets that broke the moment you added a second file, containers that crash-looped while Postgres was still coming up. Two new tools landed too. Docs and the website are now translated into 21 languages, and the interface finally clears WCAG AA contrast. + +> [!IMPORTANT] +> If you granted `users:manage` to a non-admin through a custom role, that role could take over administrator accounts on 2.0.0 through 2.1.0. Details in Security. + +### Security + +- **Privilege escalation through a delegated `users:manage` role (GHSA-9xgh-95qh-2x7h).** A non-admin holding `users:manage` through a custom role could reset the password of, or delete, a higher-privileged account, a built-in administrator included, and take over the instance. Password reset and account deletion now verify the caller's authority over the target, so a delegated role cannot reach above its own level. Affected: 2.0.0 through 2.1.0. Reported by 李春来 (Chunlai Li). (#616) +- **Every tool endpoint is now gated.** Tool access was enforced route by route, so all 45 hand-written routes had to remember the same call and none of them did. A role without `tools:use` could still run image-to-pdf, erase-object, upscale, sign-pdf and the rest. The check now lives in one preHandler keyed off the matched route, so it covers sub-paths and any route added later. (#646) +- **Rate limiting was bypassable on Docker installs.** The image shipped `TRUST_PROXY=true`, so the client IP came from a request header and a forged `X-Forwarded-For` walked past the login limiter. The default is now a private-network trust list. (#649) +- **Settings writes are checked per setting**, not once at the door, and config import is now transactional so a rejected key cannot leave half a config applied. (#618) +- **Library filenames cannot escape their storage root.** A crafted stored name, which a malicious 1.x SQLite import copies verbatim, could read or delete files outside the files directory. Only reachable after an operator imports an attacker-supplied 1.x SQLite database, which is the one path that lets a stored name be chosen. Reported by @Alpastx. (GHSA-55w2-8cqf-w969, #600) +- **Job cancellation enforces ownership**, so an authenticated user can no longer cancel another user's job by its id. Reported by @Alpastx. (GHSA-wqxf-gj2p-689x, #599) +- **A re-audit of the whole 2.0 tree caught what was still open**: a captured SAML assertion could be replayed, an MFA challenge survived any number of wrong codes, the per-request API-key lookup was a full table scan, `MAX_WORKSPACE_SIZE_GB` was dead config, and the SVG sanitizer let through unquoted `javascript:` hrefs. (#620) +- **Nine disclosed CVEs patched**: fast-uri, svgo, sharp and tar transitively (#619), plus Pillow 12.3.0 (#517). RAW decoding on arm64 now builds LibRaw 0.22.2 from source instead of linking an unpatched system copy. (#649) + +### New Features + +- **Rounded Crop**: rounded-square and iOS-style squircle masks, with a corner-radius control. Built for favicons and app icons. (#602) +- **Remove GIF Background**: strip the background from an animated GIF, WebP or APNG frame by frame and get a transparent animation back. (#502) +- **Object Eraser lasso and High Quality mode**: drag a freeform loop instead of painting every pixel, and optionally install a diffusion inpainting bundle for cleaner large-area fills. (#503, #566) +- **Save as new, or overwrite.** Editing a library file used to silently supersede the original. You now choose per edit, and the default keeps it. (#564, #577) +- **Aspect-ratio presets in Resize.** Pick 1:1, 4:3 or 16:9 and the two dimensions stay locked, without upscaling. (#530) +- **Two-tier OCR**: a fast tier is baked into the image and runs offline with no download, and a more accurate runtime installs on demand. (#519) +- **Type anywhere to search.** Start typing on the dashboard or the homepage and it lands in the search box. (#644) +- **Documentation and website in 21 languages**: the docs site, the landing pages and the API reference are all translated now, joining the app interface. A new low-resource deployment guide covers Raspberry Pi and 2 GB machines. (#548) +- **Clearer tool names**: 18 ambiguous names now self-qualify, so "Compress" became "Compress Image". Tool ids and routes are unchanged, so nothing bookmarked breaks. (#520) + +### Improvements + +- **WCAG AA contrast across the interface**: the palette was retuned so vivid orange stays on fills while text and labels use accessible ink tokens, and all 57 focus indicators now clear the 3:1 non-text bar. (#567, #574) +- **Upscale and background removal stop looking frozen**: the progress bar advances during inference instead of parking at 30%, and both tools now warn that a CPU-only host is much slower. (#605, #608) +- **Timeout messages name the real cause** and account for CPU-only hosts instead of blaming the upload. (#596) +- **SnapOtter waits for Postgres and Redis at startup** instead of crash-looping, in Compose and the all-in-one image alike. A Proxmox LXC install was restarting 76 times before this. (#537, #595) +- **Downloads survive a reverse proxy**: SnapOtter asks nginx and compatible proxies not to buffer file responses, which is the usual reason a self-hosted download starts and never finishes. (#604, #607) +- **Compress PDF lands near the target size**, and says so honestly when a target is out of reach. (#522) +- **Mobile tool controls stay reachable** now that the app shells size to the dynamic viewport. (#559) +- **Convert Audio exposes a sample-rate setting.** (#561) +- **Admins can relax the minimum password length**, down to 1 for a trusted LAN instance. (#543) +- **MFA is self-service**, the policy lockout is closed, and OIDC or SAML logins get a real MFA challenge instead of a hard block. (#531, #536) +- **A bare `Error: Error` now names its cause.** Sharp encode failures, AI sidecar exits, non-JSON document sidecar output, and background-removal failures all used to arrive with the reason scrubbed off. (#532, #534, #535, #538, #612) +- **The help dialog is translated.** Its shortcut labels and getting-started text were hardcoded English while finished translations sat unused in all 21 locale files. (#647) + +### Bug Fixes + +- **iPhone HEIC files were rejected** as unreadable before they reached the decoder that handles them. (#631) +- **Conversion presets failed on a second file**: jpg-to-pdf and its image-to-pdf siblings, plus pdf-to-png, pdf-to-jpg and pdf-to-tiff, all returned `Tool not found` once you uploaded two files. (#633, #643) +- **PDF conversion presets lost their download button.** (#629) +- **An unlimited processing timeout was not honored**, and stalled progress streams now recover instead of leaving the interface waiting. (#638) +- **Downloads hung instead of failing** when a stored file turned out shorter than its recorded length. (#617) +- **PDF page tools failed on short and encrypted files**: Remove Pages defaulted to a page range no document under six pages has, and password-protected PDFs failed cryptically inside the worker. (#594) +- **PDF to Text silently returned an empty file** for scanned PDFs. It now points you at OCR and serves text as UTF-8. (#603) +- **PDF to Word dropped colored text blocks** and split them across the page. (#500) +- **Object Eraser left ghost remnants** and blurred small objects in high-resolution images. (#501) +- **Stabilize Video wrote unplayable output** without faststart. (#593) +- **Image editor repairs**: rotate, flip, resize, levels, curves, filters and layer lock. (#597) +- **Sign PDF showed a blank canvas** instead of reporting why a PDF failed to load. (#545) +- **The file library recorded 0x0 dimensions** and skipped previews for HEIC, RAW and PSD uploads. (#636, #637) +- **Installing more than one AI bundle** left the shared virtualenv multi-versioned and quietly broke three tools. (#649) +- **Converting to JXL at quality 1 through 4 returned a 500**, and a missing ffmpeg was reported to you as a corrupt upload. (#649) +- **A transient Postgres outage stranded finished jobs**, leaving output on disk with no row pointing at it. A reconciler now adopts that work instead of dropping it. (#649) +- **A Redis endpoint that changed address wedged every consumer** while health checks still answered 200. (#649) +- **Website and docs fixes**: localized links no longer drop `#` fragments or lowercase `zh-CN`, the docs nav stays inside the viewport on tablets, and neither site calls the GitHub API from your browser any more. (#516, #562, #570, #560) + +### Upgrade Notes + +Nothing to migrate, but two shipped defaults changed: + +- **`TRUST_PROXY` now defaults to `loopback,linklocal,uniquelocal`** instead of trusting every peer. Docker bridge and Compose networks sit inside that range, so most setups need no change. If your reverse proxy reaches SnapOtter from a public address, set `TRUST_PROXY` explicitly, or rate limiting and audit logs will attribute every request to the proxy. +- **`MAX_AI_JOBS_PER_USER` defaults to 5** in-flight single-file AI jobs per user. Batch and pipeline AI runs stay uncapped. + +New optional knobs: `DB_STARTUP_TIMEOUT_MS`, `SUBPROCESS_MEMORY_LIMIT_MB` (off by default) and `GIF_BG_MAX_FRAMES`. + +### Acknowledgements + +A good part of this release started as someone else's bug report. + +Code and contributions: + +- **@mvanhorn** ❤️: Rewrote the remove-background timeout failure into a message that says what to do about it, instead of a bare timeout. (#518, #494) +- **@EuanTop** ❤️: Restored the download action on PDF conversion preset pages by making the synchronous route return the standard tool-result contract. (#629, #623) +- **@harshjainnn** ❤️: Diagnosed the download that starts and never finishes, and proposed the socket-reset direction the fix was built on. (#617, #590) + +Security disclosures: + +- **李春来 (Chunlai Li)** ❤️ ([@laijunyue](https://github.com/laijunyue)): Privately disclosed the privilege escalation through a delegated `users:manage` role, with a full source-to-sink analysis and a working exploit chain. (GHSA-9xgh-95qh-2x7h, #616) +- **@Alpastx** ❤️ (Alpesh Bhagwatkar): Disclosed an authenticated IDOR on job cancellation, and a path traversal in library stored filenames reachable after a malicious 1.x SQLite import, with a working proof of concept for each. (#599, #600) + +Bug reports: + +- **@riz467** ❤️: Real iPhone HEIC files rejected at validation, with the root cause worked out in the report. (#622) +- **@coupej** ❤️: PDF preset pages hanging forever, and jpg-to-pdf failing as soon as a second file was added. Two distinct bugs, correctly separated. (#623, #627) +- **@linuxuser1** ❤️: `PROCESSING_TIMEOUT_S=0` documented as unlimited but capped at five minutes. (#630) +- **@bezibaerchen** ❤️: Convert Audio hid the sample-rate setting its own description promised. (#558) +- **@hell-toupee** ❤️: The accurate OCR bundle failing to install with a `libpaddle` error. (#505) +- **@Michael1260** ❤️ and **@And-CSH** ❤️: Confirmed that OCR install failure independently and established that manual extraction works while the in-app installer does not, which pinned the bug to the installer. (#505) +- **@TomErnst1972** ❤️: Enabling the MFA-required policy locking an admin out of their own instance. (#515) +- **@thokich** ❤️: Object erasing producing blurry, unusable fills, which drove both the HD inpainting rewrite and the optional high-quality bundle. (#141) +- **@Hennie-git** ❤️: A Proxmox LXC install restarting 76 times against a Postgres that was not yet accepting connections. (community-scripts/ProxmoxVE#15796) +- **MickLesk** ❤️ (Proxmox VE community-scripts): Triaged that report in real time and produced the diagnosis the startup fix was built on. (#537) + +Feature requests and feedback: + +- **@LECOQQ** ❤️: Asked to relax password complexity on a home-server install. (#136) +- **@killervette42** ❤️: Asked for an Unraid Community App, now published to the Unraid CA store. (#96) +- **@alienatedsec** ❤️: Pointed out the GPU-falls-back-to-CPU fix was buried in a closed issue and undiscoverable, which is why it is in the deployment docs now. (#490, #587) +- **@neilp316** ❤️: Confirmed the Blackwell GPU failure on an RTX 5060 and mapped a working CUDA 12.8 upgrade path. (#120) +- **@Roiki11** ❤️: Argued for shared storage with path references over HTTP file transfer for off-box AI compute. (#189) + +Thank you as well to the community members who reported these over Discord and email, whose names we did not record: the squircle crop request (#602), the image-to-PDF download that started and never finished (#604, #607), the Delete Pages tool getting stuck (#594), Arabic text missing from pdf-to-text (#603), and the diagnosis behind the ONNX GPU fallback (#490). + +## Upgrade + +```bash +docker compose pull && docker compose up -d +``` + +Or pull the app image directly: + +```bash +docker pull snapotter/snapotter:2.2.0 +``` + +**Full Changelog**: https://github.com/snapotter-hq/SnapOtter/compare/v2.1.0...v2.2.0 + +

+ Docs  ·  + Live demo  ·  + Website  ·  + Discord +