Breaking wire-format change: dual-read (V1+V2) / single-write (V2) so
existing user secrets, stored messages, and inter-tenant key keep
working. Old clients and relays lose cross-version chat until both
sides are upgraded.
Relay
- Switch clientEncrypt/masterEncrypt to AES-256-GCM with HKDF-derived
per-ciphertext salts; keep CBC-V1 fallback on decrypt only.
- On-bootstrap migration re-envelopes tenant secret_encrypted and the
inter-tenant key to GCM-V2 (idempotent).
- CORS: stop reflecting arbitrary Origin; allow own origin + optional
RELAY_CORS_ORIGINS env allowlist; drop Access-Control-Allow-Credentials.
- Generic error messages replace "no such user: <name>" enumeration.
- Exception messages no longer echoed back; logged server-side.
- Collab-lock max hold capped at 10min so a member can't grief-refresh
forever.
- Attachment filenames validated against hex.<ext> pattern.
- Passwords min length 10; /api/account/password rate-limited.
App / Electron client
- Shared hyprcrdRelayEncrypt/Decrypt helpers (GCM-V2 write, V1 read).
- Chat + collab-buckets plugins delegate to the shared helpers.
- Poll + push + attachments migrated to the dual-format path.
- /api/relay/settings GET returns has_key boolean; secret never echoed.
- Setup endpoint now CSRF-checked.
- SSRF: CURLOPT_PROTOCOLS restricted to HTTP(S) everywhere; bookmarks
favicon fetch also blocks private/loopback/link-local.
- Login / password-change rate limiter moved to DB-backed IP keying.
- Passwords min length 10 throughout.
- Zip import enforces per-file (25MB) and total (500MB) caps, extension
allowlist, no-overwrite.
- Verbose relay-push debug gated behind APP_DEBUG env.
Version bumped to 2.0.0 (protocol-breaking).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
New plugin at public/plugins/bookmarks/ — bookmark lists stored as
cards tagged #bookmarks with a JSON body. Board cards render as
coloured tiles (70% card colour + surface) with a centred link icon,
title, and date/bucket metadata; no body preview.
Clicking opens a full-screen overlay with:
- Named columns (click header to rename, right-click for
rename + delete), + button to add columns
- Bookmark items: favicon (stored as data: URI via server-side
fetch-meta) + title with ellipsis. Click opens URL in new tab.
- Right-click item → rename or delete
- Positional drag-and-drop between and within columns — accent
drop-indicator line shows the insertion point
- Full-width slider sets relative column width, persisted per card
- Horizontal wheel-to-scroll on the column container
Adding a bookmark reads the clipboard; if it contains a URL, the
backend fetches <title> + favicon (as base64 data: URI, ≤32 KB) so
the card is fully self-contained. Non-URL clipboard → manual entry
dialog. Also accepts card:123 links — rendered with a card icon in
the linked card's colour; click opens the linked card's detail view
or, if it's another bookmark list, switches the overlay to it.
Multiple bookmark lists supported: header button always shows a
picker (even with one list) with a "new list" option. New lists
inherit the active bucket. Overlay title is click-to-rename.
Tests: plugin registration, header button injection, board card
decoration, overlay open/close + column + item rendering, fetch-meta
API shape, card-link icon rendering, column-width persistence.
Also fixes the flaky undo due_date test — replaced a fixed sleep
with waitForStateCard polling so the undo PATCH has time to settle.
Bumps to 1.9.23.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>