docs: sharing, mail and provenance in changelog, README and llms.txt

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNhLunz586g6DeYMa7Qsd1
This commit is contained in:
tillandClaude Opus 5 committed 2026-09-01 03:43:04 +00:00
1 parent 14dae8e550
commit 5a438e2c19
4 files changed
+173 -2

No files matched your search

+72
View File
@@ -2,6 +2,78 @@
All notable changes to Klarbild are documented here. Newest first.
## 2026-09-01 — Sharing, e-mail, password reset, and a security pass
### Added
- **Share links** (`/s/<slug>`) for a single image or a collection: someone without an
account can view them and download at full resolution. These are photos of children,
so the defaults lean safe — every link expires by itself (30 days, configurable), can
be revoked and un-revoked, optionally carries a passphrase, download can be switched
off entirely, and pages and files are noindex. Access is bound to the link, not the
image: swapping in another item id on a valid link returns 404. Private-session
results can never be shared.
- **Passing it on** by e-mail (several recipients, no attachment — a link can be
withdrawn later, an attachment cannot) or via `wa.me`, which opens WhatsApp with the
text prepared. The WhatsApp Business API needs a Meta account, approved templates and
per-conversation fees; this works today for free and the API can slot in behind it.
- **E-mail through the account's own mailbox** (SMTP, credentials AES-256-GCM encrypted
like every other secret). Powers password reset, optional job-done notifications, and
mailing share links. nodemailer has zero dependencies and no native postinstall —
checked before adding it, given the history.
- **Password reset**: single-use token, only its hash stored, one hour, identical
response whether or not the address exists, rate limited per address and per sender.
- **`/konto`** for the e-mail address and notifications, **`/teilen`** for managing links
with view and download counts.
- **IPTC/XMP provenance** on AI results (`trainedAlgorithmicMedia`, or
`compositeWithTrainedAlgorithmicMedia` for edited photos) plus the model, written by
hand into PNG iTXt and JPEG APP1 rather than pulling in a sharp upgrade. exiftool reads
it back from both formats; pixels, dimensions and dpi provably untouched.
- **CSP with real hashes** for every inline script, plus nosniff, referrer-policy,
X-Frame-Options and a permissions-policy. `style-src` stays open because React sets
styles as attributes and attributes cannot be hashed — measured, the hash-only policy
breaks the print preview.
- `mem_limit: 3g` in compose. sharp can spike; without a limit an outlier takes the whole
container down instead of just the process.
### Fixed
- **Sessions never expired server-side and survived a password reset.** `Max-Age` is a
request to the browser, not a rule for the server. A leaked cookie stayed valid
indefinitely, and the very reset meant to rescue a compromised account left the
intruder logged in; a demoted admin also kept admin, because the role came from the
cookie. Cookies now carry a session epoch, checked against the database on every
request, and the epoch is bumped on password reset and on any role change.
- **"Allow download: off" did nothing for images with transparency** — exactly the
cutouts and stickers this tool exists for. Verified: now 1500x2000 instead of the
2400x3200 original, and the download route returns 403.
- **The e-mail settings card saved nothing** while reporting success. Found by the design
critique an hour after shipping.
- **The public page explained how to bypass "view only"**, printing the long-press hint
regardless of the setting.
- The share title went into outgoing mail HTML unescaped while the message was escaped.
- argon2 ran on every request for protected links, including every image (130 ms, 64 MiB
each). Checked once now, then a short-lived signed cookie scoped to that link. The
passphrase moved from the query string to POST.
- `days` accepted nonsense and quietly produced an unlimited link (`Number('abc')` is
NaN, `NaN > 0` is false; `[]` coerces to 0).
- Rate limits keyed on the *first* X-Forwarded-For value, which the caller sets. Counting
from the right now, trusted-proxy count configurable.
- **The path-escape hole in delivery** the wiki had flagged twice as open.
- The crop maths lived twice with nothing holding the two copies together. One module
now, 60000 randomised cases asserting both entry points agree — which immediately
found a real bug: the zoom limits also clamped the *initial* crop, so an extreme format
silently broke the aspect ratio.
- Smaller: expired links could still be mailed; missing PUBLIC_BASE_URL sent dead links
while reporting success; the reset response time revealed whether an account existed;
`share_views` grew without bound; double provenance marking appended a second packet
with the older model; the colour flag covered the selection circle in the library; the
seventh nav item pushed logout off screen at 820px; `/konto` was unreachable on a
phone; network failures rendered as empty states.
### Changed
- 42 new tests (97 total). `shares`, `mail` and `provenance` had none, and that is where
both reviews found the most.
- `PRODUCT.md` and `DESIGN.md` added, written from the code and the wiki.
## 2026-08-21 — Unique, chronologically sortable filenames
### Fixed