github PicPeak/picpeak v3.132.1

latest releases: v3.132.2, v3.132.3-beta.0
4 hours ago
  • Upgrading from 3.46.x is one-way and regenerates every gallery preview, so read the notes below before you update
  • People in this gallery: face recognition on your own hardware, via an optional sidecar, off by default
  • PicTransfer: cross-event file transfers with token-protected download links
  • Admin SSO through OIDC, with role mapping and logout to your identity provider
  • Editable roles and granular permissions, with the dangerous settings split out
  • Gallery folders that contain their photos instead of filtering them
  • Reveal mode, per-gallery download resolutions and sized preview tiers so phones stop pulling 1920px
  • Emoji reactions and Lightroom colour labels for client proofing, round-tripping to your catalogue

3.132.1 (2026-09-14)

⚠️ Upgrading from v3.46.x — read this first

This release promotes roughly two months of beta work onto the stable channel at once. See Upgrading for the full guide.

One-way. Migration 186 deletes duplicate external-photo rows and 187 rewrites stored external paths with no record of the previous base — both irreversible by design. An instance on v3.132.1 cannot be moved back to a v3.46.x image. Take a backup you have tested restoring.

Two visible side effects on first boot, both verified against a real v3.46.13 database:

  • Every lightbox preview regenerates. Migration 188 clears all stored preview keys, so each photo re-renders its preview on first view. Nothing is lost, but on a large library expect a CPU and S3-egress spike over the first hours of use.
  • A fixed pixel height on gallery tiles is rewritten. Migration 181 replaces any .photo-card img { height: <N>px } with height: 100%, including a rule you wrote yourself.

Nothing is switched on for you. Face recognition, PicTransfer, newsletters, CRM, accounting, contracts, quotes and projects all stay off by default, existing permission grants are projected forward, and the optional picpeak-ml sidecar is not started unless you ask for it.

One thing you will be asked. Anonymous usage reporting is off by default, but an existing install gets a single opt-in prompt on the first admin login. Declining is permanent.

Why the version jumped from 3.46.13. Stable and beta count releases independently, so the numbers drifted far apart for identical code. A promotion now pins stable to beta's current base version — the jump is the two channels meeting, not 85 releases of change.

Features (63)

