Commit Graph
334 Commits
Author SHA1 Message Date
sandrodachandClaude Opus 5.5 83408d120d fix(workspace): more room for notes on phones
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m57s
Almost no side margin, 14 px indent per level (22 px from tablet width
on), zoom and delete buttons only take width in the active row. Very deep
rows keep about 14rem and the note scrolls sideways instead of squeezing
the text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:55:00 +01:00
sandrodachandClaude Opus 5.5 56c1fa02e6 feat(mail): sender display name (SMTP_FROM_NAME, default MindSift)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m54s
A bare SMTP_FROM address gets the display name in the From header; a name
already in SMTP_FROM wins, the envelope keeps the bare address. Also a
field in the admin mail settings.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:47:38 +01:00
sandrodachandClaude Opus 5.5 1632dd22c4 fix(workspace): English slash commands, short texts, wider menu
/image replaces /bild (aliases bild/foto keep working), shorter
descriptions for /link, /image, /audio, /diktat; the menu is wider, the
command never wraps and long descriptions are cut off.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:47:38 +01:00
sandrodachandClaude Opus 5.5 1f63e08a73 feat(media): images and recordings in backups, exports and imports (M6)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m52s
- Backup ZIP (download and automatic per-user server backups) carries the
  files of the user's own notes as media/<id><ext>; restoring creates
  them with new IDs and rewrites the blocks.
- Markdown archive puts them under attachments/ and embeds them relative
  to each note (![caption](../attachments/…)); re-importing turns them
  back into image/audio blocks.
- Obsidian import takes images and audio a note embeds on its own
  (![[file.png]], ![](path/file.png)); unused or invalid files appear as
  skipped in the preview instead of vanishing.
- Format detection and file paths moved into storage for this.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:41:26 +01:00
sandrodachandClaude Opus 5.5 3f2bc96732 feat(workspace): dictation via /diktat (M4)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m59s
/diktat (only in an empty bullet, only with voice input available)
listens right away; the bullet becomes one button with the live wave
across it and a translucent "Stopp" on top. A tap ends it and the
recognised text lands in the bullet as plain text. Uses the configured
speech recognition (own Whisper server or the browser); Esc cancels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:30:15 +01:00
sandrodachandClaude Opus 5.5 3f5e3b2ac1 feat(workspace): voice notes via /audio (M3)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m10s
- /audio (only in an empty bullet) shows a recording bubble: green round
  button starts, it turns red with a live wave and timer on its left, a
  tap saves the recording as an inline audio player. An audio file can be
  uploaded instead; Esc/✕ restores the empty bullet.
- Chrome's WebM recordings get their length measured, so the player shows
  the total time and seeking works.
- With the own Whisper server "Transkribieren" turns the recording into
  the (searchable) caption.
- Loaded files keep their Blob, as the CSP rightly blocks fetch() on blob:
  URLs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:23:54 +01:00
sandrodachandClaude Opus 5.5 e8fa14a4cb feat(workspace): image blocks via /bild (M2)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m18s
- /bild (only in an empty bullet) turns the bullet into an inline picker:
  file, camera on phones, drag & drop, paste, or an https link the server
  copies. Esc/✕ restores the empty bullet; nothing is synced before the
  upload succeeded.
- Photos are shrunk to 2048 px and stored as WebP (JPEG fallback); the
  re-encoding drops EXIF data such as the location.
- Image block: loaded with the login as blob URL, click opens full
  screen, optional caption kept in the block content
  (![caption](media:id)).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 23:14:08 +01:00
sandrodachandClaude Opus 5.5 177fa49363 feat(media): storage for images and audio in notes (M1)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m4s
- Table media (migration 5), files in $DATA_DIR/media/<docId>/.
- POST /documents/{id}/media (write access), POST .../media/fetch copies an
  external https image (SSRF-guarded, not in the demo), GET /media/{id}
  with range requests for readers of a note that shows the file.
- Format detected from the content (no SVG); own sandbox CSP on answers.
- Blocks point to files through their content (media:<id>), so the note
  sync needs no change; compaction removes files no block mentions any
  more after a day. Demo reset and demo start clear the folder.
