Commit Graph
112 Commits
Author SHA1 Message Date
SnapOtterandGitHub 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.
2026-06-21 23:24:10 +08:00
SnapOtter 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.
2026-06-21 12:52:27 +08:00
SnapOtter 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.
2026-06-21 11:59:26 +08:00
SnapOtterandGitHub 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.
2026-06-19 18:46:52 +08:00
SnapOtterandGitHub 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.
2026-06-19 18:34:33 +08:00
SnapOtter 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.
2026-06-17 15:54:35 +08:00
SnapOtter 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.
2026-06-17 14:28:41 +08:00
SnapOtterandGitHub 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.
2026-06-16 21:49:53 +08:00
SnapOtterandGitHub 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.
2026-06-16 15:48:07 +08:00
SnapOtterandGitHub 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/.
2026-06-15 22:26:24 +08:00
SnapOtterandGitHub 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.
2026-06-15 12:53:06 +08:00
SnapOtter a4fa3ce2a7 feat: rewrite install_feature.py for pre-built tar bundles 2026-06-13 16:37:00 +08:00
SnapOtter 5397f9b21c fix(ai): broaden SSRF pre-scan regex to cover srcset, poster, formaction, @import 2026-06-13 10:36:12 +08:00
SnapOtter 51666cdd5f feat(tools): 2.0 phase 5 wave 5b - ai pool: ocr-pdf, transcription, background composites (5 tools) (#226) 2026-06-13 10:19:47 +08:00
SnapOtter fc7c1f850e feat(tools): 2.0 phase 5 wave 4 - office, ebooks, data, archives (14 tools) (#224) 2026-06-13 10:19:11 +08:00
SnapOtter 2f39e38162 feat(tools): 2.0 phase 5 wave 2 - pdf depth (21 tools) (#220) 2026-06-13 10:18:55 +08:00
SnapOtter d647d8ed19 feat(modality)!: SnapOtter 2.0 phase 3 modality framework: media/doc engines, pool routing, display modes (#218) 2026-06-13 10:18:39 +08:00
SnapOtter 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.
2026-06-10 21:21:39 +08:00
SnapOtter 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)
2026-06-10 19:08:08 +08:00
SnapOtter 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.
2026-06-10 18:59:30 +08:00
SnapOtter 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)
2026-06-09 23:18:02 +08:00
SnapOtter 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)
2026-06-05 23:05:25 +08:00
SnapOtterandGitHub 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).
2026-06-05 17:53:34 +08:00
SnapOtterandGitHub 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
2026-06-05 17:29:56 +08:00
SnapOtter 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.
2026-06-04 22:27:44 +08:00
SnapOtter 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.
2026-05-18 10:19:43 +08:00
SnapOtter 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
2026-05-14 23:17:21 +08:00
SnapOtter 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).
2026-05-13 21:33:50 +08:00
SnapOtter a03d4f34ff fix(ai): tune scratch detection thresholds and remove erosive open step
- Two-gate threshold: Otsu >= 60 uses Otsu; 40-59 uses fixed 100
  (catches strong scratches on borderline images)
- Remove morphological OPEN after component filtering: it was eroding
  thin scratch lines that were correctly detected
- Lower Otsu gate from 60 to 40 to avoid false-negating borderline images
2026-05-13 17:17:32 +08:00
SnapOtter 1b81fc61f1 fix(ai): face guard for small faces, remove mode system, add colorizeStrength 2026-05-13 17:03:57 +08:00
SnapOtter e429f6fd6a fix(ai): tiled LaMa inpainting at native resolution 2026-05-13 17:02:37 +08:00
SnapOtter a6c5016c35 fix(ai): rewrite scratch detection with 8-angle Otsu and component filtering 2026-05-13 17:01:50 +08:00
SnapOtter 55e544317c feat(ai): parameterize outpaint pipeline with quality tier support
Add TIER_PARAMS dict with fast/balanced/high presets controlling band
size, mask dilation, seam strip width, and Telea pre-inpainting. Parse
tier from sys.argv[7] with balanced fallback. Conditional Telea and
seam refinement steps skip cleanly for fast tier. Progressive outpaint
now accepts band_size and progress bounds for tier-appropriate scaling.
2026-05-13 15:45:19 +08:00
SnapOtter 3760885342 fix: restore-photo colorize hang and AVIF decode failures
- Fix dispatcher pipe deadlock: drain stdout pipe in a background thread
  to prevent blocking when ONNX runtime output exceeds 64KB pipe buffer
- Add 5-minute SSE stall timeout so the UI shows an error instead of
  hanging forever when async AI processing stalls
- Guard CPU colorization: skip for images >2MP on CPU and when DDColor
  model is not installed, with clear user-facing messages
- Add AVIF decode fallback via ImageMagick for bitstream variants that
  Sharp's bundled libheif cannot decode (affects all tools)
