Sharp can read HEIF container headers (metadata succeeds) but cannot
decode HEVC pixels (stats fails with "No decoding plugin installed").
Unconditionally pre-decode HEIF files via heif-dec CLI before any Sharp
operations, matching the createToolRoute factory behavior.
- 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)
- fix(db): migration 0012 column order mismatch causing NOT NULL
constraint failure on existing databases; use explicit column
mapping instead of SELECT *
- fix(db): disable FK checks during migrations to allow SQLite
table-recreation pattern (DROP + RENAME)
- fix(security): filter cookie_secret and instance_id from settings
API response for non-admin users
- fix(lint): resolve all 7 API lint warnings (noParameterAssign,
noImplicitAnyLet) in compose, image-enhancement, and workspace
- fix(docs): correct permission count from 16 to 14 in CLAUDE.md
- fix(e2e): resolve 44 Playwright test failures across 8 spec files
including locator specificity, compress mode defaults, format count,
restore-photo UI drift, stitch image count, GIF animated fixtures,
submit button timing, and processing timeouts
Tests expected 422 for corrupted image data, but the API correctly
returns 400 since corruption is caught during validation (unrecognized
format), not during processing. Also fix watermark-image route passing
a hardcoded "watermark" string instead of the actual uploaded filename
to validateImageBuffer, which broke SVGZ detection.
validateImageBuffer catches corrupt image data before processing
reaches the tool handler, so the correct status code is 400 (bad
request) rather than 422 (processing failure). Also fix SVGZ
watermark validation by returning early for compressed SVG (Sharp
cannot read gzip-compressed SVGZ directly) and passing the actual
watermark filename to validateImageBuffer for correct format
detection.
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).
The transparency-fixer now directly detects the baked-in checkerboard
pattern using per-pixel chroma analysis instead of BiRefNet AI matting.
Achromatic pixels in the gray range are classified as background
(transparent), chromatic pixels as foreground (opaque), with smooth
transitions at anti-aliased edges.
- No longer requires Python sidecar or background-removal bundle
- Watermark removal uses Sharp median(5) filter pre-processing
- Moved tool from "ai" to "utilities" category
- Removed from PYTHON_SIDECAR_TOOLS and background-removal enablesTools
- Near-instant processing (pure Sharp, no model inference)
The luminance anomaly detection + LaMa inpainting approach failed because
watermark signal on the matted foreground was too weak (10-15 units vs
threshold of 25). Median filter with kernel=5 effectively removes
semi-transparent watermark text while preserving the stamp structure.
Pipeline is now: median filter (if toggle on) -> BiRefNet matting -> defringe.
No longer requires object-eraser-colorize bundle for watermark removal.
Add tier enum (fast/balanced/high) with balanced default to the Zod
settings schema. Pass tier through to outpaint options in both the HTTP
route and the pipeline/batch registry. Fix log message to say
"Starting AI canvas expand" and include tier in structured log fields.
- 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)
HDR and EXR files decoded by ImageMagick can produce 16-bit PNG buffers.
Sharp's CLAHE operation (hist_local) requires VIPS_FORMAT_UCHAR (8-bit).
Check the buffer depth and convert to 8-bit sRGB before processing.
Sharp's TIFF encoder silently strips the alpha channel, flattening
transparency against black. This caused border (corner radius, shadow),
beautify, and replace-color tools to produce wrong output for TIFF
inputs when the operation needs transparency. Remove TIFF from
ALPHA_FORMATS so these tools fall back to PNG output.
The find-duplicates tool failed entirely when any uploaded file couldn't
be processed, returning "Duplicate detection failed" or a format-specific
error that aborted the whole batch. With mixed-format uploads (77 files),
this made the tool unusable.
- Skip unprocessable files instead of aborting; return skippedFiles in response
- Switch from fetch() to XHR with upload progress tracking (Uploading X%)
- Add Vite proxy timeout config (5min) to prevent connection drops on large uploads
- Add "Download Grouped" button: ZIP with each duplicate group in its own folder
- Add collapsible skipped-files section in the results UI
- Add 3 integration tests for skip behavior (43 total)
Custom-route tools (split, compare, collage, find-duplicates, etc.) only
handled HEIC via ensureSharpCompat, failing on BMP, PSD, RAW, TGA, EXR,
HDR, JXL, and other formats Sharp cannot decode natively. Added the full
decode pipeline from createToolRoute to all 16 affected routes: format
validation via validateImageBuffer, HEIC decoding with actionable error
messages, CLI-based exotic format decoding with nested fallback, and SVG
sanitization to prevent XXE/SSRF/script injection.
The vectorize tool had a custom processing flow that bypassed the
standard useToolProcessor hook -- no progress indication, no server-side
batch, and the Download All ZIP relied on a client-side sequential loop.
Backend: extract core logic into vectorizeBuffer(), register via
registerToolProcessFn() so the /batch endpoint works with p-queue
concurrency and SSE progress events.
Frontend: replace custom fetch loop with useToolProcessor hook and
ProgressCard, giving upload progress, per-file batch status, and
automatic Download All ZIP via the existing tool-page infrastructure.
Also set image/svg+xml MIME type on SVG blobs during batch ZIP
extraction to ensure reliable rendering in <img> tags across browsers.
The stitch route only decoded HEIC via ensureSharpCompat, so exotic
formats (DNG, PSD, TGA, BMP, JXL, HDR, QOI, DDS, ICO, JP2, DPX, etc.)
crashed Sharp at metadata read time, causing "Stitch creation failed".
Add the full CLI decode pipeline (decodeToSharpCompat) matching the
tool-factory pattern, plus SVG sanitization with proper error handling
for each format category.
Replace fetch() with XMLHttpRequest in the frontend to surface upload
progress via onprogress events, and add a progress bar that shows upload
percentage then pulses during server-side stitching.
The X-File-Results header contained raw JSON with non-ASCII characters
from filenames (Chinese, Japanese, etc.), violating RFC 7230. Node.js
threw ERR_INVALID_CHAR on writeHead(). Fixed by wrapping the JSON in
encodeURIComponent() on the backend and decodeURIComponent() on the
frontend, ensuring only ASCII goes into the header while preserving
the original filenames after decoding.
Closes#133
Sharp can read TIFF-based RAW files (DNG, CR2, NEF) directly for
metadata without requiring ImageMagick/darktable to fully decode them.
Try Sharp on the raw buffer first; only fall back to the decode pipeline
for formats Sharp cannot open (PSD, ICO, TGA). For RAW files, enrich
with ExifTool to get real sensor dimensions instead of thumbnail size.
Error from a failed image (e.g. DNG) persisted when navigating to a
previously cached image because the cache path skipped setError(null).
Also pass the original file extension to decodeToSharpCompat so RAW
variants get the correct temp file suffix for ExifTool/ImageMagick.
The compose route only decoded HEIC/HEIF via ensureSharpCompat, causing
EPS, PSD, BMP, RAW, and other exotic formats to fail with
"Processing failed". Now uses the same full format pipeline as the
tool-factory for both base and overlay buffers.
The favicon route bypassed createToolRoute and only handled HEIC/HEIF
via ensureSharpCompat. Exotic formats (PSD, BMP, JXL, JP2, EXR, HDR,
DNG, ICO, TGA, etc.) passed validation but crashed Sharp after
reply.hijack(), causing the response stream to hang silently until the
5-minute XHR timeout.
Now decodes all formats before hijacking the response, matching the
pipeline used by createToolRoute. Files that fail to decode are skipped
with a skipped-files.txt manifest in the ZIP rather than aborting the
entire batch.
Per-image try-catch with Sharp fallback for CLI-decoded formats (matching
createToolRoute), SVG decompression/sanitization, and filename-specific
error messages so users know which image failed and why.
The image-to-pdf route used ensureSharpCompat (HEIC-only) instead of the
full format decode pipeline from tool-factory. Formats like FITS, PSD,
RAW, EXR, HDR, TGA, etc. passed through undecoded and crashed Sharp.
Replace with validateImageBuffer + decodeToSharpCompat to match the
standard tool pipeline.
- Refactor use-tool-processor and use-pipeline-processor hooks
- Enhance dropzone component with improved UX
- Improve seam carving with better error handling and tests
- Add JXL format encoding support to format-encoders
- Update tool routes for consistent format handling
- Add dropzone unit tests
CLAHE width/height is tile size in pixels, not tile count. A 3px tile on
a 992x1088 image created ~330x360 independent histogram regions, producing
crosshatch/etching artifacts. Now uses image_dimension/8 (clamped 8-256)
for ~8 tiles per axis. Also strips alpha before enhancement and re-joins
after to prevent CLAHE/normalise/linear from corrupting transparency.
- Fix SSE write-after-end crash in progress.ts (remove callback before ending stream)
- Fix blob URL memory leaks: revoke processedPreviewUrl and old HEIC preview URLs
- Add AbortController to batch fetch in use-tool-processor and use-pipeline-processor
- Fix TGA format misidentified as CUR (extension overrides magic bytes)
- Add libheif-plugin-libde265 to Docker for HEIC/HEIF decode support
- Remove unused imports and state (AppLayout, setSampledColor, useEffect)
- Fix non-null assertions in meme-text-renderer and meme-generator
- Fix confusing void type in meme-templates
- Remove unnecessary useEffect deps in adjustments-panel
- Fix Playwright strict mode violations in 5 E2E tests
When deepEnhance is true and the upscale-enhance bundle is installed,
runs SCUNet (quality tier) after the Sharp pipeline for noise/artifact
cleanup. Falls back gracefully to Sharp-only if sidecar is unavailable.
CLAHE provides adaptive local contrast, normalise stretches the
histogram, and gamma adjusts exposure perceptually. Replaces the old
modulate/linear pipeline that compounded errors and darkened images.
Preset multipliers now include clahe and normalise entries.
Key fixes beyond the spec:
- maxSlope rounded to integer (Sharp requirement)
- White balance uses linear() instead of recomb() to avoid float-cast
that breaks CLAHE in the libvips pipeline
- CLAHE tile size adapts to image dimensions (1x1 for tiny images)
- Gamma clamped to Sharp's valid range (1.0-3.0)
- Normalise lower/upper correctly mapped to percentile cutoffs
Recovers expanded export format support from orphaned commit 80961c01
and PSD export from b07ecd5. The editor export dialog now supports 7
formats (PNG, JPEG, WebP client-side; AVIF, TIFF, GIF, JXL via server
conversion). PSD export uses ImageMagick on the backend.
Custom route handler supporting template mode (JSON body with templateId)
and custom image mode (multipart upload). Registers process function for
pipeline compatibility. Includes 18 integration tests.