- Derivative key collision: processUploadedPhotos/replacePhoto passed the
client-supplied original filename as the RAW output basename, but thumbnails/
heroes/previews are global keys — two galleries uploading IMG_0001.dng would
overwrite each other's derivative. Use the unique stored newFilename instead.
(processPhoto already used the unique photo.filename.)
- Watermark: the watermark path opens the original with sharp, which can't decode
RAW, so it fell back to the original bytes and recorded the copy as watermarked.
Skip RAW in generateForPhoto (like videos) so the watermark state stays honest
until RAW watermarking is properly supported.
- exiftool added to Dockerfile.dev so dev/native runtimes don't accept a DNG then
fail it with ENOENT.
Triage of an external SAST/SCA scan run on 2026-05-06. Most loud findings
were already resolved by PR #412 (the 18-CVE backport); this PR addresses
the residual real items:
* Drop unused `handlebars` from backend deps. The runtime require was
removed in PR #367 (#367) but the package.json line stayed. handlebars
was the source of two flagged criticals (CVE-2026-33937 RCE,
GHSA-2w6w-674q-4c4q AST injection) plus 8 highs — all now gone.
* `npm audit fix` on backend + frontend. Bumps transitive picomatch,
flatted, postcss, brace-expansion via lockfile, and direct dompurify,
lodash, vite, i18next-http-backend within their existing semver ranges.
Both audits now report 0 vulnerabilities.
* Add `event.origin === window.location.origin` check to the THEME_PREVIEW
message listener in PreviewPage. The branding page posts from the same
origin, so nothing legitimate is rejected; without the check, any third
party that window.open()'d the preview could push arbitrary
branding/theme payloads (semgrep
insufficient-postmessage-origin-validation).
* nginx: `proxy_hide_header` for X-Frame-Options, X-Content-Type-Options,
Referrer-Policy, Content-Security-Policy, Permissions-Policy,
Strict-Transport-Security at server level. nginx adds these itself, but
helmet on the backend was also emitting them — clients were seeing
duplicates (testssl flagged "Multiple X-Frame-Options / CSP /
Permissions-Policy / Referrer-Policy headers" on the live origin).
Single source of truth now.
* Dockerfile hardening (checkov):
- HEALTHCHECK on backend/Dockerfile, backend/Dockerfile.dev,
frontend/Dockerfile.dev. Frontend production Dockerfile already had
one.
- USER node in frontend/Dockerfile.dev (was running as root).
* GitHub Actions docker-build.yml: explicit top-level
`permissions: contents: read`. Per-job blocks already declare
`packages: write` where needed; this stops future steps from
inheriting unintended privileges (CKV2_GHA_1).
Backend npm audit: 4 vulns -> 0.
Frontend npm audit: 6 vulns -> 0.
Backend unit tests: 13 suites, 131/132 passing (1 pre-existing skip).
Frontend type-check + lint: clean.
The pre-existing integration-test failures (live DB / S3 required) and
the ThemeCustomizerEnhanced QueryClientProvider failures are unrelated
and reproduce on origin/beta without these changes.
Video uploads on production fail with "missing ffmpeg" because the
backend container ships nothing usable for the video pipeline.
Two compounding causes:
1. **Alpine + glibc mismatch.** The npm `@ffmpeg-installer/ffmpeg`
dependency added with the video-support PR (commit 68a9dc5)
ships per-platform binaries via optionalDependencies. The Linux
binaries are built against glibc, but the backend image runs on
`node:22-alpine` (musl libc) — known to either fail to execute
or fail on shared-library lookups on Alpine.
2. **`ffprobe` missing entirely.** `@ffmpeg-installer/ffmpeg`
bundles only the `ffmpeg` binary. There's a separate
`@ffprobe-installer/ffprobe` package that the codebase never
depended on. But `videoProcessor.js:21` calls
`ffmpeg.ffprobe(videoPath, …)` — the very first step of the
video pipeline shells out to a `ffprobe` binary that doesn't
exist in the image. Even if (1) worked, every video upload
would 500 here.
The fix is to install Alpine's `ffmpeg` package via apk. It ships
both `ffmpeg` and `ffprobe` built natively against musl, ~70MB
extra image size, single line in the Dockerfile, no per-arch
handling needed (apk pulls the right binary for both linux/amd64
and linux/arm64 — works with the multi-arch infra from #349).
- `backend/Dockerfile`: add `ffmpeg` to the apk install line.
- `backend/Dockerfile.dev`: same for dev parity.
- `backend/src/services/videoProcessor.js`: remove the
`setFfmpegPath(require('@ffmpeg-installer/ffmpeg').path)` line
— without removing it, fluent-ffmpeg would prefer the broken
bundled binary over the working apk one. Letting fluent-ffmpeg
fall back to PATH lookup picks up the apk binary in the
container and the developer's locally-installed binary on dev
hosts (Homebrew on macOS, apt on Debian).
- `backend/package.json`: drop the now-unused
`@ffmpeg-installer/ffmpeg` dependency. `npm install` removes
2 packages from the lockfile.
Verified: `videoProcessor.js` still loads cleanly (`node -e
"require('./src/services/videoProcessor')"`); lint clean.
Original: feat: enhance security logging and ensure rate limit blocks are properly tracked
- Add comprehensive logging for rate limit blocks with full request details
- IP address (with proper proxy detection), user agent, headers, timestamps
- Rate limit info (current count, limit, remaining, reset time)
- Separate tracking for auth vs general endpoints
- Enhance authentication failure logging
- JWT validation failures with detailed error info
- Admin auth attempts without token
- Failed token validation with user context
- All events include IP, path, method, user agent
- Improve Winston logger configuration for production
- Add automatic log rotation (10MB errors, 50MB combined)
- Create separate security.log for auth/rate limit events
- Ensure logs directory exists automatically
- Add structured JSON format for log aggregation
- Support container logging with LOG_TO_CONSOLE env var
- Create comprehensive documentation
- Security logging guide with examples
- Monitoring recommendations
- Configuration reference
- Add test script to verify logging functionality
All rate limit settings remain configurable via admin panel:
- Window duration, max requests, auth limits
- Skip authenticated requests option
- Public endpoints only option
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>