2026-05-13 15:42:24 +08:00
SnapOtter 347b8ad530 feat: register content-aware-crop in shared package 2026-05-11 21:08:30 +08:00
SnapOtter a329dac004 fix: guard against division-by-zero in HR-matting predict and register session in installer
The BiRefNetHRMattingSession.predict normalization crashes when all
pixels share the same value (ma == mi). Use a guarded denominator so
uniform-alpha inputs produce a zero mask instead of a NaN explosion.

Also adds _register_birefnet_hr_matting() to install_feature.py so the
HR-matting model can be downloaded during feature installation, matching
the existing registration in remove_bg.py.
2026-05-05 22:54:43 +08:00
SnapOtter a44f6c7592 feat: add BiRefNet HR-matting model support for transparency fixer 2026-05-05 22:49:41 +08:00
SnapOtter 67fa302376 fix: verify CUDAExecutionProvider in onnxruntime before returning CUDA providers
gpu.onnx_providers() trusted gpu_available() which returns True via
torch.cuda without checking whether onnxruntime actually has
CUDAExecutionProvider compiled in. When onnxruntime (CPU-only) is
installed, this caused silent fallback to CPU in every ONNX-based tool.

Now verifies onnxruntime.get_available_providers() directly and emits a
diagnostic warning when torch sees CUDA but onnxruntime does not.

Closes #104
2026-04-30 18:43:47 +08:00
SnapOtter b00ef20667 fix: close SVG sanitization gap on upload routes and fix OCR/extension bugs
Security:
- Apply sanitizeSvg() to all file upload routes (files.ts, user-files.ts)
  preventing SSRF and script injection via SVG uploads to file library

Functional:
- Handle PaddleOCR-VL 1.5 markdown_texts output format in ocr.py
- Add empty-text fallback in OCR tier chain (ocr.ts) so higher tiers
  that return empty text fall back to the next tier automatically
- Fix SVG->PNG filename extension mismatch in tool-factory.ts so
  download endpoint serves correct Content-Type
- Report original upload size (not decoded size) in API response

Test infrastructure:
- Move Playwright auth state from test-results/ to .playwright/ to
  prevent mid-run cleanup deleting auth files