Galleries & guest experience

  • categories: per-event category ordering — global default + override (#782) (4698402)
  • cms: keep the editor toolbar in reach on long pages (#1335) (d6a0c4a)
  • downloads: per-gallery download resolutions (#858) (#1022) (8e35737)
  • external-media: watch reference folders and import new files automatically (#1345) (8cc7d7d)
  • feedback: a third identity mode with one shared colour tag per photo (#1197) (#1208) (22e00f8)
  • feedback: emoji reactions on photos (#839) (#855) (3d6c984)
  • feedback: let guests remove their star rating (#884) (#893) (6a048d0)
  • gallery: admin preview skips the password on protected galleries (#981) (f006615)
  • gallery: colour labels for client proofing, and one global default per feedback type (#1044) (#1137) (e2844d1)
  • gallery: folders that contain photos instead of filtering them (#1160) (#1161) (0a36ca6)
  • gallery: info banner above the photo grid (#932) (#1063) (b48fa62)
  • gallery: mouse-wheel zoom at cursor in the lightbox (#885) (#927) (926a4a5)
  • gallery: multi-select feedback filters + sort direction controls (#889) (#929) (3bcded7)
  • gallery: per-event toggle to hide the logo on the password page (#894) (#928) (08ff9f2)
  • gallery: quick return from zoomed to fit-to-screen in the lightbox (#886) (#891) (97f6889)
  • gallery: responsive grid thumbnails (#1095) (#1109) (887bdbe)
  • gallery: reveal mode — hide gallery from guests until reveal (#838) (#856) (2f05fcc)
  • gallery: sized preview tiers so phones stop pulling 1920px (#1095) (#1099) (011f6ae)
  • slideshow: guest-scannable share-link QR overlay (#848) (e8dad4b)
  • slideshow: per-event play order + category filter (#202) (b768a53)
  • uploads: DNG / camera-RAW support via embedded-preview extraction (#821) (be2ec0a)
  • uploads: HEIC/HEIF support + dynamic format hint on guest upload (#821) (2b5b23b)

Faces — opt-in, off by default

  • faces: People in this gallery — face recognition via an optional ML sidecar (#1074) (#1075) (b69dd13)
  • faces: consolidate look-alike clusters after a scan, and suggest the rest (#1107) (3583c92)
  • faces: let the photographer choose which photo represents a person (#1119) (bbce3cd)
  • faces: make "not the same person" survive a re-scan (#1132) (#1145) (c305ad4)
  • faces: show a detected face in its source photo, outlined (#1120) (38c27d0)

Admin, auth & permissions

  • admin: GitHub repo button in the sidebar footer (#778) (d3d7df4)
  • admin: shift-click range selection in the photo grid (#1212) (#1213) (f18bc56)
  • admin: surface the registry move through the update check (#993) (137a42f)
  • admin: upload single files above the batch cap through the chunked API (#1431) (c448572)
  • api: Lightroom round-trip — read proofing marks, put finished edits back (#745) (#1165) (8db8527)
  • auth: OIDC SSO for admin users — phase 1 (#798) (ed5fc5a)
  • auth: OIDC logout-to-IdP — phase 3 (#798) (#865) (219d07b)
  • auth: OIDC role mapping + login policy — phase 2 (#798) (#854) (f8a95d2)
  • auth: make the admin "Remember me" checkbox actually do something (#1186) (#1195) (d3e9a7c)
  • events: gallery QR code + printable table-card/poster PDFs (#847) (60cdd07)
  • events: publish without notifying, and send the gallery email later (#1235) (#1241) (1ef2b3c)
  • notifications: surface guest activity in the admin bell (#849) (cb5b319)
  • permissions: granular permission gating + role editor & presets (#747, phase 1 of #743) (#1045) (b118695)
  • settings: expose the API rate limiter in the Security tab (#1338) (8017370)
  • setup: add anonymous usage-reporting opt-in to the first-run wizard (8d0c329)
  • setup: configure the public address and SMTP in the wizard, not .env (#1104) (9431b9f)
  • setup: event-types step in first-run wizard + un-hardcode event type deps (#800) (7eb6357)

Security & hardening

  • security: harden .picpeak restore robustness — sessions, roles, sequences (340d91b)
  • security: opt-in recoverable gallery passwords (#1341) (fb9da72)

Business modules — flagged off by default

  • accounting: re-bill proof attachment, CRM panel & hours↔re-bills cross-add (#979) (165cebd)
  • crm: newsletter campaigns behind a newsletters flag (#1264) (fc59540)
  • invoices: configurable VAT/free-text note + fix multi-page page-number overlap (#794) (1476884)
  • newsletters: warn about deliverability before a large send (49197be)
  • transfers: add PicTransfer — cross-event file transfers (#998) (2e495d7)
  • usage: add consented beta capabilities and gallery photo totals (c358bc6)
  • usage: distinguish real edits and template delivery with v5 consent (#1339) (5c1e38d)
  • usage: open the portal signed in, with the credential never in a served URL (f114f3e)
  • usage: plain link to the public usage portal, German opt-in copy (7e40579)
  • usage: prompt existing admins once for usage reporting after an update (d20f801)

Mail

  • email: global signature footer from the business profile (#1264) (b6e40b9)
  • email: webhook transport as an alternative to SMTP (#1225) (#1231) (d62407f)

Backup, storage & deployment

  • backup: open sqlite → pg .picpeak restore as the supported upgrade direction (#1041) (#1043) (8809564)
  • deploy: make the all-in-one image installable without a shell (#1124) (7223118)
  • docker: all-in-one image (#1042) — my version of #1067 (#1068) (0874a30)

Other

  • add opt-in product usage and feedback integration (#1110) (b53e5d9)
  • expand opt-in capability coverage with versioned consent (a738259)

Bug Fixes (206)

New to the stable channel with this release.

Galleries & guest experience

  • branding: hide "Powered by PicPeak" on every page, not only the gallery (#999) (3bb4f1a)
  • branding: route the gallery footer through (#1008) (1bf19a7)
  • categories: address PR #790 review — event ownership, migration renumber, nits (a4b4485)
  • categories: validate category name length instead of 500ing (5fa04e6)
  • cms: enable the Tailwind typography plugin so prose classes work (#1288) (de3a7f7)
  • external-media: record capture dates on import, and backfill existing libraries (#1172) (#1179) (410b8f8)
  • feedback: align word-filter severity vocabulary with the admin UI (6f7aa59)
  • feedback: make the "block" severity tier actually reject (814f205)
  • feedback: name the camera original in the exports, not just the stored file (#1224) (#1228) (4f684eb)
  • gallery: a missing thumbnail tier must not take the backend down (#1128) (f735d26)
  • gallery: block password form in Instagram in-app browser and unmask login errors (#863) (323dcae)
  • gallery: bound concurrent image fetches and abort them on unmount (#1287) (4afe7a6)
  • gallery: cap how many cached zips rebuild at once in the background (#1418) (98d2560)
  • gallery: clear the guest identity on gallery logout (28f1495)
  • gallery: decide the overlay by pointer capability, not viewport width (d027488)
  • gallery: don't close the lightbox when clicking beside the photo (#883) (#890) (34c2992)
  • gallery: follow the input in use, not the device's primary pointer (0b6b8fb)
  • gallery: give the Grid layout a lazy-loading pre-load band (#1287) (b1e5287)
  • gallery: honor canvas settings in the Premium lightbox (9edce85)
  • gallery: keep canvas rendering in the lightbox, render tiles as (c75839d)
  • gallery: keep the lightbox toolbar from masking the photo (#888) (#892) (ec66cd2)
  • gallery: keep the video controls reachable while paused, and go fullscreen on iOS (#1426) (03bc520)
  • gallery: make per-event banner overrides actually work, both banners (#440, #932) (#1064) (52db982)
  • gallery: make the returning-guest recovery findable (#1210) (#1217) (1f3f7e9)
  • gallery: no-store private JSON, and give guest uploads a real status (a28f96b)
  • gallery: release grid tiles once they are far enough out of view (a6a1db5)
  • gallery: release the canvas decode when it is drawn, not at unmount (fbe9757)
  • gallery: release the canvas-mode decode, and drop a now-duplicate sanitizer (be8d79e)
  • gallery: remove the inert image-protection prop surface from AuthenticatedImage (e734e41)
  • gallery: restore the download CTA under headerStyle "none" (4c6ca49)
  • gallery: retry a failed image fetch once the tile is back on screen (77ae94e)
  • gallery: serve JPEG preview for non-displayable originals in lightbox (codex review of #832) (808d305)
  • gallery: show a guest their own likes when feedback sharing is off (#1286) (8f98f6b)
  • gallery: show a guest's own upload without a hard reload (18715b5)
  • gallery: show colour labels in the Carousel layout (#1189) (#1196) (da80216)
  • gallery: show feedback filter chips on desktop for galleries without categories (#802) (b928338)
  • gallery: show other guests' colour labels in the grid (#1178) (#1180) (51d20c5)
  • gallery: stop browser zoom tripping the devtools viewport heuristic (72894e2)
  • gallery: stop devtools protection from breaking the whole page (9d4bd7a)
  • gallery: stop invisible overlay controls swallowing mobile taps (c0d3479)
  • gallery: stop the pre-zip build leaking storage reads (#1402) (f094cc0)
  • guests: don't answer feedback with a stale identity mid-invite (7d51aa3)
  • guests: drop a stored identity when a spent invite names someone else (f2f4089)
  • guests: expire stale tokens, sync tabs, survive unwritable storage (51db1e0)
  • guests: invite wins over stored identity; clear server-rejected ones (f3f37a8)
  • guests: keep guest identity across a tab close (a21c4d3)
  • guests: read identity from whichever store holds it, write it as a pair (7a1ea84)
  • guests: rebuild consumers on identity switch; repair fallback reads (e9babf6)
  • guests: surface duplicate guest registrations, and stop making so many (#1210) (#1216) (5c85e0c)
  • images: backfill orientation for libraries that predate the fix (#1199) (edef4d7)
  • images: probe and clean up preview tiers under the extension the encoder actually wrote (#1355) (acb25a9)
  • images: single-flight lazy rendition generation and keep the old rendition during replacement (#1350) (c97341e)
  • photos: emit visibility and processing_status from the list mapper (fe5ac91)
  • photos: treat category_id 0 as uncategorized instead of storing it (3f6c81a)
  • search: match the original filename, and honour the date-format setting (15fdd70)
  • search: stop escapeLikePattern corrupting bound search values (a89057d)
  • thumbnails: regenerate external photos instead of dropping their tiers (#1129) (97d92f8)
  • ui: drop themed text colours from the last three admin surfaces (22cada9)
  • ui: stop branding-theme text colour rendering headings invisible (da9ceb1)
  • upload: enforce the chunked-upload cap on bytes received, not declared (77b11ab)
  • upload: enforce the configured per-file size limit on admin uploads (e18ab0d)
  • upload: let the csrf gate pass application/octet-stream chunks (#1401) (7c0c5c1)
  • upload: scope category ids, stop temp-file leaks, split the video cap (7c9baff)
  • upload: stop buffering a chunk body before anything checks its size (#1406) (4622478)
  • uploads: DNG magic must be a single entry (.every validation) (e732e13)
  • uploads: RAW derivative key collision, watermark skip, dev exiftool (codex review of #833 round 2) (d0ccadb)
  • uploads: allow configured raw formats (f4b685a)
  • uploads: apply RAW extraction in the actual async ingest path (codex review of #833) (b743ea0)
  • uploads: apply configured max file size to guest uploads (#613 follow-up) (1e38d84)
  • uploads: keep videos when thumbnail generation fails (#845) (0310c46)
  • uploads: register HEIC/HEIF with the file validator + fix admin format hint (codex review of #832) (c9b64d9)
  • uploads: show configured guest file types (433fb9a)
  • uploads: tighten guest max-file-size setting (codex review of #823) (e03d13e)

Faces — opt-in, off by default

  • faces: dark-mode styling for the People surfaces (#1106) (#1126) (24e11df)
  • faces: defer on unreachable storage, and commit the import path first (#1097) (0b886ed)
  • faces: face avatars were cropped against a cropped rendition (#1100) (b3a7ab2)
  • faces: restore the :beta image tag and surface sidecar health (#1087) (37a15e3)
  • faces: scan external/reference photos instead of skipping them (#1090) (#1091) (576924f)

Admin, auth & permissions

  • admin: code-review follow-ups on #910/#916 (MIME resolver + expiry reactivity) (#921) (252475f)
  • admin: gate the dimension repair as system maintenance (#1182) (3991dc3)
  • admin: interpolate activity and notification message values (78b1ddd)
  • admin: portal the update-available modal to document.body (ac50f0b)
  • auth: issuer-tag the oversize SSO logout marker (#798) (#1010) (a607cea)
  • auth: keep must_change_password in the roles-join fallback (cca2185)
  • event-types: harden setup window + catalog validation (codex review) (f8ba669)
  • event-types: un-hardcode event type dependencies in v1 API and CRM (#800) (5da1c3a)
  • events: add archive_size to the immutable column deny-set (57dd084)
  • events: drop non-canonical keys from the event update before any check runs (#1346) (810801a)
  • events: guard create-event submit against re-entrant submissions (c19e944)
  • events: honour ?tab=, show a load error, and stop lying about uploads (504a8b6)
  • events: rename a shadowing local and bound the photo-cap input (758dc00)
  • events: render a not-found state instead of hanging on a 404 (c2428aa)
  • events: return 409 instead of 500 when a slug is taken (afc5779)
  • oidc: fail clearly when no public base URL is configured (ac1838f)
  • oidc: local-credential lockout, session hydration, split-origin gaps (codex round 3) (e91c7de)
  • oidc: security + robustness hardening from codex review rounds 1-2 (7f7d38a)
  • settings: clear the accounting flag when its parent is turned off (3e16b81)
  • settings: derive the sidebar preview from the real sidebar declaration (c6cb018)
  • settings: don't crash on a fresh load before permissions resolve (673f055)
  • settings: remove the duplicated section heading on 11 tabs (3acb452)
  • setup: put the setup token where a NAS user can find it (#1218) (#1219) (696c69a)
  • setup: refresh usage state after accepting consent (a5f7b38)
  • setup: require the full usage reporting disclosure (9168bdd)

Security & hardening

  • security: actually apply the general API rate limiter (b0f33c1)
  • security: apply image-security defaults on every creation path (ab6c33d)
  • security: apply per-IP rate limiting to credential endpoints (50e8ed6)
  • security: apply the Image-security defaults instead of storing them (#1296) (8ca3610)
  • security: check for an escaped identifier before consuming the escape (b6dc099)
  • security: chunked-upload init checks the size cap before the type allow-list (0ac006b)
  • security: close four middleware gaps around the API edge (839bf4e)
  • security: close the case-sensitivity bypass in the API rate limiter (a929aff)
  • security: close the remaining image-security default gaps (19c518a)
  • security: close two CSS url() bypasses the sanitizer dedup exposed (1cf8274)
  • security: decode settings at the API boundary and honour the transaction (0e560eb)
  • security: let cors() own Access-Control-Allow-Origin on protected images (#1118) (0077623)
  • security: make the CSS sanitizer's remote-URL block actually block (a7d0972)
  • security: mask backup credentials on read + unblock MFA login during maintenance (07f2c90)
  • security: one settings decoder, and the last creation path (0deef25)
  • security: raise the general limiter's fallback budget to 300 (19e125d)
  • security: rate-limit the password-change endpoints per IP too (5a0c9f5)
  • security: re-check inline CSS after template substitution (027afb6)
  • security: reject array values for every field on the event update (933f2d8)
  • security: reject array values on the event update route too (8f3436f)
  • security: stop a gallery viewer's own image fetches spending the anonymous budget (7b2dd3f)
  • security: strip control characters before scanning CSS for url() (99f54a3)
  • security: use CSS whitespace, not JavaScript's, in the url() reader (4196e83)
  • security: validate CSS urls last, after every pass that moves text (1151e96)

Business modules — flagged off by default

  • accounting: allow creating a customer from the picker (be39929)
  • accounting: gate cross-add counters on the permission their endpoint checks (#984) (4b53b64)
  • accounting: let "bill to a customer" work with the portal off (3790156)
  • contracts: add tooltips to the ellipsized block-library names (d16137b)
  • contracts: widen the block-library list column (bd44708)
  • crm: label the two invitation conflicts and stop guessing after a 5xx (bc90b4d)
  • crm: pass trx to logActivity inside transactions — audit rows silently lost on SQLite (#851) (a6a3c9f)
  • crm: stop the invitation UI claiming more than it can know (6bb12c6)
  • crm: tell the admin whether a customer's invitation actually went out (1b8e5f8)
  • newsletters: make the warning's duration and queue claim honest (7b4a65e)
  • quotes: enforce the status state machine, and correct the table (103863c)
  • usage: cap the update-prompt modal height so it scrolls on short viewports (c61a6b0)
  • usage: classify prompt acknowledgement in privacy coverage (fb2f833)
  • usage: close the QA findings on opt-in product usage (1e8b6f1)
  • usage: close the remaining withdrawal races, reset per-item name consent (22da018)
  • usage: drop the tinted block and stop the modal opening with a focus ring (75ef137)
  • usage: explain and de-emphasize the pending-packet button lock (#1363) (9f4b9ba)
  • usage: introduce consented v4 without changing historical reports (ef8a52f)
  • usage: isolate the Postgres fixture, and stop two more wrong signals (cc263f2)
  • usage: keep the settings tab usable on a bad collector URL, and report layouts and CSS accurately (bb76ca5)
  • usage: let a withdrawal win against an activation that is still starting (80e238f)
  • usage: let an operator clear a participation the collector never accepted (e40bc47)
  • usage: minimize session receipts and clarify privacy controls (e347f8f)
  • usage: name the unreadable-key failure, unpin the collector default, align the tab (c043897)
  • usage: preserve consent choices and make the prompt accessible (9a437ee)
  • usage: preserve report contracts with compatible receiver validation (7ca783f)
  • usage: protect a pending withdrawal, widen the backup signal, explain an unreadable key (83fbb63)
  • usage: reformat the consent modal so the disclosure can be read (a9e51d8)
  • usage: report restricted gallery downloads in v3 instead of an always-true signal (02b353e)
  • usage: scope the participation notice, highlight it, and call ignoring what it is (4944b9b)
  • usage: stop WebKit collapsing the consent dialog to its header and footer (9d18868)
  • usage: stop local backups implying S3 use, and make the protocol-error branch reachable (32d745b)
  • usage: take the withdrawal baseline before the lease, not after it (9785b63)

Mail

  • email: compare queue timestamps in JS, and make retry actually send (89db469)
  • email: derive preview sample data from each template's variables (1be2740)
  • email: give every template a real display name in the config UI (355fe4f)
  • email: give gallery_created a real German translation (73b08a7)
  • email: keep the webhook payload out of the logs, and bound the response read (#1225) (#1233) (0d41fe5)
  • email: make waiting rows read-only, and time the grace from when due (4deac22)
  • email: read naive SQLite timestamps as UTC, and page the candidates (98aa06a)
  • email: repair and seed the gallery lifecycle templates (41e1de7)
  • email: scrub gallery passwords from the sent-mail archive (#1340) (69754f8)
  • email: show a queue nobody is working instead of reporting all-clear (73d8675)
  • email: wire the settings status card, and cap-aware truncation (2d403f7)

Backup, storage & deployment

  • analytics: send Umami page views through track(), not the removed trackView() (ac01b73)
  • analytics: serve self-hosted trackers same-origin so CSP stops blocking (3468550)
  • analytics: warn about the CSP allowlist on every tracker provider (3489610)
  • archives: restore categories for original-filename archives on main too (#1252) (a35d2ba)
  • archives: run search, filter and sort server-side (fc7cb22)
  • archives: sort and total on real archive sizes, escape LIKE wildcards (da6e34d)
  • archives: stop restore from dropping videos and rewriting the row (#1425) (d37c0c8)
  • storage: write business documents under STORAGE_PATH, not the cwd (#1070) (f22999a)

Other

  • enforce gallery access and consolidate gallery workflows (#1357) (f0e6d2d)
  • interrupt idle worker waits during shutdown (a31a2e2)
  • per-field template guard, LIKE escaping, wait for all uploads (da8fcc8)
  • remove the fragmentation handling stranded by #1303 (7ff8caf)
  • remove the image-fragmentation surface (967224c)
  • retain revocations for tokens without expiry (662516a)
  • single-photo gallery downloads 404 on S3 storage backends (#1048) (bb2f709)
  • sync gallery feedback filters after lightbox like/rating in simple mode (#882) (33f1bc4)
  • calendar: don't put a fixed reference date in the month header (76a1453)
  • ci: publish v-prefixed image tags so :vX.Y.Z resolves (#668) (784d059)
  • ci: publish v-prefixed image tags via type=ref,event=tag (#668) (39db7bf)
  • dates: normalize SQLite epoch timestamps at remaining API surfaces (#485 follow-up) (#857) (c6ec93e)
  • export: name the camera master in photo exports, not the delivered render (#1229) (#1230) (f4c054a)
  • file-watcher: bound concurrent photo processing (#846) (8337a71)
  • frontend: allow blob: in the CSP media-src directive (#1427) (7d7ed04)
  • i18n: make i18n:ci pass by fixing the extractor config (5dbb435)
  • i18n: rewrite the German product-usage copy (35cbcef)
  • middleware: log ownership lookup failures; drop dead auth surface (42ba835)
  • migrations: judge each German field on its own in migration 195 (4515632)
  • release: target stable in release-please.yml + undo the bogus 2.7.0 bump (65ac6ed)
  • tests: raise jest timeouts to survive the growing migration chain (#860) (40eb03f)
  • tests: raise migration-boot hook timeout pins to the 120s default (#900) (d9ad982)
  • types: resolve the TypeScript build:check backlog (6e5755d)
  • users: give the cancel-invitation dialog a distinct confirm label (31ffbc8)
  • watcher: stop re-importing a photo whose file was replaced (#1226) (#1237) (6ca8baa)
  • webhooks: write delivery timestamps as ISO strings (c5c5a6b)
  • workflows: restore the once-per-process seed guard (a7d45dd)

Also included (91)

These shipped to stable earlier as 3.45.x / 3.46.x patches and are listed under those headings too. Repeated here so this entry is a complete inventory of the release — if you are coming from v3.46.13 you already have them.

Galleries & guest experience

  • external-media: one row per external file per event (#1162) (#1167) (06da1b9)
  • external-media: store external paths from the media root (#1163) (#1168) (a7b74bc)
  • feedback: persist guest feedback settings, unshadow the guest route (#1030) (#1031) (89dc962)
  • gallery: a guest's own hidden feedback is hidden from them too (#1150) (#1153) (2c81888)
  • gallery: bound and reclaim storage reads in the remaining zip builders (#1410) (70f5a8c)
  • gallery: coerce SQLite 0/1 booleans in the guest surface (#1028) (#1034) (34ee311)
  • gallery: give masonry tiles their real shape back (#1130, #1131) (87115b2)
  • gallery: guest filters respect show_feedback_to_guests, and marks survive a mid-write clear (#1147) (00b20b2)
  • gallery: keep videos playable under enhanced and maximum protection (#1404) (1080388)
  • gallery: let an admin preview a draft through its short share URL (#1405) (f92d4bb)
  • gallery: no Logout button on galleries that don't require a password (#1149) (#1152) (e4a8be8)
  • gallery: stop the lightbox loading originals to display a photo (#1166) (#1169) (77953c1)
  • images: fence the capture-date backfill on the file it read (#1201) (#1204) (cec8eff)
  • images: respect EXIF orientation in thumbnails, heroes and previews (#1194) (c18f54e)
  • preview: generate lightbox previews for external/reference photos (#1078) (#1079) (af7970b)
  • previews: preserve alpha and animation in the preview tier (#1171) (1366d6d)
  • slideshow: stop "no crop" fit letterboxing a pre-cropped frame (#1015) (#1018) (75bfad2)
  • ui: stop iOS Safari zooming in on 14px form fields (#1113) (d241919)
  • upload: let Android guests reach the camera without breaking video (#1244) (66989d7)
  • uploads: prevent cross-photo contamination from filename collisions and non-atomic writes (#931) (#933) (defeae9)
  • video: try metadata extraction and thumbnail generation independently (#1371) (a2bf1f6)

Admin, auth & permissions

  • admin: expose view/download counters in the admin photos list (#895 follow-up) (#914) (aca3c8e)
  • admin: keep header-style tiles from overflowing their cards (#1422) (7cd7654)
  • admin: make "Storage used" report storage used (#1164) (#1170) (849a580)
  • admin: move the maintenance sweeps' run state into the database (#1181) (#1184) (05e23ef)
  • admin: serve videos with their real MIME type in the admin photo view (#908) (#910) (67c56c5)
  • admin: stop marking events expired up to 24h early (#909) (#916) (487f55f)
  • admin: the "Uncategorized" photo filter returns every photo (#1211) (#1214) (a490b64)
  • auth: fail closed when the adminAuth roles join errors (#974) (6699855)
  • auth: treat zxcvbn suggestions as advice, not blocking errors (#1050) (4f352de)
  • events: accept hero_logo_visible: null on create/update (#822) (b97b130)
  • events: apply the gallery password policy to publish and send-later (#1253) (6938bad)
  • events: delete stored objects when cascading an event delete (#1051) (202c553)
  • events: make event_date/expires_at nullable on SQLite (#1029) (#1035) (671c4db)
  • setup: require Node 22.12 for sanitize-html (e06d0b4)

Security & hardening

  • security: authz/ownership gaps (token binding, auth revocation, feedback/customer ownership, token logging) (#950) (c2ce12c)
  • security: backup/restore hardening — public-dir DB dump, restore path allowlist, gunzip bound, manifest keying (#956) (0d4c308)
  • security: block guest access to hidden/client-only photos across bulk + secure routes (#939) (8a87c92)
  • security: bound inbound-mail resources, redact secrets from logs (GHSA-2qf9, pgmp, r794) (#959) (1b4e5fe)
  • security: bound password input before zxcvbn, and drop the legacy media mounts (14cd5ea)
  • security: bump backend deps to close all 14 open Trivy code-scanning alerts (#869) (38b8d47)
  • security: bump sanitize-html to 2.17.5 (CVE-2026-53606) (#937) (fe615c8)
  • security: bump sanitize-html to 2.17.7 (6583178)
  • security: close 5 Trivy alerts — postcss/tar bumps + drop npm from the runtime image (#878) (08be2b8)
  • security: close GHSA-g94x (cross-gallery photo read) + GHSA-pv6w (admin DB export) (#924) (03087c7)
  • security: close authorization/ownership gaps (token scope, mass-assignment, category hero, project docs) (#943) (82d6871)
  • security: contain logo, favicon and PDF-logo unlinks to their upload directories (3e46530)
  • security: enforce event ownership on the v1 API surface (GHSA-9697) (#957) (e2ce95e)
  • security: enforce project ownership on project + project-email routes (GHSA-wrg5, GHSA-93x4) (#960) (7c0c0a5)
  • security: enforce the strength-endpoint validators, and stop the generator spinning (054cd6f)
  • security: escape brand tokens, block tracker redirects, trim logo diagnostic (GHSA-j347, mw76, 29vm) (#961) (164129b)
  • security: harden .picpeak restore operator-preservation (GHSA-qxfx follow-up) (38fd41a)
  • security: harden four smaller gallery and contract paths, drop the unmounted photo auth middleware (835312e)
  • security: neutralize spreadsheet formulas in all CSV/export cell-writers (CSV injection cluster) (#948) (8f91c2c)
  • security: never serve a photo under its stored MIME, and stop trusting the chunked-upload type (063977d)
  • security: preserve current admin on .picpeak restore (GHSA-qxfx-4493-4v8f) (348894e)
  • security: read the password-complexity key the settings UI writes (#843) (8060fed)
  • security: redact gallery share tokens from analytics tracking (GHSA-7m6c) (#952) (1c8f7d5)
  • security: reject ZIP-slip entries in archive/backup restore (GHSA-jfhw-fj23-fx6x) (9cd6b08)
  • security: remove unguarded legacy /api/events router (GHSA-4j34-x562-5vfq) (6cd546e)
  • security: resolve DNS before vetting external hostnames (SSRF cluster) (#941) (b700569)
  • security: sanitize chunked-upload filename (GHSA-pc72-jf53-w28j) (31bc01c)
  • security: scope dashboard stats/analytics/activity to the caller's events (GHSA-c2jj, gqx7, jhcf) (#958) (da855cf)
  • security: share-login must not bypass gallery password (GHSA-9hmx-68vc-qpqw) (7dace04)
  • security: stop reflecting submitted passwords in validation errors (903e471)
  • security: stop reflecting submitted values in validation errors everywhere, cap credential lengths, close the login timing oracle (40a8a98)
  • security: unauth share_token leak (HIGH) + restore path-traversal, logo file-read, branding path keys (#946) (9050aff)
  • security: verify the signature before writing a token to the revocation list (0ca0e4a)
  • security: vet the destination project when linking a deal (#991) (0c8ad6b)

Business modules — flagged off by default

  • projects: stop the cockpit offering email controls the API rejects (#976) (67592fc)

Backup, storage & deployment

  • analytics: make per-photo view/download counters actually count (#895) (#904) (78116e2)
  • archives: take the restored category from the manifest (#1240) (0d340f4)
  • backup: honor the configured database-backup destination path (#1366) (15cd5ed)
  • backup: make backup settings actually apply (#871) (#874) (a2e7234)
  • docker: default NODE_ENV=production so non-compose deploys don't fall back to SQLite (#1038) (#1039) (6de30e5)
  • storage: add S3 client timeouts so a dropped connection can't wedge uploads (#1049) (3600231)

Other

  • backend: bump sharp, nodemailer, multer, js-yaml, joi for security fixes (#1374) (f6b81fa)
  • backend: contain and sanitize the SQLite restore source path (#1384) (316bcbd)
  • backend: enforce event ownership on short URL deletion (#1379) (e290207)
  • backend: reject a replayed TOTP code within its validity window (#1389) (cdde937)
  • backend: require actor to hold every permission of a role they grant (#1378) (59ea83c)
  • backend: shorten payment-check token TTL and notify admin on use (#1385) (e324791)
  • backend: use the strong password generator for resets and enforce must_change_password (#1387) (b798d8e)
  • backend: validate business-profile logo uploads by content, not filename (#1381) (abc9601)
  • backend: validate event id before using it in the logo storage filename (#1382) (38b0e1d)
  • backend: validate the S3 endpoint host before the restore download (#1383) (ec03089)
  • deps: bump ip-address, brace-expansion and postcss for open CVEs (#987) (6c03fea)
  • deps: bump nanoid and js-yaml out of two HIGH advisories (#1013) (e3830cd)
  • pdf: RFC 6266-encode Content-Disposition on quote/invoice PDFs (#1024) (#1055) (3a11e6e)
  • scripts: regenerate-thumbnails resolves external sources through ensureThumbnail (#1148) (#1151) (b581267)
  • update: target docker-compose.production.yml in dashboard update steps (51a505e)

Don't miss a new picpeak release

NewReleases is sending notifications on new releases.