- Limits MEDIA_MAX_IMAGE_MB, MEDIA_MAX_AUDIO_MB, MEDIA_QUOTA_MB,
  MEDIA_DEMO_MAX_MB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:57:59 +01:00
sandrodachandClaude Opus 5.5 e385a44fb9 chore(docker): explanations in .env.example, whisper as commented block
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 8s
- docker-compose.yml only maps variables; every variable is explained in
  .env.example (German, with defaults, units and choices).
- The whisper service is a commented block to switch on instead of a
  compose profile.
- TZ, TRUSTED_PROXIES, LOGIN_PROTECTION, DEMO_RESET_MINUTES and
  LEGAL_*_CONTENT are now passed through (they had no effect from .env).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:51:57 +01:00
sandrodachandClaude Opus 5.5 d396d42acf docs(changelog): hold-to-record cancels by dragging up
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 7s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:49:32 +01:00
sandrodachandClaude Opus 5.5 d52992afd2 Merge branch 'changelog_backlog' into dev
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 8s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:49:02 +01:00
sandrodachandClaude Opus 5.5 cc68d3f334 fix(workspace): keep the cursor in a block that turns into a code block
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m58s
Converting via /code or ``` swapped the text field for the code editor's
and left the focus nowhere, so further typing triggered app shortcuts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:48:35 +01:00
sandrodach b624093980 docs(changelog): backfill release history from v0.1 to v0.5.2 2026-10-29 22:42:43 +01:00
sandrodachandClaude Opus 5.5 9124809f54 Merge branch 'feat/code-block' into dev
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:37:17 +01:00
sandrodachandClaude Opus 5.5 a9d0e49159 Merge branch 'feat/sidebar-explorer' into dev
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:37:17 +01:00
sandrodachandClaude Opus 5.5 7fa21e485b feat(voice): all microphone buttons use the server's speech recognition
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m56s
- New useVoiceInput hook (lib/voice.ts): "server" records with MediaRecorder
  and sends to /voice/transcribe, "browser" keeps the live recognition,
  "off" or missing browser support hides the microphone.
- Quick dump bar, mobile capture sheet and hold-to-record use it; live
  waves from the real input level (VoiceWave) and "Wird erkannt…".
- Recordings stop after STT_MAX_SECONDS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:23:48 +01:00
sandrodach ff53f06991 feat(workspace): code block with language picker and live highlighting 2026-10-29 22:18:03 +01:00
sandrodachandClaude Opus 5.5 7801cd13b9 feat(voice): self-hosted speech recognition via Whisper (STT_*)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m49s
- STT_URL switches voice input to an OpenAI-compatible Whisper server
  (e.g. speaches); without it the browser's recognition stays. Configured
  by environment only, with limits for upload size, parallel jobs, hourly
  budget per user and timeout. The demo always uses the browser.
- POST /voice/transcribe forwards recordings and returns the text; nothing
  is stored. On startup MindSift waits for the server and asks it to
  download the model if missing.
- Admin card shows the active mode and server state, with a test recording.
- docker-compose: optional "whisper" service (speaches 0.8.3 CPU, profile
  "whisper", models in ./whisper).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 22:12:57 +01:00
sandrodach 9f3c78dde0 feat(workspace): sidebar as a plain file explorer 2026-10-29 22:08:35 +01:00
sandrodachandClaude Opus 5.5 6a5e82431f chore(release): v0.6.1
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m6s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:20:00 +01:00
sandrodachandClaude Opus 5.5 fbc404fea1 fix(notes): no self-conflicts when typing in a rhythm
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m51s
Several unsent text changes of one block were applied one by one to the
local view, numbering local versions ahead of the server, which folds them
into one change (formBatch). The focused field kept the inflated version as
base, so the next edit after a sync conflicted with the user's own text.
The local view now applies pending ops as they will be sent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 21:13:02 +01:00
sandrodachandClaude Opus 5.5 b55334b163 chore(release): v0.6.0
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m8s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:40:59 +01:00
sandrodachandClaude Opus 5.5 090d11d4c3 refactor(capture): memoize writable lists in the capture bar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:40:59 +01:00
sandrodachandClaude Opus 5.5 ef16778ef5 Merge branch 'feat/single-list-shortcut' into dev
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 20:36:11 +01:00
sandrodachandClaude Opus 5.5 9d9698186c fix(security): demo sends nothing, hidden SMTP password, body limits (FLAW-33..38)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m52s
- Demo: senders for mail, Discord and webhooks refuse, no alarm ticker;
  test alarm, SMTP test, server settings, backups, account and session
  changes of the shared demo accounts answer 403; the session list only
  shows the own device. The UI shows the notices and disables the controls.
- SMTP password is never sent back (smtpPassSet), empty keeps it.
- ?token= on webhook ingest accepts only API keys.
- Request bodies capped at 2 MB (sync 8 MB, imports 101 MB).
- Deleting an account removes its server backups.
- Backup downloads, exports and import uploads get 30 minutes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:43:23 +01:00
sandrodachandClaude Opus 5.5 3709282a4f fix(auth): longer IDs and invite codes, register limits, single-use invites (FLAW-32, FLAW-39, FLAW-40)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m2s
- NewID uses 96 random bits; invite codes get their own 100 bits in Base32.
- Registration and forgot-password are limited per address via loginGuard.
- The invite is claimed atomically before the account is created and
  released again when the account cannot be created.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:28:09 +01:00
sandrodachandClaude Opus 5.5 1af3a6e90a feat(auth): server-side sessions and current password for account changes (FLAW-30, FLAW-31)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m52s
- Every login is a session (device, IP, last use); the token's jti is its ID.
  Sessions end on logout, per device from the profile, or after 30 idle days.
- Tokens older than a day are renewed via the X-Session-Token header.
- Changing password or login e-mail needs the current password; a new
  password logs out all other devices and mails the old address.
- Demo reset keeps the demo visitors' sessions.
- revoked_tokens is replaced by sessions (migration 4); everyone is logged
  out once after the upgrade.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:25:22 +01:00
sandrodachandClaude Opus 5.5 b2ecda9896 fix(security): safe links and security headers (FLAW-28, FLAW-29)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m48s
safeHref lets only http, https and mailto become links (Markdown viewer,
outliner, legal links); javascript: and friends show as text. A
middleware sets a Content-Security-Policy (scripts only from MindSift,
no framing, connections only to this server), X-Frame-Options,
nosniff, Referrer-Policy and a Permissions-Policy. The two inline
scripts of index.html moved to files so script-src stays 'self'.

The second inline script also deleted every cache on each page load,
which undermined the offline start; it is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:02:13 +01:00
sandrodachandClaude Opus 5.5 3ca4ff7bce chore: Semantic Versioning and a changelog from now on
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m50s
Tags become three-part (v0.6.0), the header shows the full version and
pre-release tags no longer move :stable. CHANGELOG.md (Keep a Changelog)
starts with an Unreleased section; older versions will be added from the
history. The quantity field opens the number keypad on phones.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:55:32 +01:00
sandrodach 3a27a37acb feat(triage): skip list selector when only one writable list exists 2026-10-29 18:49:35 +01:00
sandrodachandClaude Opus 5.5 ff27d07aa3 docs(flaws): FLAW-33 covers outgoing alarms of a public demo
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 9s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 18:39:20 +01:00
sandrodachandClaude Opus 5.5 6cbfedd013 fix(lists): typing into the shopping list on phones
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m1s
The + button and the menu entry focused the new-item field 60 ms after
the tap; iOS only opens the keyboard for focus() inside the user gesture,
so the field was selected but nothing could be typed. Focus now happens
right in the tap. The list inputs use 16px on phones, so iOS no longer
zooms in and pushes the field under the keyboard. Version 0.5.2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:28:43 +01:00
sandrodachandClaude Opus 5.5 3c31a22ba0 fix(capture): + button opens the capture sheet again in triage and tasks
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m59s
Hold-to-record opened the sheet on pointerup; the click the browser sends
right after landed on the new sheet's backdrop and closed it at once.
Lists and notes have no such backdrop and kept working. A short tap now
opens on the click itself (as before), and after a hold that click is
ignored. Tests send the real pointerup + click sequence. Version 0.5.1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:12:03 +01:00
sandrodachandClaude Opus 5.5 fcdf44a0ad docs: fresh review – new flaw list (FLAW-28 to 41) and feature ideas
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 8s
The old list (FLAW-01 to 27) was done and is removed; references to it
in other documents are gone or replaced by the content. New findings
from a review after v0.5, among them a high one: javascript: links in
task notes run on click (Markdown viewer), which with the session in
localStorage allows taking over accounts through a shared task list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 17:02:02 +01:00
sandrodachandClaude Opus 5.5 13a7c9f08d release: v0.5 – security hardening, backups, demo reset, API key scopes, hold-to-record
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 2m0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:54:09 +01:00
sandrodachandClaude Opus 5.5 373987292f fix(capture): hold-to-record works by keyboard, confirms only after saving; drop FLAW-16
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m43s
The + button lost its click handler for pointer events, so keyboard,
VoiceOver and switch control could no longer open the capture sheet; a
click without pointer (detail 0) now taps. "Im Quick Dump gespeichert"
appears after the save succeeded, and the cancel state uses an existing
border color. FLAW-16 (setup open until the first account, demo
accounts) is removed from the list: intended behaviour, not a flaw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:35:26 +01:00
sandrodachandClaude Opus 5.5 ac2d9e1412 Merge branch 'feat/hold-to-record' into dev: hold the + button to record a thought
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:31:29 +01:00
sandrodachandClaude Opus 5.5 64e077dd57 feat(backup): admin and account UI for server backups, import by section
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m44s
Admin settings: switch, time, retention, list with download and a
"Jetzt sichern" button. Account settings: own server backups with
download and restore (replace or add, for chosen sections, replace needs
a second tap). The MindSift import gets the same section picker.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 16:11:49 +01:00
sandrodachandClaude Opus 5.5 c97765e08e feat(backup): daily server and user backups, restore by section (backend)
Admins switch automatic backups on, pick the time and the retention
(default 03:00, 3 days). Each run writes a gzip snapshot of the whole
database (VACUUM INTO, consistent while others write) and one MindSift
backup ZIP per active user below $DATA_DIR/backups, then deletes files
past the retention. A restart after the time catches up on that day.
Admins list, download and trigger runs; users list, download and restore
their own backups.

Import and restore can be limited to sections (notes, tasks, lists,
inbox, themes): restore only empties the chosen sections. Export and
import now carry custom themes. File names are checked against a fixed
pattern, so no other file can be fetched through the API.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:10:06 +01:00
sandrodachandClaude Opus 5.5 cab5a79f6d refactor(storage): one definition of task list access (FLAW-20)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m56s
accessibleTaskListsSQL in tasklists.go defines which task lists a user
can see; GetTaskLists, GetTaskListByID and the task queries use it
instead of three copies of the same subquery.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:05:05 +01:00
sandrodachandClaude Opus 5.5 0fdf46635b test(frontend): task dialog, sharing avatars, trash, API key scopes, passwords (FLAW-19)
Covers opening and closing the task dialog (the former hook-order bug),
date/time fields and the view-only mode for viewer lists, the avatar
stack of shared items, the two-tap delete in the trash, scope selection
and badges of API keys, and the shared password rule.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:03:54 +01:00
sandrodachandClaude Opus 5.5 4c50d15915 chore(frontend): real ESLint instead of a placeholder; fix hook order (FLAW-19)
eslint.config.js with the recommended JS/TypeScript rules and the React
hooks rules; npm run lint runs ESLint and is part of the image build.
Errors fail the build, the 24 exhaustive-deps findings stay warnings.

It found two real bugs: TaskDetailModal and ManageColumnsModal called
their hooks after 'if (!isOpen) return null', so the hook count changed
whenever the dialog opened or closed. Both are now a thin wrapper plus a
content component. Also removes unused imports and dead code (old task
list share handlers, an unused realtime status, an unused rollup).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 15:02:52 +01:00
sandrodachandClaude Opus 5.5 6b56a33500 fix(db): report write errors, multi-step changes all or nothing (FLAW-14)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m4s
Store.execInTx runs related statements in one transaction and returns
the first error. Used for deleting and moving folders (with the trash),
shared folders, folder shares and mounts, deleting task lists and
accounts, pinning, and the password reset (new password and the end of
all other links together). Writes whose success the API reports now
pass their error to the client (logout, new password, setup, sharing
mode, new group, restore, unlinking a note block), follow-up tasks and
alarm markers log failures. Remaining ignored writes are listed as
intentional in flaws.md.

Found on the way: DeleteTaskList unlinked all tasks before checking
ownership, so a stranger with the list ID could empty it; viewers could
unlink tasks from note blocks. Both fixed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:59:23 +01:00
sandrodachandClaude Opus 5.5 792ca946f8 fix(db): transactions wait for the write lock instead of failing (FLAW-23)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 58s
A load test with 16 concurrent writers lost 12 of 480 writes with
SQLITE_BUSY: transactions that read before writing cannot be helped by
busy_timeout once another connection writes. _txlock=immediate makes
every transaction take the write lock at BEGIN, so it queues instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:51:01 +01:00
sandrodachandClaude Opus 5.5 dc44f8250c fix(db): numbered migrations that run once and fail loudly (FLAW-13)
schema_migrations records every applied step; the former migrate() is
step 3, the baseline that builds the v0.4.1 schema and repairs older
databases. Columns are only added when missing, so every other error now
stops the start with a clear message instead of being swallowed, and the
cleanup statements no longer run on each start. migrations/001_init.sql
was never read and is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:49:35 +01:00
sandrodachandClaude Opus 5.5 f57a9884d1 fix(security): stream tickets instead of session tokens in URLs (FLAW-22)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m32s
WebSocket and EventSource put the 30-day session token into the URL,
where proxies log it. The app now gets a one-time ticket right before
connecting (POST /auth/stream-ticket, 60 s, bound to the token version);
?token= is no longer accepted for streams, non-browser clients may use
the Authorization header.

With BASE_URL set, CORS allows only that origin and WebSocket upgrades
only it or the requested host; credentials are off (no cookies exist).
Logout, ending all sessions, a new password and deactivation close all
live streams of the account; clients with a valid session reconnect
with a fresh ticket.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:46:58 +01:00
sandrodachandClaude Opus 5.5 5c711c2cf7 feat(api-keys): scopes for triage, tasks and list items (FLAW-07)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 1m33s
Keys get scopes when created (webhook:triage, webhook:task,
webhook:list; default triage, old keys become triage). The webhook
ingest learns target task (optional task list) and list_item (needs a
list the owner may write to); /inbox checks the scope of its target as
well; a missing scope answers 403. Keys can no longer open live streams,
which carried all of the owner's events.

The visible key UI lived in the account settings while EcosystemView
was never rendered; it is replaced by ApiKeysPanel in the settings with
scope chips, scope badges per key and a simulator for all three
targets. Examples send the key as X-API-Key header, not in the URL.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:36:17 +01:00
sandrodachandClaude Opus 5.5 3900c6104e fix(security): list items need write access to their list (FLAW-27)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
Creating, checking, editing, deleting, reordering and clearing list
items did not check the list at all: any logged-in user with a list or
item ID could change it, viewers of shared lists as well. All item
endpoints and converting a thought into a list item now require owner
or editor rights and that the item belongs to the list (404 without
access, 403 for viewers).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:28:39 +01:00
sandrodach eabb018c64 feat(capture): hold mobile + button to record voice thoughts directly to inbox 2026-10-29 14:27:42 +01:00
sandrodachandClaude Opus 5.5 11c14fa9ff feat(auth): lock logins after repeated failures, trust proxies explicitly (FLAW-09)
MindSift CI & Docker Release / Build, Test in Container & Push (push) Successful in 59s
Failed logins are counted per account+address (3), per account (20) and
per address (30) within 15 minutes; a full bucket answers 429 with
Retry-After for 15 minutes. Locking the account+address bucket first
means an attacker cannot lock the owner out from elsewhere. Unknown
names count like accounts. Success clears the account buckets, a
password reset (mail or admin) lifts the lock. LOGIN_PROTECTION=false
switches it off.

chi's RealIP believed X-Forwarded-For from anyone. The client address
now comes from the headers only when the connection is from a trusted
proxy: TRUSTED_PROXIES=private (default: loopback, Docker, LAN), all
(old behaviour), none, or CIDRs. A proxy that sends no client address
is logged once and per-address limits are skipped for it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 14:26:02 +01:00