mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
7cd514dd8cdee28b9f600722f43ba63bcdbe2928
239
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7cd514dd8c |
fix(ai): detect a paddle-only GPU so OCR uses PaddleOCR-GPU not Tesseract (#439)
gpu_available() probed torch, then ONNX Runtime, then nvidia-smi, but never paddle. The OCR bundle ships paddlepaddle-gpu with no torch or ONNX, so on an OCR-only GPU host every probe missed the GPU: nvidia-smi saw it but returned False by design, and OCR silently fell back to Tesseract (CPU, lower quality) with no signal why. Add a paddle probe as the last resort in gpu_available(). It runs only after nvidia-smi confirms a GPU is physically present, and in an isolated subprocess, because importing paddlepaddle-gpu on a GPU-less host segfaults and would wedge the shared AI dispatcher. It returns True only when paddle reports both a CUDA build and a visible device, signalling the result through the exit code so paddle's own import chatter on stdout cannot corrupt the reading. CPU-only and torch/ONNX GPU hosts are unaffected: the probe never runs on the former (nvidia-smi finds nothing) and is never reached on the latter (the torch step already returns True first). Claude-Session: https://claude.ai/code/session_01NfaRxjek8ex5nawvx3mVMf |
||
|
|
cf884b52cd |
fix: offline CodeFormer face-enhance (ship RealESRGAN_x2plus in upscale-enhance bundle) (#433)
* fix: ship RealESRGAN_x2plus.pth in the upscale-enhance bundle for offline CodeFormer codeformer-pip 0.0.4 downloads RealESRGAN_x2plus.pth at import of codeformer.app, unconditionally, even though enhance_faces calls inference_app with background_enhance=False and never uses the background upsampler. The weight was not bundled, so explicit CodeFormer face-enhance (enhance-faces model=codeformer) failed in strict offline mode (SNAPOTTER_ALLOW_MODEL_DOWNLOAD=0) on a host that had never cached it -- the guard raised before the import could complete. Add RealESRGAN_x2plus.pth to the upscale-enhance bundle manifest (only that bundle uses codeformer-pip; photo-restoration uses the CodeFormer ONNX path) and link it in prepare_codeformer_weights alongside the other three weights, replacing the download-or-error guard. Once the bundle ships it, the import resolves offline and strict mode works. Archive SHA256s updated in a follow-up once the bundle is rebuilt. Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7 * fix: require face-detection bundle for enhance-faces + point manifest at the x2plus archives enhance-faces runs MediaPipe face detection (blaze_face_short_range.tflite) before CodeFormer/GFPGAN. That model ships in the face-detection bundle, not the tool's primary upscale-enhance bundle, so a standalone upscale-enhance install failed face detection (offline: hard error; online: a surprise download) before reaching the codeformer path. Declare the dependency in TOOL_EXTRA_BUNDLES like passport-photo does. Update the upscale-enhance archive SHA256/sizes to the rebuilt bundles that include RealESRGAN_x2plus.pth (amd64-gpu + arm64-cpu), verified to install and run enhance-faces model=codeformer in strict offline mode with zero downloads. Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7 |
||
|
|
6e3a14ec6b |
fix: remove automatic third-party egress of user data + optional strict offline mode (OSM tiles, Scalar fonts, editor fonts, AI model downloads) (#422)
* fix: remove all automatic third-party egress (OSM tiles, Scalar fonts, editor Google Fonts, AI model download fallbacks) Phone-home audit follow-up. The product no longer makes any automatic third-party request; user-initiated click-outs stay, and production now fails closed on missing AI models. 1. GPS leak via OSM tiles: the strip-metadata panel auto-loaded tile.openstreetmap.org tiles encoding the photo's GPS position. The Leaflet mini-map is gone; coordinates render as text plus an explicit View on map link (openstreetmap.org, opens on click only). Removed tile.openstreetmap.org from the CSP img-src, dropped the leaflet dependency, added the viewOnMap i18n key to all 21 locales. 2. Scalar docs fonts: /api/docs loaded Inter and JetBrains Mono from fonts.scalar.com. Scalar now renders with withDefaultFonts: false and both --scalar-font and --scalar-font-code pinned to system stacks; fonts.scalar.com removed from the docs CSP font-src. Verified by injecting GET /api/docs/: config carries withDefaultFonts false and the served page has no fonts.scalar.com reference. 3. Editor Google Fonts: the editor font picker built fonts.googleapis.com stylesheet URLs for 25 web fonts the served CSP already blocked. The remote loading path is deleted; the picker now offers system fonts only, with a SELF_HOSTED_FONTS seam (FontFace API, same origin) for bundling fonts later. Unknown families saved in old documents fall back to the browser default. 4. Python sidecar fails closed on model downloads: new packages/ai/python/offline_guard.py gates every runtime download fallback (inpaint, outpaint, restore, noise_removal, detect_faces, enhance_faces, face_landmarks, red_eye_removal, remove_bg, ocr, transcribe, upscale) behind SNAPOTTER_ALLOW_MODEL_DOWNLOAD=1 with an actionable error. Bundled models keep working untouched. 5. OCR and transcription library-internal downloads: unbundled PaddleOCR language and detection fallbacks now raise the guard error naming the language instead of resolving models over the network; faster-whisper gets local_files_only when downloads are off. 6. GFPGAN and CodeFormer cwd-relative weights: facexlib and codeformer-pip resolve helper weights relative to the process cwd and fetch them from GitHub when absent. They are now symlinked from the installed bundle files under MODELS_PATH/gfpgan/facelib before the libraries load, failing closed when unresolvable. Defense in depth: HF_HUB_OFFLINE=1 and TRANSFORMERS_OFFLINE=1 are set in the runtime image and in the sidecar spawn env; install_feature.py lifts them for user-initiated bundle installs and restores them afterwards (it can run in-process inside the dispatcher). SNAPOTTER_ALLOW_MODEL_DOWNLOAD is documented in .env.example, default off. Validation: typecheck 9/9 workspaces, Biome clean on touched files, 5178 unit tests pass, py_compile on all touched scripts, guard behavior exercised in both dispatcher exec and per-request import modes, zero remaining runtime references to the three hosts. Docker build and live AI inference need post-merge verification on the GPU host. Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7 * fix: allow AI model downloads by default, make strict offline mode opt-in Product call: ease of use first. The download gating from the previous commit inverts its default: runtime model fetches (public model weights only, never user data) are allowed out of the box so AI tools self-heal, and SNAPOTTER_ALLOW_MODEL_DOWNLOAD=0 becomes the explicit strict offline mode for airgapped deployments, where every fallback raises the actionable error instead of fetching. Changes: offline_guard blocks only on an explicit 0/false; the unconditional HF_HUB_OFFLINE/TRANSFORMERS_OFFLINE image ENV is removed and bridge.ts sets those flags for the sidecar only in strict mode; .env.example documents the new default; install_feature's lift/restore stays. All bundled-path preferences, pre-existence checks, and symlink pre-placement remain, so installed bundles never trigger a download. The OSM, Scalar font, and editor font fixes are unchanged. Validation rerun: typecheck 9/9, Biome clean on touched files, 5178 unit tests pass, py_compile on touched scripts, guard behavior verified for unset/1 (allowed) and 0/false (blocked with the new message). Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7 |
||
|
|
8b3f1e6884 |
fix: stamp SnapOtter as Producer on generated PDFs (#416)
Conversion engines wrote their own names into PDF metadata: LibreOffice, Ghostscript, pdfcpu, WeasyPrint, and PDFKit all stamped Producer/Creator on generated files. A new doc_scrub_meta docs-profile script (PyMuPDF) rewrites both fields to SnapOtter and drops the stale XMP copy; the worker applies it to the 25 PDF-generating tools before outputs reach object storage. Best effort by design: any failure keeps the original bytes and only logs a warning. Deliberately untouched: tools that edit the user's own PDF and preserve its metadata (qpdf edits, sign, flatten), encrypted outputs (copied through), and pdfa-convert, where a metadata rewrite risks PDF/A conformance. Claude-Session: https://claude.ai/code/session_01XGB4pGvTvb7sUX4JN745U7 |
||
|
|
b4375e558d |
fix: harden install queue/dispatcher lifecycle and repair review-sweep regressions (#395)
Fixes 15 defects found by a max-effort multi-agent review of the last 6 merged PRs (#388, #390, #391, #392, #393, #394), all adversarially verified before fixing. Install queue + dispatcher (the serious cluster): - features.ts: finalize the installer child exactly once. A failed spawn fires both "error" and "close", and the second event released the file lock and active slot that pump() had just handed to the next queued bundle, letting two pip processes write the same venv concurrently. Outcome recording now happens before pump() so the next bundle's first progress frame cannot race the previous install's bookkeeping. - feature-status.ts: keep failed-install errors in a per-bundle map instead of the single progress slot. With the queue auto-starting the next install, the slot was overwritten within seconds and a failed install vanished without ever surfacing to GET /features. - bridge.ts: scope child lifecycle per process (stopped-children set + request generation tags) instead of an instance-wide shuttingDown flag that the next spawn reset. A stale SIGTERMed child's late close event could record a phantom crash (5 of which permanently disable the dispatcher), null out the freshly spawned child, and reject the new child's pending requests. The request-timeout kill path still counts as a real crash. - install_feature.py: the pre-write disk re-check measured ai_dir's filesystem even when budgeting the cross-filesystem copy that lands on the venv's disk; now each budget is checked against the filesystem the bytes actually land on, so ENOSPC cannot strike mid-write and leave site-packages half overwritten. Behavior regressions: - embed-subtitles: preserve pre-existing subtitle tracks (0:s?) and MKV attachments (0:t?) that the -map 0:v:0/0:a? rewrite silently dropped; data streams stay unmapped on purpose (the actual MPEG remux fix). The new subtitle maps first so the language tag hits the right stream. - usage-survey-overlay: fail closed when the settings fetch fails; the fail-open path rendered the blocking survey against an unhealthy API and soft-locked admins, the lock-out class #392 fixed. - features-store: queued bundles poll instead of each holding an SSE connection (Install All could pin 7 EventSources and exhaust the browser's 6-per-origin HTTP/1.1 limit, hanging the whole app); listenToProgress closes any prior stream and stops any poll before subscribing; installAll skips bundles already installing or queued. Contracts, tests, i18n: - openapi.yaml: add "queued" to the features status enum and document downloadBytes/installedBytes (Schemathesis conformance). - feature-lifecycle e2e: queue transcription (~0.5 GB) instead of ocr (~6 GB) and give the test a budget that covers both install drains (the stacked waits exceeded the old 900s timeout). - docker-compose.qa.yml: parameterize the host port (QA_APP_PORT) so QA_PROJECT_NAME concurrent stacks can actually bind. - compare + watermark-image: restore per-input error attribution ("Invalid first/second image", "Invalid watermark image") lost in the shared-handler migration. - ai-features-section: the "{size} on disk" suffix now goes through i18n; key added to all 21 locales. - watermark-image + content-aware-resize: migrate to the shared inputHandlerFor("image") chain like compare/vectorize/compose, fixing drift in the inline copies (no SVG sanitize, no RAW extension hint, no AVIF probe). Verified: typecheck across 9 workspaces, Biome clean on all changed files, 584 targeted unit tests and 249 integration tests green (including real-ffmpeg embed-subtitles runs). One unit test updated to the new poll-while-queued contract with a single-EventSource assertion. Claude-Session: https://claude.ai/code/session_017mR1HiHaf3a1BmUtrHX4j3 |
||
|
|
b37faed95f |
fix: QA sweep - tool routes, security, i18n, a11y, + AI bundle install hardening (#393)
* fix(api): correct format/filename/container handling across tool routes Found during a comprehensive QA sweep exercising every tool against its full accepted-format matrix: - watermark-image, compose: preserve the requested output format and a matching download filename/extension instead of always emitting the source format - compose: crop oversized overlays to the visible base area instead of crashing Sharp's composite, and reject only overlays fully outside the base image instead of any oversized one - compare, vectorize: switch to the shared image input handler so filenames and formats like .svgz/.tga/RAW survive validation instead of being rejected pre-processing - tool-factory, images-to-video: normalize frames through Sharp before handing them to FFmpeg, fixing GIF/AVIF/RAW image-to-video jobs that previously failed or hung - media-tool, replace-audio, embed-subtitles: fix legacy container MIME/codec handling for MPEG sources and subtitle remux cases - files: expand download MIME mapping for text/data/document/video/audio outputs that were falling back to a generic content type - convert-document/presentation/spreadsheet: same-format conversions now return the original validated file instead of erroring or producing corrupt tiny output Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(web): dropzone a11y, stale localStorage getter, dead code - dropzone: stop making the whole drop-zone section clickable/focusable. A section acting as an interactive element around a real upload button is a nested-interactive-element anti-pattern that confuses screen readers; drag-and-drop doesn't need focus semantics, only the button fallback does. Keeps that button semantic and keyboard-reachable. Updates the two e2e call sites that clicked the section directly. - api, use-auth: read through window.localStorage via the existing API storage helper instead of the bare global, which resolves to Node's experimental localStorage getter under Vitest and threw - find-duplicates-settings, info-settings, login-page: remove dead code (unused zip-download handler, a stale mount-only effect dependency that left cached info stuck at reused indices, an unused response variable) Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(i18n): pt-BR, zh-CN, zh-TW were silently falling back to English The locale loader looked up dynamic-import exports by the raw locale code (mod["pt-BR"], mod["zh-CN"], mod["zh-TW"]), but those three modules export camelCased bindings (ptBR, zhCN, zhTW) since identifiers can't contain hyphens. The lookup returned undefined and every consumer silently fell back to English for these three locales. Replaces the generic lookup with explicit per-locale loaders so the mapping can't drift out of sync again. Also updates the dropzone helper copy across all 21 locales to match the drag-only dropzone wording from the previous commit. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(docs): clear build warnings in the VitePress site - config.mts: add an onwarn handler for the @vueuse INVALID_ANNOTATION warnings emitted during the docs build - deployment.md: the caddyfile code fence language isn't a shiki grammar VitePress ships with, so it warned on every build; use txt instead Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * test(qa): update QA harness for the drag-only dropzone and regen metadata - api-sweep, qa-helpers, verify-ai: add JSON-body tools, multi-input secondary fixtures, async polling for slow valid jobs, 501 FEATURE_NOT_INSTALLED skip handling, and safer per-tool settings - input-preview, pipeline-ui specs: update upload flow for the drag-only dropzone surface - add tests/fixtures/data/valid/chart.json, a valid chart fixture the updated helpers route to - regenerate tools-meta.json against current TOOLS[] Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(security): close a login timing side-channel, harden zip-slip tests Found during a black-box security sweep of the real auth-enabled production container: a nonexistent username returned 401 in ~3-10ms, while a wrong password for a real user took ~35-42ms, because scrypt verification only ran when a user row existed. That timing gap lets an attacker enumerate valid usernames without ever guessing a password. Now runs verification against a cached dummy hash on the unknown-user path too, so both cases cost the same regardless of outcome. extract-zip already had a relative-traversal regression test (../evil.txt), but its absolute-path rejection branches (name.startsWith("/") / startsWith("\\")) had none. Added the three missing cases: deep relative traversal, absolute Unix path, and Windows-style absolute path. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * test(qa): add UI-driven AI bundle install scripts QA_PROMPT.md's Phase 2 requires installing AI models the way a user does -- through the UI, on demand from HuggingFace -- and treats the curl-based admin install endpoint as fallback-only. Nothing in the harness actually drove that flow; tests/qa/seed-ai-models.sh installs via docker exec + pip, which is further from a real user than even the API fallback. install-ai-bundles-ui.mts logs in, opens Settings > AI Features, screenshots the pre-install state, clicks Install All, and screenshots progress -- then exits, since installs continue server-side once triggered. verify-ai-install-complete.mts polls bundle status, screenshots the completed state, and runs one real tool per installed bundle to prove the freshly-downloaded model actually executes. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(qa): correct the apiToolPath import in the AI verify script Dynamic import of the package name failed under tsx's module resolution from apps/api's node_modules context; use the same relative-path import api-sweep.mts already uses successfully. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(web): correct AI bundle size estimates shown before install Measured real downloads during GPU-node QA verification: photo-restoration pulls ~4.4GB (was advertised as 800MB-1GB, off by 4-5x) and ocr pulls ~5.5GB (was advertised as 3-4GB). Both estimates only accounted for model weights, not the pip dependencies (torch/paddle) that come down with them. Updated to reflect actual total download size, since that's what a user deciding whether they have the disk/bandwidth actually needs to know. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(web): make desktop Settings reachable when auth is disabled AvatarDropdown (the only desktop entry point to Settings) was gated behind `!isMobile && authEnabled`. With AUTH_ENABLED=false the synthetic anonymous admin user should have full Settings access per how auth.ts documents this mode -- and the mobile bottom nav already worked this way, showing Settings unconditionally. Desktop just had a stray extra gate the component doesn't need: AvatarDropdown already resolves its own username internally (falling back to "admin") and reads authEnabled itself where it actually matters (hiding the Logout button). Removed the outer gate; verified end-to-end against a fresh AUTH_ENABLED=false instance -- avatar now renders, Settings opens, shows the anonymous/Admin identity correctly. Also documents (not changes) a related finding in install_feature.py: detect_arch() always resolves amd64 hosts to the GPU-bundled archive variant regardless of actual GPU presence, since no CPU-only amd64 archive is published to the bundle repo yet. Left as a code comment rather than a behavior change, since requesting an unpublished archive key would hard-fail installs entirely -- worse than the current oversized-but-working download. Full detail in the QA report. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(ai): stop logging expected dispatcher reloads as crashes After each AI bundle install the Python dispatcher reloads because the venv changed, and after every app shutdown it's SIGTERMed. Both took the close handler's `code !== 0` branch (SIGTERM makes the exit code null), so they were counted as crashes -- producing an alarming "crash" line in the logs and a pointless ~1s recovery backoff after each of 7 installs. A `stopping` flag set in shutdown() lets the close handler tell an intentional stop apart from a real crash. The request-timeout kill path deliberately does not set it, so a genuinely hung script still records a crash and the 5-in-60s permanent-disable threshold is untouched. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(api): return a clean message when content-aware resize times out Carving a very high-resolution image down to a tiny target could exceed the caire subprocess timeout, and the raw error forwarded to the user was caire's terminal output -- ANSI color codes and progress-spinner control characters -- instead of anything actionable. Now: the timeout path throws a clear "timed out; try a smaller image or larger target" message (keeping the raw stderr as `cause` for server logs); friendlyError() strips ANSI/control chars centrally so any subprocess dump surfaced through the shared sanitizer is plain text; and the content-aware-resize route (a custom route that bypassed the sanitizer) now routes its error paths through friendlyError like every other tool. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(ai): stop bundle installs from exhausting host disk Installing an AI bundle on a tight-disk host could push the root filesystem to zero bytes free after the preflight check had already passed. Two root causes: - move_tree used copytree+rmtree, so during the move the extracted payload existed in both staging and the venv at once -- a full transient doubling on disk. Rewrote it to rename entries (a cheap metadata op on the same filesystem, no copy), falling back to a copy only across filesystems. - the preflight budget used the manifest's extractedSize verbatim, which is 0 for several archives, collapsing the estimate to just the compressed size. Added a conservative fallback (3x compressed) so a missing value can't under-reserve. Also added a real-on-disk re-check immediately before the first destructive venv write (measuring the actual extracted payload and whether the move needs extra space for a cross-filesystem copy), which also now covers the offline-import path that previously skipped the disk check entirely; wrapped the moves so an out-of-space failure returns a clean actionable error instead of a traceback; and made the disk check resolve the nearest existing ancestor so it never throws on a not-yet-created venv path. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * feat(web): show the real per-arch AI bundle download size The bundle cards and install prompt showed a hardcoded, architecture-blind estimatedSize string. That's misleading: amd64 hosts always pull the CUDA-inclusive archive (there's no CPU-only amd64 variant published), so a bundle labelled "1-2 GB" can actually download several times that, while arm64 pulls a much smaller archive for the same label. The manifest already carries the real per-arch compressedSize (and extractedSize where measured), so surface those: a new optional downloadBytes/installedBytes on FeatureBundleState, populated in getFeatureStates() for this host's arch (resolver mirrors install_feature.py detect_arch), shown by the UI when present with estimatedSize kept as the fallback label. Also nudged upscale-enhance's fallback string (4-5 -> 5-6 GB) to match its real compressed size, consistent with the earlier photo-restoration/ocr fixes. Fields are optional so demo/mock and existing tests stay compiling; the manifest's extractedSize is 0 for a few archives, which now surfaces as null rather than a bogus 0. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(web): move the AI install queue to the server so it survives tab close Installing multiple bundles could silently lose all but the first. The server rejected a concurrent install with 409, so the client worked around it by queueing the rest in browser-local state and only POSTing each once it saw the previous finish. A single POSTed install is durable (the installer child is detached from the request), but a queued one had zero server footprint -- close the tab mid-queue and those installs vanished with no error, while the UI still showed them "Queued". The client "mutex" didn't even serialize: the queued bundles' local waits all resolved at once and raced into concurrent POSTs that 409'd each other. Now the queue lives on the server (a small in-memory FIFO leaf module). The install endpoint enqueues instead of 409-ing and returns 202 {jobId, queued}; a pump starts the next bundle when the current one's child exits (and after an offline import releases the lock), all behind the existing venv + file locks, which are unchanged. The client just POSTs every bundle immediately and reflects the server-reported queued/installing status; Install All fires all POSTs and lets the server serialize them, keeping the one-shot retry-on-failure. Adds "queued" to FeatureStatus (the bundle card already rendered that state) and surfaces it from getFeatureStates. In-memory is deliberate: it matches the existing contract (survives a tab close, not a server restart, which already clears the lock on boot). Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG * fix(qa): don't log env-derived credentials in the AI-install script CodeQL flagged clear-text logging of sensitive information: the login status line interpolated the QA base URL and username (both read from the process environment) into a console.log. Replaced with a static message. QA helper only, but it's a real hygiene issue and cleared the high-severity code-scanning alert on the PR. Claude-Session: https://claude.ai/code/session_019fpSXhLGLXWwfyZY2tWhLG |
||
|
|
bd1838e40b |
fix: repair docker validation QA tooling, dispatcher crash-accounting, and image-enhancement RAW hang (#391)
Found and fixed during a full local Docker build validation (amd64/arm64, all four fleet targets, AI bundle installs, QA harness) and the follow-up bug sweep requested afterward. None of the affected scripts run in CI, so these had been silently broken indefinitely. - docker/feature-manifest.json: pythonVersion was a flat "3.11", but the amd64 base (Ubuntu 24.04) ships Python 3.12 while arm64 (Debian bookworm) ships 3.11. Changed to a per-arch object matching the file's existing convention. - tests/qa/api-sweep.mts and verify-ai.mts: bare "@snapotter/shared" import can't resolve since tests/ is not a pnpm workspace member, making both silently unrunnable via their own documented command on any fresh checkout. Switched to a relative import. - tests/qa/generate-ledger.mts: wrote to docs/qa/ without creating the directory first; docs/ is gitignored except COMMUNITY_GUIDE.md, so a fresh checkout threw ENOENT. - Seven QA Playwright spec files (input-preview, settings, settings-extended, multifile, output-preview, pipeline-ui, smoke) had ~115 fixture() calls using directory names that don't exist. Resolved every call programmatically against the real fixture tree. - packages/ai/src/bridge.ts: AI dispatcher restart (happens on every bundle install) was falsely counted as a crash, risking permanent dispatcher disable after enough legitimate restarts within the crash window. Added a shuttingDown flag checked at all three recordCrash() call sites. - packages/image-engine/src/operations/auto-enhance.ts: image-enhancement hung 40+ seconds on large RAW photos (confirmed on a real 20.2MP file) in Sharp's .clahe() step, whose cost scales with total pixel count regardless of tile size. Added a 16-megapixel cap above which CLAHE is skipped; verified against the real file (40+s -> 2.0s) with no regression to other RAW formats or normal-sized images. Fixing this surfaced a second, smaller bug where the saturation step's CLAHE compensation boost was keyed off the raw toggle instead of whether CLAHE actually ran. - Two QA-harness robustness gaps closed per "fix everything, even the small bugs": the passport-photo/erase-object input-preview tests now skip cleanly with a clear reason on a container without their AI bundle installed, and docker-compose.qa.yml's hardcoded project/container name (the actual root cause of a mid-validation container swap between two concurrent sessions) is now parameterized via QA_PROJECT_NAME. Full validation report is local-only per repo convention. |
||
|
|
af7cd77e84 |
chore(deps): bump the production-deps group with 16 updates
Bumps the production-deps group with 16 updates: | Package | From | To | | --- | --- | --- | | [@scalar/fastify-api-reference](https://github.com/scalar/scalar/tree/HEAD/integrations/fastify) | `1.60.0` | `1.62.0` | | [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.59.0` | `10.62.0` | | [bullmq](https://github.com/taskforcesh/bullmq) | `5.79.1` | `5.79.2` | | [fastify](https://github.com/fastify/fastify) | `5.8.5` | `5.9.0` | | [js-yaml](https://github.com/nodeca/js-yaml) | `4.2.0` | `4.3.0` | | [playwright](https://github.com/microsoft/playwright) | `1.61.0` | `1.61.1` | | [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.38.2` | `5.38.6` | | [sharp](https://github.com/lovell/sharp) | `0.35.1` | `0.35.2` | | [tar](https://github.com/isaacs/node-tar) | `7.5.16` | `7.5.19` | | [lucide](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide) | `1.21.0` | `1.22.0` | | [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.59.0` | `10.62.0` | | [pdfjs-dist](https://github.com/mozilla/pdf.js) | `6.0.227` | `6.1.200` | | [posthog-js](https://github.com/PostHog/posthog-js) | `1.391.9` | `1.395.0` | | [react-hotkeys-hook](https://github.com/JohannesKlauss/react-keymap-hook) | `5.3.2` | `5.3.3` | | [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1073.0` | `3.1075.0` | | [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) | `3.1073.0` | `3.1075.0` | Updates `@scalar/fastify-api-reference` from 1.60.0 to 1.62.0 - [Release notes](https://github.com/scalar/scalar/releases) - [Changelog](https://github.com/scalar/scalar/blob/main/integrations/fastify/CHANGELOG.md) - [Commits](https://github.com/scalar/scalar/commits/HEAD/integrations/fastify) Updates `@sentry/node` from 10.59.0 to 10.62.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.59.0...10.62.0) Updates `bullmq` from 5.79.1 to 5.79.2 - [Release notes](https://github.com/taskforcesh/bullmq/releases) - [Commits](https://github.com/taskforcesh/bullmq/compare/v5.79.1...v5.79.2) Updates `fastify` from 5.8.5 to 5.9.0 - [Release notes](https://github.com/fastify/fastify/releases) - [Commits](https://github.com/fastify/fastify/compare/v5.8.5...v5.9.0) Updates `js-yaml` from 4.2.0 to 4.3.0 - [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.0/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.2.0...4.3.0) Updates `playwright` from 1.61.0 to 1.61.1 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.61.0...v1.61.1) Updates `posthog-node` from 5.38.2 to 5.38.6 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.38.6/packages/node) Updates `sharp` from 0.35.1 to 0.35.2 - [Release notes](https://github.com/lovell/sharp/releases) - [Commits](https://github.com/lovell/sharp/compare/v0.35.1...v0.35.2) Updates `tar` from 7.5.16 to 7.5.19 - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v7.5.16...v7.5.19) Updates `lucide` from 1.21.0 to 1.22.0 - [Release notes](https://github.com/lucide-icons/lucide/releases) - [Commits](https://github.com/lucide-icons/lucide/commits/1.22.0/packages/lucide) Updates `@sentry/react` from 10.59.0 to 10.62.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.59.0...10.62.0) Updates `pdfjs-dist` from 6.0.227 to 6.1.200 - [Release notes](https://github.com/mozilla/pdf.js/releases) - [Commits](https://github.com/mozilla/pdf.js/compare/v6.0.227...v6.1.200) Updates `posthog-js` from 1.391.9 to 1.395.0 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/compare/posthog-js@1.391.9...posthog-js@1.395.0) Updates `react-hotkeys-hook` from 5.3.2 to 5.3.3 - [Release notes](https://github.com/JohannesKlauss/react-keymap-hook/releases) - [Changelog](https://github.com/JohannesKlauss/react-hotkeys-hook/blob/main/CHANGELOG.md) - [Commits](https://github.com/JohannesKlauss/react-keymap-hook/compare/v.5.3.2...v5.3.3) Updates `@aws-sdk/client-s3` from 3.1073.0 to 3.1075.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1075.0/clients/client-s3) Updates `@aws-sdk/lib-storage` from 3.1073.0 to 3.1075.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1075.0/lib/lib-storage) --- updated-dependencies: - dependency-name: "@scalar/fastify-api-reference" dependency-version: 1.62.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/node" dependency-version: 10.62.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: bullmq dependency-version: 5.79.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: fastify dependency-version: 5.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: js-yaml dependency-version: 4.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: playwright dependency-version: 1.61.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: posthog-node dependency-version: 5.38.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: sharp dependency-version: 0.35.2 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: tar dependency-version: 7.5.19 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: lucide dependency-version: 1.22.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/react" dependency-version: 10.62.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: pdfjs-dist dependency-version: 6.1.200 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: posthog-js dependency-version: 1.395.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: react-hotkeys-hook dependency-version: 5.3.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: "@aws-sdk/client-s3" dependency-version: 3.1075.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@aws-sdk/lib-storage" dependency-version: 3.1075.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps ... Signed-off-by: dependabot[bot] <support@github.com> |
||
|
|
0cdd560ac4 |
feat: add Sign PDF tool (draw/type/upload signatures, place on a PDF) (#370)
Draw, type, or upload a signature and place resizable/rotatable copies across PDF pages; output flattened server-side with PyMuPDF. Visual electronic signature, not cryptographic. New interactive-sign display mode (pdf.js + Konva) and a custom docs-pool route. |
||
|
|
8f4235d2c6 |
fix(enterprise): ship enterprise package in prod image + S3, analytics, tracing, queue fixes (#342)
* fix(enterprise): ship enterprise pkg in prod image, full license features, tracing key fallback docker/Dockerfile: COPY packages/enterprise manifest+src into the production stage. Without it, apps/api's workspace link to @snapotter/enterprise dangles and every import() throws (silently caught), so all 19 enterprise features failed closed (enterprise.active=false) regardless of a valid license. scripts/generate-license.mjs: sync PLAN_FEATURES with packages/enterprise/src/license.ts so a --plan enterprise license unlocks all 19 features (was 8) and team unlocks 8. apps/api/src/tracing.ts: accept SNAPOTTER_LICENSE_KEY as a fallback to LICENSE_KEY so distributed_tracing activates with the same key as the rest of the app. * fix(docker): keep scripts/bake-analytics.mjs in build context .dockerignore excluded the whole scripts/ dir (PR #82, V1 hardening), but docker/Dockerfile later added 'COPY scripts/bake-analytics.mjs' for the analytics bake step. A clean production image build therefore fails with 'scripts/bake-analytics.mjs: not found'. The published image build is gated off in CI so this latent break went unnoticed. Exclude scripts/* but re-include the one file the Dockerfile needs. * fix: S3 upload stream, analytics bake reaches API, dedupe retention field, reconcile orphan jobs storage-s3.ts: wrap the upload AsyncIterable in Readable.from() so @aws-sdk/lib-storage accepts it. STORAGE_MODE=s3 file uploads failed with 'Body Data is unsupported format' for every tool because a bare async generator is not a Readable. docker/Dockerfile: COPY the builder-baked analytics baked.ts into the API runtime stage. The API re-copied the committed (off) baked.ts from the build context, so the SNAPOTTER_ANALYTICS build arg had no effect on the API -- and since the SPA reads /api/v1/config/analytics, analytics was off everywhere regardless of the arg. settings-dialog.tsx: remove the duplicate tempFileMaxAgeHours control under Data Retention; it bound the same setting key as the File Management control with a different default, so editing either silently overwrote the other. apps/api/src/index.ts: reconcile orphaned job rows (empty tool_id, never enqueued to BullMQ) at boot so they don't sit in processing/queued forever and inflate the per-user concurrent-job count and the upgrade-check in-flight gate. * fix(web): style the SSO login buttons (they referenced undefined theme tokens) The OIDC/SAML 'Sign in with <provider>' buttons used bg-secondary / text-secondary-foreground, which the web theme never defines (it has primary, background, foreground, muted, border, card, primary-subtle). Those classes resolved to nothing, so the buttons rendered as bare unstyled text on the login page. Restyle: the optional (non-enforced) buttons become white-card outline buttons with a key icon and an orange hover tint, secondary to the primary Login button; the SSO-enforced buttons become solid primary with the icon. * fix: gate S3 behind license, custom-role enterprise perms, wire retention UI, cleanup S3 is a licensed feature, but shipping packages/enterprise in every image removed the implicit gate, so STORAGE_MODE=s3 worked without a license. Enforce isFeatureEnabled('s3_storage') at boot and fail fast if unlicensed. Custom roles can now be granted security:manage / compliance:manage / webhooks:manage (roles.ts ALL_PERMISSIONS + the Roles UI) so admins can build least-privilege compliance/security roles instead of only the built-in admin role. retentionSweep now reads the jobsRetentionDays / auditRetentionDays DB settings the System Settings UI writes (env vars become the fallback default), mirroring how the temp-file sweep reads tempFileMaxAgeHours. Previously those two UI controls were no-ops. Cleanup: drop the never-set snapotter_storage_bytes gauge and the unused MAX_WORKSPACE_SIZE_GB env var; emit tool_client_error to PostHog from the web ErrorBoundary (client crashes were not reaching analytics); add the Python OpenTelemetry packages so the innermost sidecar.<script> span exports; fix the stale 'only local storage' line in the docs; delete two e2e-analytics specs that tested the removed consent UI. * fix(env): restore MAX_WORKSPACE_SIZE_GB default security-auth-hardening.test.ts asserts env.MAX_WORKSPACE_SIZE_GB defaults to 10, so the var is an intentional (tested) default, not dead code. Removing it in the cleanup commit broke that unit test. Keep the declaration. |
||
|
|
35e18d8b79 |
fix: GPU deployment robustness (6 fixes from end-to-end testing on an RTX 4070) (#334)
* fix(docker): pin CUDA base to 12.6 so the GPU image starts on R560+ drivers The amd64 base nvidia/cuda:12.9.2-cudnn-runtime bakes a cuda>=12.9 driver gate enforced by nvidia-container-toolkit at container start, so the image fails to launch on common production drivers (e.g. 570.x / CUDA 12.8). The AI bundles are all cu126 wheels and the image installs libcublas-12-6, so 12.9 was misaligned with the workload. Pin to nvidia/cuda:12.6.3-cudnn-runtime-ubuntu24.04 to match the wheels and lower the driver floor to R560+. * fix(ai): broaden OOM detection so the rembg lighter-model fallback fires onnxruntime/CUDA allocation failures surface as 'Failed to allocate memory for requested buffer', CUBLAS_STATUS_ALLOC_FAILED, or bad_alloc, not just 'out of memory'. The background-removal and transparency-fixer fallback-to-lighter-model paths only matched the literal 'out of memory', so the fallback was dead code and transparency-fixer (default birefnet-hr-matting) always failed with an allocation error. Add isMemoryAllocError() and use it in both checks. * fix(ai): use bundled PaddleOCR models so OCR runs offline ocr.py passed no model dirs to PaddleOCR, so PaddleX resolved models from ~/.paddlex and downloaded them from HuggingFace at runtime (slow first use, broken air-gapped), ignoring the models the OCR bundle ships in MODELS_PATH; it also pulled doc-orientation/unwarping models that are not bundled. Pin detection, recognition and textline models to the bundled dirs in MODELS_PATH (per language) and disable use_doc_orientation_classify / use_doc_unwarping, with per-component fallback when a model is absent. Verified: OCR runs with zero HuggingFace requests. * fix(docker): add CAP_KILL so container shutdown is graceful cap_drop: ALL without re-adding KILL meant tini (PID 1, root) could not forward SIGTERM to the gosu-dropped snapotter process (root minus CAP_KILL cannot signal a different UID). docker stop logged '[FATAL tini] forwarding signal: Operation not permitted', never delivered the signal, and fell back to SIGKILL after the 10s timeout. Add KILL to cap_add in both compose files. Verified: docker stop completes in 0s with SIGTERM delivered (exit 143) and no FATAL tini. * fix(ai): serialize bundle installs against AI jobs to prevent sidecar segfault A feature bundle install rewrites the shared Python venv (pip + copytree of site-packages/*.so) as a background subprocess, with no coordination against AI tool jobs that dlopen native libs (torch / onnxruntime CUDA) from the same venv; a job loading a shared object while it is overwritten segfaults the sidecar. Add a process-wide async mutex (venv-lock.ts): bridge.run() acquires it before every AI script and the install route holds it across the installer subprocess. Both run in the same Node process so a module-level lock suffices. Verified: concurrent install + AI job produces zero segfaults and the job serializes behind the install. * fix(ai): make the venv lock read/write so concurrent AI jobs are not serialized The first cut used an exclusive mutex, which (a) deferred the dispatcher spawn by a microtask and broke unit tests that synchronously drive the mocked spawn, and (b) serialized AI jobs against each other, removing the dispatcher's by-id request multiplexing. Make it a writer-preferring read/write lock: AI jobs are shared readers (with a synchronous fast path so spawn still happens in-tick) and a bundle install is the exclusive writer. Verified: all 764 AI unit tests pass. * fix(ai): degrade OCR to Tesseract on CPU-only hosts instead of segfaulting The amd64 AI bundle ships paddlepaddle-gpu, whose native libs dlopen libcuda.so.1 at import and segfault on a host without a GPU (libcuda is the driver lib, injected only by nvidia-container-toolkit on GPU hosts). The segfault crashed the shared long-lived AI dispatcher and, after a few attempts, tripped the bridge crash-recovery permanent-disable, wedging all AI until a container restart. The standalone ocr tool defaults to quality=balanced (PaddleOCR), so it hit this on every CPU-only deployment; ocr-pdf already hardcoded Tesseract and was unaffected. ocr.py now gates the PaddleOCR tiers on gpu_available(): balanced/best transparently fall back to fast (Tesseract, CPU-capable) when no usable GPU is present, and run_paddleocr_v5/run_paddleocr_vl refuse before importing paddle so the GPU build is never dlopen'd on CPU. GPU hosts are unchanged. Verified on a CPU-only Windows/WSL2 box: ocr returns Tesseract text across repeated runs with the dispatcher staying healthy (no wedge). |
||
|
|
7a70affac5 |
fix(ai): enforce the feature gate on the per-request fallback path (#331)
The persistent Python dispatcher rejects scripts whose feature bundle is not installed, but the per-request fallback (used when the dispatcher is down, e.g. restarting right after a model repair) spawned scripts directly and bypassed that gate. Behavior was therefore inconsistent: a gated script would fail under the dispatcher but run under the fallback -- the "works once after a repair" symptom from the original report. - add packages/ai/src/feature-gate.ts: SCRIPT_BUNDLE_MAP + missingBundleForScript, mirroring TOOL_BUNDLE_MAP in dispatcher.py, reading the same installed.json and failing closed exactly like dispatcher._get_installed_bundles() - runPerRequest now rejects with "feature_not_installed" (the same message the dispatcher path surfaces) when a gated script's bundle is not installed - unit tests for the gate, plus a drift test pinning the TS map to dispatcher.py Closes #327 |
||
|
|
c8371c3cd1 |
chore(deps-dev): bump the dev-deps group with 5 updates (#297)
Bumps the dev-deps group with 5 updates: | Package | From | To | | --- | --- | --- | | [@playwright/test](https://github.com/microsoft/playwright) | `1.60.0` | `1.61.0` | | [@testcontainers/postgresql](https://github.com/testcontainers/testcontainers-node) | `12.0.2` | `12.0.3` | | [@testcontainers/redis](https://github.com/testcontainers/testcontainers-node) | `12.0.2` | `12.0.3` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.19.21` | `22.20.0` | | [vitepress-plugin-llms](https://github.com/okineadev/vitepress-plugin-llms) | `1.13.1` | `1.13.2` | Updates `@playwright/test` from 1.60.0 to 1.61.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.60.0...v1.61.0) Updates `@testcontainers/postgresql` from 12.0.2 to 12.0.3 - [Release notes](https://github.com/testcontainers/testcontainers-node/releases) - [Commits](https://github.com/testcontainers/testcontainers-node/compare/v12.0.2...v12.0.3) Updates `@testcontainers/redis` from 12.0.2 to 12.0.3 - [Release notes](https://github.com/testcontainers/testcontainers-node/releases) - [Commits](https://github.com/testcontainers/testcontainers-node/compare/v12.0.2...v12.0.3) Updates `@types/node` from 22.19.21 to 22.20.0 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `vitepress-plugin-llms` from 1.13.1 to 1.13.2 - [Release notes](https://github.com/okineadev/vitepress-plugin-llms/releases) - [Commits](https://github.com/okineadev/vitepress-plugin-llms/compare/v1.13.1...v1.13.2) --- updated-dependencies: - dependency-name: "@playwright/test" dependency-version: 1.61.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: "@testcontainers/postgresql" dependency-version: 12.0.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@testcontainers/redis" dependency-version: 12.0.3 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@types/node" dependency-version: 22.20.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: vitepress-plugin-llms dependency-version: 1.13.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
5d5117acf7 |
fix(deps): close js-yaml DoS alert + document rembg non-reachability (#286)
* fix(deps): patch gray-matter onto js-yaml 4.2.0 (close js-yaml DoS alert) js-yaml 3.14.2 (quadratic-complexity DoS in merge-key handling, GHSA patched only in 4.2.0) was kept in the tree by a scoped pnpm override "gray-matter>js-yaml": "^3.14.1" that exempted gray-matter from the global js-yaml>=4.2.0 override. gray-matter is a build-time-only transitive dep of the docs site (vitepress-plugin-llms, @sugarat/theme-shared) and pinned 3.x because it calls the removed yaml.safeLoad / yaml.safeDump APIs. Remove the exemption so gray-matter resolves js-yaml 4.2.0, and add a pnpm patch renaming safeLoad->load / safeDump->dump (the 4.x equivalents; load is safe by default). js-yaml 3.x is now gone from the lockfile. Verified: gray-matter parse+stringify smoke test passes on 4.2.0; full VitePress docs build green (177 pages, llms plugin parses all tool frontmatter with no safeLoad/safeDump error). * docs(ai): document rembg 2.0.69 pin and advisory non-reachability The patched rembg 2.0.75 pulls a numpy 2.x closure (numpy>=2.3, scipy>=1.16, scikit-image>=0.26) that is incompatible with the numpy==1.26.4-locked AI stack (realesrgan 0.3.0 and codeformer-pip 0.0.4 break on numpy 2.x). Both open rembg advisories are unreachable in this codebase: rembg is used purely as a library (never the `rembg s` server), and new_session() only receives allowlisted model names (remove_bg.py ALLOWED_MODELS), never user-controlled paths. Record this rationale next to the pin; the Dependabot alerts are dismissed as not_used. |
||
|
|
4fdd10f488 |
revert(deps): keep rembg at 2.0.69 (2.0.75 conflicts with pinned numpy==1.26.4)
rembg 2.0.75 requires a numpy incompatible with the pinned numpy==1.26.4 that the rest of the ML stack (onnxruntime etc.) depends on, making pip-audit's resolution impossible. The rembg <2.0.75 advisory (medium) is accepted as a residual: it only affects the on-demand background-removal AI bundle (publishing currently paused) and can't be patched without a numpy 2.x migration across the whole Python sidecar. |
||
|
|
f21667db67 |
chore(deps): patch vulnerable dependencies (Dependabot/CodeQL)
- dompurify >=3.4.11 (runtime SVG sanitization) - nanoid 4.x -> >=5.0.9 (vulnerable 4.0.x transitive; 3.x/5.x kept) - undici >=8.5.0 (dev-only: jsdom/vitest/semantic-release; removes 8.4.1) - rembg 2.0.69 -> 2.0.75 (Python AI sidecar, CPU + GPU) js-yaml is already >=4.2.0; the residual 3.14.2 is gray-matter's build-time pin (no 3.x patch exists). typecheck + build pass. |
||
|
|
2bd3e2302a |
fix: post-2.0 audit bug fixes (404 route, worker logging, outpaint gate, pandoc path)
Surgical post-2.0 fixes: SPA 404 route, worker logging, outpaint gate, pandoc path resolution. |
||
|
|
3b50bcdc5c |
fix(ai-bundles): repair bundle build + publish pipeline (deepsafe repo, CPU provider, manifest)
Bundle build/publish fixes: CPUExecutionProvider in rembg build, pip/import/arm64 deps, hf-CLI publish to deepsafe/feature-bundles, real manifest sha256+sizes, installer fallback repo. |
||
|
|
3726335063 |
fix(ai): pin rembg to 2.0.69 to keep numpy<2 compatibility
rembg 2.0.70+ requires numpy>=2.3.0, but the AI bundle pins numpy==1.26.4 (mediapipe, realesrgan/basicsr, codeformer, paddle all need numpy<2). The unresolvable rembg==2.0.75 + numpy==1.26.4 combination broke pip-audit's dependency resolution (CI red) and the background-removal bundle build. 2.0.69 is the newest rembg with an unconstrained numpy requirement. Verified: pip-audit resolves with no unignored vulnerabilities on Python 3.11. |
||
|
|
620552569e |
feat!: SnapOtter 2.0.0
Bump all workspace package versions and APP_VERSION to 2.0.0, marking the official 2.0 release. Removes the stale 1.x .release-notes.md artifact (semantic-release regenerates release notes). The 2.0/multimodality docs and rebrand already landed on main via #254 and #261, so this carries only the version designation forward from the rebrand branch. BREAKING CHANGE: SnapOtter 2.0 - the platform re-architecture (Postgres 17 + Redis 8 + BullMQ durable jobs, 157 tools across five modalities) is the 2.0 release line, replacing the 1.x SQLite single-container architecture. |
||
|
|
1f5b222267 |
test: fix docker test-image env and container-specific test guards
Make the full pnpm test:docker suite pass the env-dependent tests (~85 failures): - Dockerfile.test: ENV LD_LIBRARY_PATH=/usr/local/lib so the built libheif 1.21 is not shadowed by the base image's older system libheif (heif-dec failed with an undefined-symbol error -> 'No HEIF decoder found' on 72 HEIF tests); add libjxl-tools (JXL) and ghostscript + the ImageMagick policy.xml EPS allow-edit. - docker-compose.test.yml: SYNC_WAIT_MS=30000 so sync-wait image tools do not fall back to 202 under single-container contention (10 tests). - install_feature.py: guard tarfile.extractall(filter='data') behind Python>=3.12 (bookworm ships 3.11); the manual entry guards already protect. - feature-status.test.ts / docker-file-secrets.test.ts: skip the two cases that cannot hold inside the container (/.dockerenv always present; root bypasses chmod). Verified on host: all still pass. |
||
|
|
79233ff19d |
chore(deps): patch Dependabot security advisories (esbuild, qs, uuid, yaml, js-yaml, babel, otel, rembg) (#257)
Resolve the actionable Dependabot alerts via pnpm overrides (for transitive deps) and a Python pin bump. - pnpm overrides: esbuild >=0.28.1 (the lone high-severity alert), @babel/core >=7.29.6, @opentelemetry/core >=2.8.0, js-yaml >=4.2.0, qs >=6.15.2, uuid >=11.1.1, yaml >=2.8.3 - rembg 2.0.62 -> 2.0.75 in requirements.txt and requirements-gpu.txt Verified: pnpm install, typecheck, lint, and full build all pass. NOT included: the astro advisory requires Astro 5 -> 6 (a major, breaking framework upgrade), which warrants its own migration PR rather than a security bump. |
||
|
|
08961fcc89 |
fix: PDF tool QA sweep - library auto-save versioning, AI fileId threading, modality polish (#251)
* fix(pdf): never enlarge on compress, honor redact case, hide same-format convert
- compress-pdf: guard both modes so output is never larger than the input; low-DPI scans could be upsampled and grow. Falls back to the original bytes.
- doc_redact.py: caseSensitive=true now filters PyMuPDF's case-insensitive search to exact-case hits, so the toggle works instead of always over-redacting.
- convert-{document,presentation,spreadsheet}: omit the input's own format from the output dropdown; the backend already rejects same-format conversions.
Verified end-to-end against an isolated Docker stack during a full visual QA sweep of all 37 PDF tools.
* fix(ui): show real multi-file preview thumbnails per modality
The bottom multi-file preview strip rendered a raw <img src=blobUrl> for every file, so audio/video/PDF inputs showed a broken-image icon plus the filename. ThumbnailStrip now branches on FileEntry.previewKind: images use <img> (icon fallback on error), video shows a captured first frame, PDF shows a pdf.js page-1 render, and audio/other show a type icon + extension. Fixes the multi-file preview across all modalities.
Verified in the browser for image/PDF/audio/video.
* fix(modality): make pipeline, batch validation, save/upload, previews & UI modality-aware
The app grew up image-only; several paths still assumed image. They now dispatch on the tool/file modality (image/video/audio/document/file):
- pipeline /execute + /batch: validate+decode input via inputHandlerFor(modality) instead of validateImageBuffer, so PDF/audio/video/data pipelines work (were rejected 'Invalid image').
- batch: non-image inputs now get per-modality validation (ffprobe/qpdf) before the worker instead of passing through unchecked.
- files /upload, user-files /save-result + /thumbnail: accept non-image files (MIME from extension; video-poster / pdf-first-page thumbnails).
- postprocess CONTENT_TYPE_TO_EXT: cover video/audio/pdf/text/zip so output extensions are corrected for all modalities.
- worker pipeline-finalize: attach result payload to the complete SSE event so the sync-window-timeout fallback still delivers a download.
- frontend: batch-ZIP blob MIME by extension (not svg-only); modality-neutral fallback labels/filenames; 'smaller file' not 'smaller image'.
Found via a codebase-wide image-only-assumption audit. Verified: PDF/audio/video pipelines + batch now work; image paths unchanged. canBrowserPreview kept image-only by design (non-image is rendered by dedicated displayMode viewers).
* fix(pipeline): generate a modality-aware preview for pipeline results
processPipelineFinalize now derives the output content type from its extension and runs generatePreview (video poster / pdf first page / image thumb), sets previewRef on the result, and surfaces previewUrl in the /execute sync response and the SSE complete event (via buildLegacyResultPayload). Pipeline outputs get a preview like single-tool results instead of always returning previewUrl: undefined.
Verified: PDF pipeline -> previewUrl returns a valid PNG first-page render; png pipeline correctly has no previewUrl; audio/video/multi-step pipelines all 200.
* fix(worker): auto-save a new library version when processing a library file
The worker hardcoded savedFileId = undefined ('No auto-save') even though the whole versioning feature was wired around it: the frontend sends fileId for library files and reads result.savedFileId, tool-factory threads fileId into ToolJobData, and autoSaveToLibrary implements the new-version save -- but the worker never called it (dead code from the tool-first-workflow merge). processToolJob now calls autoSaveToLibrary with data.fileId; without a fileId it is a no-op, so tool-first uploads are unchanged.
Verified: processing a library PDF with fileId creates version 2 (parent linked, toolChain appended, savedFileId returned); processing without fileId saves nothing.
* fix(library): ownership check + modality-aware dimensions in autoSaveToLibrary
- Only create a new version when the requester owns the parent (parent.userId === opts.userId); prevents versioning another user's file via a known fileId.
- Dimensions are modality-aware: sharp for images, ffprobe (probeMedia) for video, null for audio/document. Previously sharp-only, so non-image versions always got null dims.
* fix(ai): thread fileId + real userId through the 16 AI tool routes
AI custom routes parsed neither the fileId multipart field nor the authenticated user (they hardcoded userId: null), so processing a library file via an AI tool never created a new version, and AI jobs were unattributed. Each route now parses fileId like clientJobId and passes getAuthUser(request)?.id as userId to enqueueToolJob.
Verified: ocr-pdf on a library PDF creates a new version (v2); the ownership check still denies cross-user versioning.
|
||
|
|
d8cf979d4b |
fix: resolve 18 QA-discovered bugs across tools, previews, and the AI pipeline (#242)
Exhaustive QA sweep of all 157 tools. Fixes: CSP blob media, csv-excel ExcelJS interop, ocr-pdf segfault, chart-maker upload, non-PDF doc preview, RAW decode, merge-tool multi-file path, html-to-image chromium, ogv/wma/amr/ac3 preview fallbacks, meme/gif/stabilize codecs, nav+home a11y. Plus orphan-format and test-debt cleanup, the AI bundle build script, and a reusable Playwright QA harness under tests/qa/. |
||
|
|
9a61cb6af1 |
chore(deps-dev): bump the dev-deps group with 10 updates (#238)
Bumps @biomejs/biome, @testcontainers/postgresql, @testcontainers/redis, @tailwindcss/vite, @types/node, @types/react, @types/yauzl, tailwindcss, semantic-release, turbo. |
||
|
|
784f7a28cd |
chore(deps): bump the production-deps group with 15 updates (#237)
Bumps the production-deps group with 15 updates: | Package | From | To | | --- | --- | --- | | [@scalar/fastify-api-reference](https://github.com/scalar/scalar/tree/HEAD/integrations/fastify) | `1.58.0` | `1.59.3` | | [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.56.0` | `10.57.0` | | [bullmq](https://github.com/taskforcesh/bullmq) | `5.78.0` | `5.78.1` | | [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `5.8.0` | `5.9.0` | | [ioredis](https://github.com/luin/ioredis) | `5.10.1` | `5.11.1` | | [pdfkit](https://github.com/foliojs/pdfkit) | `0.18.0` | `0.19.1` | | [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.35.14` | `5.37.0` | | [sharp](https://github.com/lovell/sharp) | `0.34.5` | `0.35.1` | | [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.56.0` | `10.57.0` | | [posthog-js](https://github.com/PostHog/posthog-js) | `1.379.2` | `1.386.6` | | [react-colorful](https://github.com/omgovich/react-colorful) | `5.6.1` | `5.7.0` | | [react-konva](https://github.com/konvajs/react-konva) | `19.2.3` | `19.2.5` | | [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.16.0` | `7.17.0` | | [@aws-sdk/client-s3](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/clients/client-s3) | `3.1066.0` | `3.1068.0` | | [@aws-sdk/lib-storage](https://github.com/aws/aws-sdk-js-v3/tree/HEAD/lib/lib-storage) | `3.1066.0` | `3.1068.0` | Updates `@scalar/fastify-api-reference` from 1.58.0 to 1.59.3 - [Release notes](https://github.com/scalar/scalar/releases) - [Changelog](https://github.com/scalar/scalar/blob/main/integrations/fastify/CHANGELOG.md) - [Commits](https://github.com/scalar/scalar/commits/HEAD/integrations/fastify) Updates `@sentry/node` from 10.56.0 to 10.57.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.56.0...10.57.0) Updates `bullmq` from 5.78.0 to 5.78.1 - [Release notes](https://github.com/taskforcesh/bullmq/releases) - [Commits](https://github.com/taskforcesh/bullmq/compare/v5.78.0...v5.78.1) Updates `fast-xml-parser` from 5.8.0 to 5.9.0 - [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases) - [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md) - [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.8.0...v5.9.0) Updates `ioredis` from 5.10.1 to 5.11.1 - [Release notes](https://github.com/luin/ioredis/releases) - [Changelog](https://github.com/redis/ioredis/blob/main/CHANGELOG.md) - [Commits](https://github.com/luin/ioredis/compare/v5.10.1...v5.11.1) Updates `pdfkit` from 0.18.0 to 0.19.1 - [Release notes](https://github.com/foliojs/pdfkit/releases) - [Changelog](https://github.com/foliojs/pdfkit/blob/master/CHANGELOG.md) - [Commits](https://github.com/foliojs/pdfkit/compare/v0.18.0...v0.19.1) Updates `posthog-node` from 5.35.14 to 5.37.0 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.37.0/packages/node) Updates `sharp` from 0.34.5 to 0.35.1 - [Release notes](https://github.com/lovell/sharp/releases) - [Commits](https://github.com/lovell/sharp/compare/v0.34.5...v0.35.1) Updates `@sentry/react` from 10.56.0 to 10.57.0 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.56.0...10.57.0) Updates `posthog-js` from 1.379.2 to 1.386.6 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/compare/posthog-js@1.379.2...posthog-js@1.386.6) Updates `react-colorful` from 5.6.1 to 5.7.0 - [Release notes](https://github.com/omgovich/react-colorful/releases) - [Changelog](https://github.com/omgovich/react-colorful/blob/master/CHANGELOG.md) - [Commits](https://github.com/omgovich/react-colorful/commits/5.7.0) Updates `react-konva` from 19.2.3 to 19.2.5 - [Release notes](https://github.com/konvajs/react-konva/releases) - [Commits](https://github.com/konvajs/react-konva/commits) Updates `react-router-dom` from 7.16.0 to 7.17.0 - [Release notes](https://github.com/remix-run/react-router/releases) - [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md) - [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.17.0/packages/react-router-dom) Updates `@aws-sdk/client-s3` from 3.1066.0 to 3.1068.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/clients/client-s3/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1068.0/clients/client-s3) Updates `@aws-sdk/lib-storage` from 3.1066.0 to 3.1068.0 - [Release notes](https://github.com/aws/aws-sdk-js-v3/releases) - [Changelog](https://github.com/aws/aws-sdk-js-v3/blob/main/lib/lib-storage/CHANGELOG.md) - [Commits](https://github.com/aws/aws-sdk-js-v3/commits/v3.1068.0/lib/lib-storage) --- updated-dependencies: - dependency-name: "@scalar/fastify-api-reference" dependency-version: 1.59.3 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/node" dependency-version: 10.57.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: bullmq dependency-version: 5.78.1 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: fast-xml-parser dependency-version: 5.9.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: ioredis dependency-version: 5.11.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: pdfkit dependency-version: 0.19.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: posthog-node dependency-version: 5.37.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: sharp dependency-version: 0.35.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/react" dependency-version: 10.57.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: posthog-js dependency-version: 1.386.6 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: react-colorful dependency-version: 5.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: react-konva dependency-version: 19.2.5 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: react-router-dom dependency-version: 7.17.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@aws-sdk/client-s3" dependency-version: 3.1068.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@aws-sdk/lib-storage" dependency-version: 3.1068.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
3fb8164fa5 |
feat: add OpenTelemetry distributed tracing (enterprise) (#232)
* feat(tracing): add OpenTelemetry dependencies and --import preload flag * feat(enterprise): add distributed_tracing feature gate * feat(tracing): add SDK bootstrap with enterprise gating * fix(tracing): correct test coverage for enterprise-unavailable path and prevent double-init Test 2 now mocks @snapotter/enterprise to throw an import error, exercising the catch block in the preload. Test 3 imports with no endpoint so the preload is a no-op, avoiding leaked SDK from double-initialization. Added idempotency guard to initTracing() as a safety net. * feat(tracing): add Pino trace mixin and shared logger When OTel tracing is active, every Pino log line now includes traceId, spanId, and traceFlags fields for log-to-trace correlation. The mixin is a no-op when no SDK is registered (community users). * feat(tracing): add _otel to ToolJobData and inject trace context at enqueue Add optional _otel carrier field to ToolJobData for W3C trace context propagation across BullMQ job boundaries. When an active OTel span exists, propagation.inject() writes traceparent/tracestate into the job data before queue.add(). When no SDK is registered (community edition), the carrier stays empty and _otel remains undefined -- zero overhead. * feat(tracing): extract trace context and create spans in BullMQ worker * feat(tracing): inject trace context into Python sidecar calls * feat(tracing): add trace context extraction to Python sidecar * feat(tracing): add shutdownTracing to graceful shutdown sequence * feat(tracing): enrich HTTP spans with tool_id and user_id attributes * docs: add OpenTelemetry env var documentation to .env.example * test(tracing): add lifecycle integration tests for trace propagation * fix(tracing): inject trace context into pipeline and batch flow jobs * fix(tracing): add sidecar.execute Node-side span and remove unnecessary comment Wraps PythonDispatcher.run() with a sidecar.execute span on the Node side so traces show the full round-trip (Node span -> Python span). Also removes an obvious comment from logger.ts. |
||
|
|
a4fa3ce2a7 | feat: rewrite install_feature.py for pre-built tar bundles | ||
|
|
5397f9b21c | fix(ai): broaden SSRF pre-scan regex to cover srcset, poster, formaction, @import | ||
|
|
51666cdd5f | feat(tools): 2.0 phase 5 wave 5b - ai pool: ocr-pdf, transcription, background composites (5 tools) (#226) | ||
|
|
fc7c1f850e | feat(tools): 2.0 phase 5 wave 4 - office, ebooks, data, archives (14 tools) (#224) | ||
|
|
2f39e38162 | feat(tools): 2.0 phase 5 wave 2 - pdf depth (21 tools) (#220) | ||
|
|
d647d8ed19 | feat(modality)!: SnapOtter 2.0 phase 3 modality framework: media/doc engines, pool routing, display modes (#218) | ||
|
|
3b8d529b44 |
fix(ci): revert rembg to 2.0.62 (2.0.75 requires numpy>=2.3)
rembg 2.0.75 pulls in numpy>=2.3.0 which conflicts with our pinned numpy==1.26.4 and would break the entire AI dependency chain. The two rembg CVEs (SSRF + path traversal) are in its server/CLI components which we don't use; they're already in the pip-audit ignore list. |
||
|
|
8792080982 |
fix(deps): patch Dependabot security alerts
- Pillow 11.1.0 -> 12.2.0 (6 CVEs: OOB writes, decompression bomb, DoS) - rembg 2.0.62 -> 2.0.75 (SSRF + path traversal in server component) - @fastify/static ^8.1.0 -> ^9.1.3 (path traversal + route guard bypass) - Remove redundant @fastify/static pnpm override - Dismiss stale esbuild alert (already at 0.28.0) - Dismiss file-type alert (16.5.4 is dev-only via @types/potrace) |
||
|
|
7c70c60b9e |
fix(ai): surface actionable fix for libGL.so.1 missing on headless installs
Proxmox LXC and other headless Linux installs lack libgl1, causing all AI tools to show a misleading "install opencv-python-headless" error even though the pip package is already installed. Detect the libGL ImportError and suggest `apt-get install -y libgl1` instead. |
||
|
|
60e3ac2210 |
fix: resolve hardcoded /app paths and loosen mediapipe pin for native installs
Path resolution for the feature manifest and install script was hardcoded to /app/..., which only works inside the Docker container. Native installs (e.g. Proxmox at /opt/snapotter) hit "No such file or directory" errors. Resolve both paths relative to the source file location via import.meta.url so they work regardless of where the project is installed. Also loosen mediapipe==0.10.21 to >=0.10.21 in requirements.txt and requirements-gpu.txt to match the feature manifest. The exact pin has no cp313 wheel, so it fails on Python 3.13 (Debian 13 default). mediapipe 0.10.35 ships py3-none universal wheels that resolve cleanly. Reported-by: MickLesk (community-scripts/ProxmoxVE#14720) |
||
|
|
66e503730d |
fix: resolve 6 production Sentry errors
- Prevent @fastify/static double-registration crash via decorateReply guard - Fix non-ASCII filename header encoding (X-Output-Filename + RFC 5987 Content-Disposition) - Add EACCES error handling to all startup mkdir calls with actionable messages - Add WAL autocheckpoint and journal size limit to prevent unbounded SQLite growth - Fix Python sidecar EPIPE handling to reject pending requests and trigger restart - Ensure Docker entrypoint creates all subdirectories before chown |
||
|
|
80957f6e10 |
feat: improve remove background with edge smoothing, color decontamination, output formats
- Expose birefnet-hr-matting in UI (People/Ultra) and fix model defaults (People/Max now uses birefnet-matting for true alpha matting) - Add output format selector (PNG/WebP/AVIF) with lossless alpha support - Add edge smoothing post-processing (Off/Light/Medium/Strong) via morphological mask refinement to reduce gray halo artifacts - Add color decontamination to remove background color spill from semi-transparent edge pixels - Thread new settings through full stack: frontend -> API schema -> Python sidecar -> Sharp effects pipeline - Add i18n keys for all 21 locales - Add unit tests for new option serialization (3 tests) - Add integration tests for new settings validation (4 tests) |
||
|
|
abd1efb46d |
fix: add retry logic to HuggingFace model downloads (#201)
HuggingFace snapshot_download had no retry logic, causing lama-onnx and codeformer-onnx installs to fail on transient network errors. Direct URL downloads already had 3 retries with exponential backoff -- this adds the same pattern to HF downloads (3 attempts, 10s/20s backoff). |
||
|
|
074c96e8c3 |
fix: enable tiling in Real-ESRGAN to prevent CUDA OOM on 8GB GPUs (#200)
Process images in 512px tiles instead of all at once, drastically reducing peak VRAM usage. If OOM still occurs, retry with 256px tiles after clearing the CUDA cache. Covers both upscale and face enhance. Closes #191 |
||
|
|
e03c6089af |
feat: support downloadFn-based model manifests and improve install error messages
Add support for models defined via downloadFn/args (rembg_session, hf_snapshot) in bundle verification, recovery, and uninstall paths. Previously only path-based models were tracked, so bundles using rembg or HF snapshot downloads appeared broken after install. Also improve pip install error messages with user-friendly hints for common failures (basicsr build issues, OOM, disk full) and add better error context for rembg session download failures. |
||
|
|
dfbc4cfd59 |
chore(release): 1.17.1
Bump version across all workspaces, update changelog, release notes, OpenAPI spec, bug report template, and OpenSSF badge answers. |
||
|
|
0bfbe31fe0 |
chore(deps-dev): bump the dev-deps group with 13 updates (#147)
Bumps the dev-deps group with 13 updates: | Package | From | To | | --- | --- | --- | | [@biomejs/biome](https://github.com/biomejs/biome/tree/HEAD/packages/@biomejs/biome) | `2.4.8` | `2.4.15` | | [@playwright/test](https://github.com/microsoft/playwright) | `1.58.2` | `1.60.0` | | [@semantic-release/github](https://github.com/semantic-release/github) | `12.0.6` | `12.0.8` | | [@semantic-release/release-notes-generator](https://github.com/semantic-release/release-notes-generator) | `14.1.0` | `14.1.1` | | [jsdom](https://github.com/jsdom/jsdom) | `29.0.1` | `29.1.1` | | [turbo](https://github.com/vercel/turborepo) | `2.8.20` | `2.9.14` | | [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node) | `22.19.15` | `22.19.19` | | [@types/pdfkit](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/pdfkit) | `0.17.5` | `0.17.6` | | [@tailwindcss/vite](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-vite) | `4.2.2` | `4.3.0` | | [tailwindcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/tailwindcss) | `4.2.2` | `4.3.0` | | [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) | `6.4.1` | `6.4.2` | | [vitepress-plugin-llms](https://github.com/okineadev/vitepress-plugin-llms) | `1.12.0` | `1.12.2` | | [@tailwindcss/postcss](https://github.com/tailwindlabs/tailwindcss/tree/HEAD/packages/@tailwindcss-postcss) | `4.2.4` | `4.3.0` | Updates `@biomejs/biome` from 2.4.8 to 2.4.15 - [Release notes](https://github.com/biomejs/biome/releases) - [Changelog](https://github.com/biomejs/biome/blob/main/packages/@biomejs/biome/CHANGELOG.md) - [Commits](https://github.com/biomejs/biome/commits/@biomejs/biome@2.4.15/packages/@biomejs/biome) Updates `@playwright/test` from 1.58.2 to 1.60.0 - [Release notes](https://github.com/microsoft/playwright/releases) - [Commits](https://github.com/microsoft/playwright/compare/v1.58.2...v1.60.0) Updates `@semantic-release/github` from 12.0.6 to 12.0.8 - [Release notes](https://github.com/semantic-release/github/releases) - [Commits](https://github.com/semantic-release/github/compare/v12.0.6...v12.0.8) Updates `@semantic-release/release-notes-generator` from 14.1.0 to 14.1.1 - [Release notes](https://github.com/semantic-release/release-notes-generator/releases) - [Commits](https://github.com/semantic-release/release-notes-generator/compare/v14.1.0...v14.1.1) Updates `jsdom` from 29.0.1 to 29.1.1 - [Release notes](https://github.com/jsdom/jsdom/releases) - [Commits](https://github.com/jsdom/jsdom/compare/v29.0.1...v29.1.1) Updates `turbo` from 2.8.20 to 2.9.14 - [Release notes](https://github.com/vercel/turborepo/releases) - [Changelog](https://github.com/vercel/turborepo/blob/main/RELEASE.md) - [Commits](https://github.com/vercel/turborepo/compare/v2.8.20...v2.9.14) Updates `@types/node` from 22.19.15 to 22.19.19 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) Updates `@types/pdfkit` from 0.17.5 to 0.17.6 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/pdfkit) Updates `@tailwindcss/vite` from 4.2.2 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/@tailwindcss-vite) Updates `tailwindcss` from 4.2.2 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/tailwindcss) Updates `vite` from 6.4.1 to 6.4.2 - [Release notes](https://github.com/vitejs/vite/releases) - [Changelog](https://github.com/vitejs/vite/blob/v6.4.2/packages/vite/CHANGELOG.md) - [Commits](https://github.com/vitejs/vite/commits/v6.4.2/packages/vite) Updates `vitepress-plugin-llms` from 1.12.0 to 1.12.2 - [Release notes](https://github.com/okineadev/vitepress-plugin-llms/releases) - [Commits](https://github.com/okineadev/vitepress-plugin-llms/compare/v1.12.0...v1.12.2) Updates `@tailwindcss/postcss` from 4.2.4 to 4.3.0 - [Release notes](https://github.com/tailwindlabs/tailwindcss/releases) - [Changelog](https://github.com/tailwindlabs/tailwindcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/tailwindlabs/tailwindcss/commits/v4.3.0/packages/@tailwindcss-postcss) --- updated-dependencies: - dependency-name: "@biomejs/biome" dependency-version: 2.4.15 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@playwright/test" dependency-version: 1.60.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: "@semantic-release/github" dependency-version: 12.0.8 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@semantic-release/release-notes-generator" dependency-version: 14.1.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: jsdom dependency-version: 29.1.1 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: turbo dependency-version: 2.9.14 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: "@types/node" dependency-version: 22.19.19 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@types/pdfkit" dependency-version: 0.17.6 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@tailwindcss/vite" dependency-version: 4.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: tailwindcss dependency-version: 4.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps - dependency-name: vite dependency-version: 6.4.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: vitepress-plugin-llms dependency-version: 1.12.2 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: dev-deps - dependency-name: "@tailwindcss/postcss" dependency-version: 4.3.0 dependency-type: direct:development update-type: version-update:semver-minor dependency-group: dev-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
0dadca3b99 |
chore(deps): bump the production-deps group across 1 directory with 17 updates (#144)
Bumps the production-deps group with 17 updates in the / directory: | Package | From | To | | --- | --- | --- | | [@scalar/fastify-api-reference](https://github.com/scalar/scalar/tree/HEAD/integrations/fastify) | `1.49.5` | `1.57.2` | | [@sentry/node](https://github.com/getsentry/sentry-javascript) | `10.49.0` | `10.53.1` | | [fflate](https://github.com/101arrowz/fflate) | `0.8.2` | `0.8.3` | | [p-queue](https://github.com/sindresorhus/p-queue) | `9.1.0` | `9.3.0` | | [posthog-node](https://github.com/PostHog/posthog-js/tree/HEAD/packages/node) | `5.29.5` | `5.34.2` | | [sharp](https://github.com/lovell/sharp) | `0.33.5` | `0.34.5` | | [tsx](https://github.com/privatenumber/tsx) | `4.21.0` | `4.22.1` | | [zxing-wasm](https://github.com/Sec-ant/zxing-wasm) | `3.0.2` | `3.0.3` | | [react](https://github.com/facebook/react/tree/HEAD/packages/react) | `19.2.4` | `19.2.6` | | [react-dom](https://github.com/facebook/react/tree/HEAD/packages/react-dom) | `19.2.4` | `19.2.6` | | [lucide-react](https://github.com/lucide-icons/lucide/tree/HEAD/packages/lucide-react) | `0.469.0` | `0.577.0` | | [@sentry/react](https://github.com/getsentry/sentry-javascript) | `10.49.0` | `10.53.1` | | [posthog-js](https://github.com/PostHog/posthog-js) | `1.370.0` | `1.373.5` | | [react-colorful](https://github.com/omgovich/react-colorful) | `5.6.1` | `5.7.0` | | [react-konva](https://github.com/konvajs/react-konva) | `19.2.3` | `19.2.4` | | [react-router-dom](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router-dom) | `7.13.1` | `7.15.1` | | [zustand](https://github.com/pmndrs/zustand) | `5.0.12` | `5.0.13` | Updates `@scalar/fastify-api-reference` from 1.49.5 to 1.57.2 - [Release notes](https://github.com/scalar/scalar/releases) - [Changelog](https://github.com/scalar/scalar/blob/main/integrations/fastify/CHANGELOG.md) - [Commits](https://github.com/scalar/scalar/commits/HEAD/integrations/fastify) Updates `@sentry/node` from 10.49.0 to 10.53.1 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.49.0...10.53.1) Updates `fflate` from 0.8.2 to 0.8.3 - [Release notes](https://github.com/101arrowz/fflate/releases) - [Changelog](https://github.com/101arrowz/fflate/blob/master/CHANGELOG.md) - [Commits](https://github.com/101arrowz/fflate/compare/v0.8.2...v0.8.3) Updates `p-queue` from 9.1.0 to 9.3.0 - [Release notes](https://github.com/sindresorhus/p-queue/releases) - [Commits](https://github.com/sindresorhus/p-queue/compare/v9.1.0...v9.3.0) Updates `posthog-node` from 5.29.5 to 5.34.2 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/packages/node/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/commits/posthog-node@5.34.2/packages/node) Updates `sharp` from 0.33.5 to 0.34.5 - [Release notes](https://github.com/lovell/sharp/releases) - [Commits](https://github.com/lovell/sharp/compare/v0.33.5...v0.34.5) Updates `tsx` from 4.21.0 to 4.22.1 - [Release notes](https://github.com/privatenumber/tsx/releases) - [Changelog](https://github.com/privatenumber/tsx/blob/master/release.config.cjs) - [Commits](https://github.com/privatenumber/tsx/compare/v4.21.0...v4.22.1) Updates `zxing-wasm` from 3.0.2 to 3.0.3 - [Release notes](https://github.com/Sec-ant/zxing-wasm/releases) - [Changelog](https://github.com/Sec-ant/zxing-wasm/blob/main/CHANGELOG.md) - [Commits](https://github.com/Sec-ant/zxing-wasm/compare/v3.0.2...v3.0.3) Updates `react` from 19.2.4 to 19.2.6 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.6/packages/react) Updates `react-dom` from 19.2.4 to 19.2.6 - [Release notes](https://github.com/facebook/react/releases) - [Changelog](https://github.com/facebook/react/blob/main/CHANGELOG.md) - [Commits](https://github.com/facebook/react/commits/v19.2.6/packages/react-dom) Updates `lucide-react` from 0.469.0 to 0.577.0 - [Release notes](https://github.com/lucide-icons/lucide/releases) - [Commits](https://github.com/lucide-icons/lucide/commits/0.577.0/packages/lucide-react) Updates `@sentry/react` from 10.49.0 to 10.53.1 - [Release notes](https://github.com/getsentry/sentry-javascript/releases) - [Changelog](https://github.com/getsentry/sentry-javascript/blob/develop/CHANGELOG.md) - [Commits](https://github.com/getsentry/sentry-javascript/compare/10.49.0...10.53.1) Updates `posthog-js` from 1.370.0 to 1.373.5 - [Release notes](https://github.com/PostHog/posthog-js/releases) - [Changelog](https://github.com/PostHog/posthog-js/blob/main/CHANGELOG.md) - [Commits](https://github.com/PostHog/posthog-js/compare/posthog-js@1.370.0...posthog-js@1.373.5) Updates `react-colorful` from 5.6.1 to 5.7.0 - [Release notes](https://github.com/omgovich/react-colorful/releases) - [Changelog](https://github.com/omgovich/react-colorful/blob/master/CHANGELOG.md) - [Commits](https://github.com/omgovich/react-colorful/commits/5.7.0) Updates `react-konva` from 19.2.3 to 19.2.4 - [Release notes](https://github.com/konvajs/react-konva/releases) - [Commits](https://github.com/konvajs/react-konva/compare/v19.2.3...v19.2.4) Updates `react-router-dom` from 7.13.1 to 7.15.1 - [Release notes](https://github.com/remix-run/react-router/releases) - [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router-dom/CHANGELOG.md) - [Commits](https://github.com/remix-run/react-router/commits/react-router-dom@7.15.1/packages/react-router-dom) Updates `zustand` from 5.0.12 to 5.0.13 - [Release notes](https://github.com/pmndrs/zustand/releases) - [Commits](https://github.com/pmndrs/zustand/compare/v5.0.12...v5.0.13) --- updated-dependencies: - dependency-name: "@scalar/fastify-api-reference" dependency-version: 1.57.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/node" dependency-version: 10.53.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: "@sentry/react" dependency-version: 10.53.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: fflate dependency-version: 0.8.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: lucide-react dependency-version: 0.577.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: p-queue dependency-version: 9.3.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: posthog-js dependency-version: 1.373.5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: posthog-node dependency-version: 5.34.2 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: react dependency-version: 19.2.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: react-colorful dependency-version: 5.7.0 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: react-dom dependency-version: 19.2.6 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: react-konva dependency-version: 19.2.4 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: react-router-dom dependency-version: 7.15.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: sharp dependency-version: 0.34.5 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: tsx dependency-version: 4.22.1 dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-deps - dependency-name: zustand dependency-version: 5.0.13 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps - dependency-name: zxing-wasm dependency-version: 3.0.3 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-deps ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
36431bce48 |
fix: improve AI feature install error handling and resource limits
Handle OOM kills (exit code 137) with actionable memory guidance, filter ANSI/progress noise from error output, add --no-cache-dir to pip installs, reduce download concurrency to 2, and bump default container memory from 4g to 6g. |
||
|
|
3e06d68f4f |
chore: bump version to 1.17.0 and update docs for release
- Bump all workspace package versions to 1.17.0 - Update APP_VERSION constant and OpenAPI spec - Update AI tool count from 15 to 16 across docs and i18n - Update tool table with AI Canvas Expand, Meme Generator, Beautify - Add image editor, OIDC, and 20 languages to README features - Add release notes for v1.17.0 - Add JSON-LD structured data and SEO improvements to landing/docs |
||
|
|
212ef653b5 |
fix: resolve 5 Sentry production errors (668 total events)
- Filter known client-error noise (rate limit, empty body, unsupported media type, content-length mismatch, premature close) from Sentry via beforeSend to stop 644 events of non-actionable noise - Sanitize x-output-filename header to prevent TypeError on non-ASCII filenames in optimize-for-web preview (23 events) - Handle EPIPE on Python dispatcher stdin write with graceful fallback to per-request spawning instead of crashing (NODE-W) - Map EACCES on storage directory/file write to proper 503 status instead of generic 500 (NODE-P, 3 events) |
||
|
|
19a607454a |
fix: improve GPU detection diagnostics and fallback for container environments
The GPU detection in gpu.py had two issues preventing GPU usage in containers (especially rootless podman with CDI): 1. When torch was installed but torch.cuda.is_available() returned False, the function returned immediately without trying the ONNX Runtime + nvidia-smi fallback. This meant a CPU-only torch build (installed before GPU was available) would block all GPU detection, even for ONNX-based tools. 2. The failure logged a generic "torch loaded but CUDA not available" with no diagnostic information, making it impossible to debug whether the issue was a CPU-only build, missing libraries, or device permissions. The fix restructures gpu_available() into three detection tiers (torch -> ONNX Runtime -> nvidia-smi) that always fall through on failure. When torch CUDA fails, it now checks torch.version.cuda to distinguish CPU-only builds from CUDA builds that can't access the GPU, and logs LD_LIBRARY_PATH, torch.cuda.init() errors, and nvidia-smi results. Also fixes two env var passthrough bugs in buildMinimalEnv(): - SNAPOTTER_GPU was never passed to the Python subprocess, so the user-facing GPU override env var had no effect - MODELS_DIR was a dead entry (never set as env var); replaced with MODELS_PATH which the Dockerfile sets and Python scripts read Closes #134 |
||
|
|
4e64ee2779 |
fix(security): comprehensive security audit and hardening
Auth: login rate limit 30/min (was 500), global rate limit 1000/min (was unlimited), password/username max lengths on all Zod schemas, session invalidation on role change, API key legacy scan bounded to 100 keys. SVG: hardened regex sanitizer with CDATA stripping, XML entity decoding, set/animate/iframe/embed blocking, comprehensive data: URI blocking, use element external href blocking. 11 attack payload fixtures added. SSRF: fixed DNS rebinding TOCTOU by pinning resolved IPs via custom HTTP/HTTPS agents. Added 6to4 and NAT64 to blocked IPv6 ranges. Docker: capability dropping (cap_drop ALL + minimal cap_add), resource limits (4g/8g mem, 512/1024 pids), healthcheck timeout, password removed from startup banner, default password warning comments. Network: CSP and HSTS applied in all environments (not just production), stack traces removed from all error responses, internal paths stripped from error details, per-route rate limits on uploads (60/min) and URL fetches (200/hour). Files: exclusive temp file creation (O_EXCL), disk space circuit breaker, per-user storage quotas, settings payload 64KB size guard. Python sidecar: script name allowlist in dispatcher, minimal environment for subprocess spawns. Dependencies: fixed 6 production CVEs (drizzle-orm, fastify, fast-uri, @fastify/static, next, archiver/lodash). Pinned all GitHub Actions to SHA hashes. 114 security tests added. Full OWASP Top 10 penetration test matrix verified against production Docker container (30/30 pass after hardening). |