GHSA-26w7-cxv4-gfx2 (kritisch, RCE beim Verarbeiten von AVIF) hat für
Astro 5 keinen Fix. Also hoch auf Astro 7.3.2, @astrojs/node 11,
@astrojs/react 6 und sharp 0.35. `npm audit`: 0 Funde.
Zwei Stellen mussten mit:
- `experimental.csp` heißt seit Astro 7 `security.csp`. Das ist keine
kosmetische Umbenennung: ein `csp` auf oberster Ebene wird ohne Warnung
geschluckt und die Seite läuft ganz ohne Policy. Gemessen — Header
vorher weg, nach der Korrektur wieder da, mit denselben Skript-Hashes
wie unter Astro 5.
- sharp exportiert den Typ `Sharp` jetzt benannt statt als Namensraum.
Dazu das Image: node 20 → 22 (Astro 7 verlangt mehr als 20.19, die Tests
brauchen 22.6 für --experimental-strip-types), und `npm ci` mit Lockfile
statt `npm install` — sonst baut das Image nicht die Versionen, die
getestet wurden.
Geprüft gegen echtes Postgres: alle Seiten 200, Upload durch sharp 0.35,
Bogen-PDF auf 210,00 × 297,00 mm gemessen, Einzelbild 35 × 45 mm bei
300 dpi = 413 × 531 px mit korrekter dpi-Angabe. 124 Tests grün.
Mail goes out through the account's own mailbox (SMTP) rather than a sending
service: the domain lives there anyway, so SPF and DKIM line up by themselves
and no third party gets to see the messages. Credentials are AES-256-GCM
encrypted in settings like every other secret, never in the repo. nodemailer
has zero dependencies and no native postinstall -- checked before adding it,
given the history.
Password reset: single-use token, only its hash is stored, one hour, and the
request endpoint answers identically whether or not the address exists so it
cannot be used to enumerate accounts. Rate limited per address.
Share links (/s/<slug>) let someone without an account see the pictures and
download them at full resolution. These are photos of children, so the
defaults lean safe: every link expires by itself (30 days), can be revoked,
optionally carries a passphrase, and the page 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 at all.
Tracking records when and with what, never who: IP addresses are only stored
salted-and-hashed, so repeat visitors can be counted but nobody identified.
Sharing goes out by mail (several recipients at once, 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 would need a Meta
account, approved templates and per-conversation fees; this works today for
free and the API can slot in behind it later.
Verified end to end against a local SMTP server: reset issues and consumes its
token exactly once, the old password stops working, an unknown address gives
the same answer, share mail reaches several recipients. Link behaviour checked
for expiry (410), revocation (410), wrong passphrase (401) and download lock
(403). Outbound SMTP is blocked in the sandbox, so the real mailbox still needs
one test from the live server.
Also fixes a regression this introduced: the seventh nav item pushed the logout
button off screen at 820px. The bar now shrinks and scrolls, logout never does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BNhLunz586g6DeYMa7Qsd1
It slipped in from a local UI check. The Docker build installs devDependencies,
and playwright's postinstall would try to download browsers there - the same
class of failure as the tsx/esbuild one.
The crop frame had width:100% together with max-height, so the height was
clamped while the width stayed - the frame lost the target aspect ratio and
object-fit:fill stretched the picture into it. Measured at 390px: frame 0.843
instead of 0.778, picture 8.3% too wide. The width now follows from the allowed
height and the ratio, and the image carries its own aspect-ratio instead of a
second percentage, so it cannot stretch at all.
Dragging was fine but felt dead: at fill zoom the crop sits at the stop in one
axis. The editor now states which way it can move, double-tap zooms, arrow keys
nudge, and the zoom slider shows its factor.
Sizes: 41 -> 86 in ten groups, including US inch sizes, instant-film picture
areas, cards, DIN A0-A7 and eleven aspect ratios as physical sizes.
The tab is called Drucken now; /druck and /passbilder redirect there.
The tsx postinstall pulls its own esbuild binary, which fails inside the
Docker build ("Expected 0.28.2 but got 0.27.7") and broke the deploy.
Tests now run on node --experimental-strip-types, so the image installs
nothing extra.
renderCell brings a relative crop onto an exact physical size; missing bleed
pixels are copied from the edge instead of stretching the motif. Without a
crop it falls back to a centred cover crop, never a distorted fill.
buildSheetPdf writes a PDF page that is exactly the sheet size in mm with
hairline crop marks and an optional footer. Source resolution guards uploads
to the sources/ prefix and reuses the library visibility rules.