- Fix auth.setup.ts navigation race with waitForURL
- Fix gui-batch.spec.ts regex matching "Presets" instead of "reset"
- Fix pipeline-advanced.spec.ts crop bounds and resize assertions
- Broaden pipeline cleanup to include all E2E-prefixed pipelines
2026-04-30 16:11:33 +08:00
SnapOtter c6c78dc76f fix: prevent OOM kills during background removal on CPU
Skip alpha matting on CPU (pymatting's sparse matrices are the main
memory hog), auto-downscale images above 2048px before sending to
rembg, and retry with the lighter u2net model when OOM is detected.
2026-04-28 02:20:48 +08:00
SnapOtter 4f81b29fbc fix: AI feature install failures — missing rembg session and Fastify 415 (#102, #103)
Register custom BiRefNet-matting ONNX session in install_feature.py so
rembg.new_session("birefnet-matting") no longer raises ValueError during
on-demand installs. The session was already registered in remove_bg.py
(runtime) and download_models.py (build-time) but was missed in the
install path, causing background-removal bundle installs to always fail.

Send JSON body on install/uninstall POST requests to avoid Fastify 5's
strict content-type parser rejecting body-less POSTs with 415.

Fix error message extraction to preserve structured {"error": ...} JSON
from the Python script and filter out pthread_setaffinity_np noise.
2026-04-27 02:38:17 +08:00
SnapOtter 4486cf926f fix: revert Pillow/rembg upgrades that break dependency tree
Pillow 12.x conflicts with pinned numpy 1.26.4, rembg, realesrgan,
and mediapipe. Revert to working 11.1.0 pins and ignore the CVEs
in pip-audit instead — they require a coordinated major version
upgrade across the entire ML stack (Pillow, numpy, torch, basicsr).

Ignored CVEs:
- CVE-2024-27763 (basicsr, no fix available)
- CVE-2026-40086 (rembg, fix needs Pillow 12)
- CVE-2026-25990 (Pillow, fix is 12.1.1)
- CVE-2026-40192 (Pillow, fix is 12.2.0)
2026-04-27 01:23:25 +08:00
SnapOtter b926e5d1be fix: CI failures — QR test timeout and Python dependency CVEs
- Increase QR generate max-size test timeout to 120s (10000x10000
  PNG generation exceeds 30s default on CI runners)
- Update Pillow 11.1.0 → >=12.2.0 (CVE-2026-25990, CVE-2026-40192)
- Update rembg 2.0.62 → >=2.0.75 (CVE-2026-40086)
- Update opencv-python-headless to flexible range >=4.10,<4.12
- Ignore CVE-2024-27763 in pip-audit (basicsr transitive dep from
  realesrgan, no fix available upstream)
- Align requirements-gpu.txt and Dockerfile with same versions
2026-04-27 01:19:25 +08:00
SnapOtter dee9452c48 fix: format preservation, dispatcher stability, and health reporting
Closes #17, #18, #19, #31, #32, #33, #34

Format preservation (#17, #18, #19):
- Add resolveOutputFormat to rotate, resize, text-overlay, watermark-text,
  border, replace-color, blur-faces, upscale, erase-object, restore-photo
- Alpha-aware fallback: border with corner radius/shadow and replace-color
  with makeTransparent fall back to PNG for non-alpha formats (JPEG)
- Python sidecar tools (blur-faces, upscale, erase-object) now convert
  PNG output back to input format, matching restore-photo/colorize pattern
- Upscale and erase-object default to "auto" format detection instead of PNG

Dispatcher stability (#31, #32):
- Add gc.collect() and torch.cuda.empty_cache() after each dispatcher request
- Add configurable max_requests (default 50) for periodic dispatcher restart
- Add exponential backoff to dispatcher crash recovery in bridge.ts
- Circuit breaker: 5 crashes within 60s permanently disables dispatcher
- Reset crash counter on successful dispatcher startup

Health & security (#33, #34):
- Export getDispatcherStatus() from @snapotter/ai with running/ready/failed/
  gpu/pid/consecutiveCrashes fields
- Admin health endpoint now includes full dispatcher status
- Add pip-audit job to CI workflow for Python dependency scanning
2026-04-26 03:22:26 +08:00
SnapOtter 8bc8b18f90 fix: version constant and CPU torch install for Docker release
- Bump APP_VERSION to 1.15.11 (was hardcoded at 1.15.9, causing
  health endpoint to report wrong version in Docker images)
- Fix cpu_fallback_packages() splitting --index-url into separate
  pip install arguments, breaking torch install on CPU-only amd64
2026-04-25 08:49:31 +08:00
SnapOtter bf0307d87d fix: QA sweep — 7 bugs fixed, 17 test corrections
Code fixes:
- Sidebar state bleed: reset file store on HomePage mount
- restore-photo: raise error instead of silently skipping colorize
  when DDColor model missing
- PaddleOCR OOM: cap input images to 2048px before OCR inference
- Torch CPU optimization: use --index-url .../whl/cpu on CPU nodes

Test fixes:
- upscale: add exact:true to scale factor button locators
- smart-crop: add exact:true to "Pad to square" locator
- colorize: use regex for model button names (Best/Balanced/Fast)
- enhance-faces: use .first() for ambiguous percentage display
- passport-photo: fix DPI locator, .or() compound, generate fallback
- people: update maxUsers assertions for unlimited (0) default
- automate: "Save Pipeline" → "Save" matching actual button text
- tools.test: add resize to Sharp mock chain for OCR tests
2026-04-25 07:23:58 +08:00
SnapOtter e3259d163a fix: PaddleOCR CPU crash, content-aware-resize limits, barcode fixtures
- Add enable_mkldnn=False to PaddleOCR constructor to bypass PaddlePaddle
  3.3+ OneDNN/PIR crash on CPU-only systems
- Add 25MP and 75% max-reduction guard to seam carving with clear error
  messages instead of silent timeout/crash
- Replace barcode/QR AVIF test fixtures with actual scannable codes
  (old fixtures did not contain real barcodes)
2026-04-24 23:58:06 +08:00
SnapOtter 0309e0f680 chore: deploy to Cloudflare Pages and update branding
- Add Cloudflare Pages deployment for landing page (snapotter.com) and
  docs (docs.snapotter.com)
- Create deploy-landing.yml and update deploy-docs.yml workflows
- Update CI to ignore apps/landing/** paths
- Fix logo transparency (remove white background) across all apps
- Recreate social-preview.png with SnapOtter branding
- Update all docs URLs from GitHub Pages to docs.snapotter.com
- Update VitePress config: light theme default, fix llms.txt paths
- Add .vitepress/cache/ and .env.* to gitignore
2026-04-24 18:06:29 +08:00
AshimandGitHub 0b8e0bf774 fix: auto-fallback from CodeFormer to GFPGAN in face enhancement (#87)
When model is set to "auto", CodeFormer failure previously threw an
error telling users to manually switch to GFPGAN. Now it falls back
to GFPGAN automatically, matching the graceful degradation pattern
already used in OCR.
2026-04-21 23:51:11 +08:00
ashim-hq 77a60b24cc fix: resolve 5 bugs found during comprehensive tool testing
1. split batch 404: register split tool in batch registry via
   registerToolProcessFn() so /api/v1/tools/split/batch works

2. CodeFormer crash: inference_app() expects a file path, not a numpy
   array. Save to temp file before calling, read result back.

3. OCR fallback chain: fix case-sensitive "Segmentation fault" match
   that prevented PaddleOCR crash from triggering Tesseract fallback.
   Also add "process crashed" check. Upgrade ARM paddlepaddle to >=3.2.1.

4. blur-faces large images: downscale to 1920px max before MediaPipe
   detection, scale coordinates back. Also add rotation retry for
   portrait-oriented images where BlazeFace misses faces. Applied to
   detect_faces.py, enhance_faces.py, and restore.py.

5. color-adjustments tool ID: fix mismatch in index.ts registration
   array (was "color-adjustments", should be "adjust-colors").
2026-04-21 22:25:06 +08:00