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.
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.
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)
- 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)
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).
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
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.
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.
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
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).
- 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
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.
- 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)
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.
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
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
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.
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.
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)
- 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
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
- 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
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
- 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)
- 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
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.
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").
When the Python dispatcher crashes and bridge.ts retries via per-request
spawning, the shim from dispatcher.py isn't loaded. basicsr then fails
importing torchvision.transforms.functional_tensor (removed in v0.17).
Adding the shim directly to both scripts ensures they work regardless
of whether they run through the dispatcher or standalone.
- Add safe_onnx_session() to gpu.py with graceful CUDA EP → CPU fallback
- Replace bare ort.InferenceSession() calls across colorize, restore, inpaint, remove_bg
- Add libcublas-12-6 to production Dockerfile for ONNX Runtime CUDA EP
- Add skipIfFeatureNotInstalled guards to remove-bg, blur-faces, smart-crop, ocr, noise-removal e2e specs
- Add AI tool install prompt detection in tools-all.spec.ts
- Add smart-crop to PYTHON_SIDECAR_TOOLS so frontend shows install prompt correctly
- Create Dockerfile.test.dockerignore to include tests/ in test image builds
- Add libheif-examples and exiftool to Dockerfile.test for HEIC and metadata tests
- Regenerate visual regression baselines for Docker/Linux and skip on non-Docker platforms
- Add 8 new E2E specs for AI tools (upscale, enhance-faces, colorize,
restore-photo, erase-object, smart-crop, passport-photo, red-eye-removal)
closing all HIGH/MEDIUM coverage gaps from the test matrix audit
- Fix ensureAiDirs() crash on non-Docker environments by gating on
isDockerEnvironment() — prevents ENOENT when /data doesn't exist
- Bump torch 2.6.0→2.7.0 and torchvision 0.21.0→0.22.0 in feature
manifest for broader Python version compatibility
- Add Python 3.14 version guard warning in install_feature.py
- Remove duplicate torchvision shims from upscale.py and enhance_faces.py
(dispatcher.py already handles this at startup)
- Remove orphaned tools.batch i18n key and dead pipeline-builder filter
- Regenerate 4 visual regression baselines for current UI state
- Add data-testid to passport-photo generate button for E2E testability
The torchvision compatibility shim for basicsr 1.4.2 was missing the
parent-package binding and only proxied a single attribute, causing
upscale and enhance-faces to fail at import time. The fix adds a
__getattr__ proxy for all attributes, binds the shim to the parent
package, and installs it in the dispatcher at startup for defense-in-depth.
Also removes unused anyInstalling variable, redundant `as any` cast,
and applies Biome formatting fixes across the codebase.
- Fix NameError in restore.py: face enhancement loop used undefined
variable `i`, now uses enumerate()
- Fix gpu.py ONNX fallback: previous smoke-test with empty bytes
always raised, making GPU detection unreachable via the ONNX path.
Now uses nvidia-smi hardware check after confirming CUDA EP is
compiled in — works on Linux, Windows, and gracefully fails on macOS
- Fix cpu_fallback_packages stripping CUDA-specific index URLs when
replacing paddlepaddle-gpu with paddlepaddle for CPU-only systems
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Pin torch==2.6.0+cu126 and torchvision==0.21.0+cu126 in feature
manifest to prevent NCCL symbol mismatch on CUDA 12.6 base images
- Move lpips after torch in install order to prevent wrong version
resolution from PyPI
- Add einops to upscale-enhance common deps (required by SCUNet)
- Update cpu_fallback_packages to handle multi-package CUDA torch
entries on amd64 without GPU
- Fix gpu.py ONNX CUDA detection: replace hardcoded .so path with
cross-platform session smoke-test
- Fix os.dup(1) crashes on Windows in upscale, enhance_faces, and
noise_removal by wrapping in try/except with sys.stderr fallback
- Guard top-level numpy/cv2 imports in colorize.py and restore.py
with helpful error messages
- Add weights_only=False fallback for torch.load in noise_removal
- Fix integration tests to accept 501 for uninstalled AI features
and 422 for missing system tools (exiftool, libheif)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add tool-specific suffix to output filenames so downloads don't overwrite originals (batch & single-tool routes)
- Skip deleting shared models when uninstalling a bundle that shares models with another installed bundle
- Auto-detect NVIDIA GPU and swap GPU-only pip packages (onnxruntime-gpu, paddlepaddle-gpu) for CPU equivalents
- Refactor docker-compose with YAML anchors and explicit cpu/gpu profiles
- Add libheif-plugin-x265 to Dockerfile
- Fix install-all queue logic to handle concurrent individual installs and clear stale errors
- Unify playwright docker config to use same test dir with API_URL env var
- Fix flaky e2e selectors, rename Strip Metadata → Remove Metadata, handle collage custom dropzone, improve fallback test image generation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>