community/posts.get.ts + social/profile/[userId].get.ts lieferten neben
nickname auch username an fremde Clients — username ist der Login-
Identifikator ({username}@rebreak.internal) und verletzt die Nickname-
Anonymitäts-Invariante (REQ-COMM-005 / R-DATA-07) + exponiert das halbe
Login-Credential-Paar. Frontend rendert das Feld nirgends (verifiziert);
totes Typ-Feld in stores/community.ts entfernt.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- mail/scan{,-internal}.post.ts + imap-idle: attach imap.on('error')
+ targeted uncaughtException/unhandledRejection guards so a
connection-level IMAP error (ECONNRESET / TLS disconnect) can no
longer propagate to a process-level uncaughtException and kill the
Nitro API (root cause of the staging 502 crash-loop)
- services/push.ts: lazy dynamic-import expo-server-sdk (singleton,
like voip-push.ts) to fix "Class extends value [object Module]"
(ESM/CJS undici interop) that broke DM push notifications;
+ nitro.config externals safety net
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Härtung der öffentlich downloadbaren Magic-Apps gegen Reverse Engineering
(Assessment: docs/specs/magic-re-hardening.md):
- Windows: protection.json per ACL auf SYSTEM+Admins (DNS-Token nicht mehr von
Standard-Usern lesbar) — setup.rs
- Mac: MacProfileInstaller.remove() + Debug-Supervision-Modi/Reset nur noch
#if DEBUG (kein Removal-/Debug-Pfad im Release-Binary)
- Mac: staging-URL einmal als Konstante statt 4x Literal; interne Infra-Notizen
aus String-Literalen raus
- Backend: Rate-Limit (10/IP/min) auf /api/magic/pair/redeem
NUR Backend-Teil deployt via Push; Mac/Win brauchen Xcode-/Cargo-Release-Build
(zied) + Smoke-Tests vor Release. MagicAPIClient.swift trägt etwas vorbestehenden
WIP mit (gleiche Magic-Client-Domäne).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Devices/Magic:
- Offline-Profil-Enroll deaktiviert (410) — Lock-PW würde im Klartext im
Download landen; stationärer Schutz läuft jetzt nur über Rebreak Magic
- Mac-DNS-Template: ProhibitDisablement (Filter nicht abschaltbar)
- Push "Neues Gerät verbunden" an mobile Geräte bei neuer Bindung
- Realtime auf user_devices → Settings aktualisiert Magic-Bindings live
- Geräte-Detail-Sheet (Tap auf Gerät): Status, verbunden-seit, Schutz-Donut
Hard-Lock (server-gehaltenes Removal-PW, User sieht es nie):
- magic_removal_password generiert/gespeichert + in Profil injiziert (Lazy-Backfill)
- Reveal NUR bei Account-Löschung (user/delete) + Kündigung (stripe webhook),
per Resend-Mail + in-Response
- Signing config-gated (inaktiv ohne Cert; Lock greift auch unsigniert)
Migrations: user_devices-Realtime-Publication + magic_removal_password-Spalten
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Apex-only-Format (bare domain) blockte nur z.B. bet365.com, NICHT
www.bet365.com — Casino-Content + Google-Ad-Links liegen aber auf www.
Adblock-Syntax ||domain^ deckt Domain + alle Subdomains ab.
Betrifft alle DNS-Plattformen (Mac/Windows/DoH-Server).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Schliesst hans-muellers K1-Befund (Datenschutz-Audit): der Coach-Prompt
sendete Identifier + Art.9-nahe Daten an US-LLMs (Gemini/OpenAI/Anthropic).
- message.post.ts: Geburtsjahr/exaktes Alter -> Altersgruppe (Dekaden-Bucket);
Stadt komplett entfernt (Bundesland bleibt). Geschlecht/Familienstand/Beruf/
Nickname unveraendert (gewollte Personalisierung; Nickname = Pseudonym).
- lyraMemoryExtract.ts: Extraction-Prompt reduziert Dritt-Klarnamen auf Rolle
("Frau Maria" -> "seine Frau"), keine Orte/Arbeitgeber im Memory-Content.
- 08-datenschutz-audit: Payload-Audit-Platzhalter durch Vorher/Nachher-Tabelle
ersetzt, K1 erledigt, ZDR-Update (DPA/SCCs deemed-signed, TIA offen).
Pseudonymisierung zaehlt jetzt als zweite Schutzmassnahme neben ZDR fuer die TIA.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
New 'erinnerung' topic for manual Lyra community posts that gently remind
users they can add optional, anonymous profile details. Wording stays
jargon-free (no 'DiGA'/'data'/'study'). Manual-only, not in the auto-cron
catalog.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Domain/display-name adds now force a full re-scan so newly-added gambling
senders are caught immediately instead of waiting for the incremental UID
window. IMAP-idle NOOP tick lowered 2min -> 30s to close the Junk-folder gap
faster (Outlook drops straight into Junk, which IDLE does not watch).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The old streak was non-functional: streaks.current_days was always 0 (never
computed/incremented), and the profile page read me.streak (0) + account
created_at as the "since" date — showing "0 days protected since <signup>"
for everyone. This is the DiGA key metric, so it had to be rebuilt.
New model: optimistic protection-coverage based on actual VPN/MDM protection
state, never resets to 0.
- backend: append-only protection_state_log + migration; POST /api/protection/event
(ingestion, deduped) + GET /api/protection/coverage (read-time compute, no cron);
server-side cooldown_disable event on cooldown resolve. Generous >6h-off/day rule.
- frontend: report protection on/off transitions (initial + flips, deduped) from
useProtectionState; rewrote profile StreakSection → half-donut (protected vs
unprotected) + progress bar (current streak → personal record) + empty state.
- coverage starts fresh from deploy (no historical backfill — clean data for DiGA).
- spec: docs/specs/protection-coverage-streak.md (shared contract).
- old streaks/streak_events/profiles.streak left intact (coach/scores consumers).
Also adds go-to-market one-pagers under docs/marketing/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Der inkrementelle Scan-Pfad rief imap.search({ uid: 'X:*' }) ohne das
zweite { uid: true }-Argument auf → ImapFlow sendet "SEARCH UID X:*"
statt "UID SEARCH UID X:*" → Server antwortet mit Sequence-Numbers.
Die nachfolgende fetchAll(..., { uid: true }) interpretiert diese als
UIDs → fetcht die falschen (alten) Mails → neu eingegangene Gambling-
Mail (höchste echte UID) wird nie klassifiziert/gelöscht (>15min Lag).
Auch Ursache des "Command Error. 10" Log-Spams (Mega-UID-Liste).
Fix: { uid: true } als zweites search()-Argument.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
imapflow.status() liefert uidValidity als BigInt. Der Code reichte den BigInt in
JSON.stringify (patchFolderScanState) → 'TypeError: Do not know how to serialize
a BigInt' → vom stummen connection-level catch verschluckt → weder
patchFolderScanState noch markFullSweepDone liefen je → folder_scan_state blieb
{} + last_full_sweep_at NULL → inkrementeller Scan aktivierte nie (immer Full-Sweep).
Fix:
- serverUidValidity: Number((status).uidValidity ?? 0) — BigInt → number vor JSON.
- Stumme catches (auth/lock/conn) loggen jetzt; Persist-Calls (patchFolderScanState
x2, markFullSweepDone) in eigene try/catch mit console.error — Diagnostik bleibt
drin für Post-Deploy-Verify.
Lokal verifiziert: Build EXIT 0, imapflow extern.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Phase-2-Rebuild reaktivierte den bekannten imapflow/node-apn util.inherits-Bundle-
Bruch → scan-internal warf 500 → Mail-Filtern (USP) down. Rollback von
scan-internal.post.ts + db/mail.ts auf den funktionierenden Stand (5b57bea).
Schema (folder_scan_state, last_full_sweep_at) + Migration BLEIBEN angewendet —
kein Prisma-Drift; die Spalten warten ungenutzt auf den gefixten Phase-2-Retry.
Root-Cause (warum der inkrementelle imap.status/search-Pfad das Bundle bricht)
muss vor erneutem Phase-2-Deploy in der nitro-Externalize-Config gelöst werden.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend-Lag-Fix Phase 1 — entlastet die CPU-Dauerschleife im Mail-Stack:
- delete mail-scan-cron.ts: der 30-Min-Nitro-Cron scannte alle User parallel
(Promise.allSettled) und war redundant zum IMAP-IDLE-Daemon (Single Source
of Truth). Reine Dauerlast ohne Mehrwert.
- imap-idle: In-Flight-Guard (scanInFlight + coalescePending). triggerScan ist
jetzt re-entry-safe — pro Connection max. 1 aktiver + 1 pending Scan statt
bis zu 8 gestapelt pro 2-Min-NOOP-Tick. Gilt für NOOP + exists-Event.
- plan-features: Pro mailAgents 3->2 (+ Math.min-Hack in coach/message aufgeräumt).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- ring.post: log [ring] when triggered
- voip-push: log [voip-push] sent on success with env (prod/sandbox) + callId
- chat.ts sendDirectMessage: when attachmentType=='call' parse audio:<state>:<sec>
into proper preview (Verpasster Anruf, Anruf abgelehnt, Anruf (m:ss), \u2026)
so post-call push has body text instead of empty.
- callkit.startOutgoingCall: skip on Android (telecomManager opens dialer UI \u2014
wrong for in-app WebRTC; iOS-CallKit only for audio-session mgmt).
SOS (urge.tsx) uses /api/coach/message as a stateless LLM proxy for game
comments, share drafts and the stream fallback — sending SOS_BOOT +
[INTERN:] prompts. The endpoint persisted the full messages array into
coachSession for pro/legend users, so those internal prompts and the raw
JSON replies leaked into the Coach chat history as visible bubbles.
- Reactivate the sosMode flag (already sent by all three SOS call-sites):
when set, the endpoint skips coachSession persistence, memory extraction
and feedback detection — pure LLM proxy, no shared state.
- Add a defensive filter on /api/coach/history that strips internal
messages (SOS_BOOT, [INTERN:], [SYSTEM-HINT], raw JSON / [[CHIPS]]
replies) so already-contaminated sessions self-heal on next load.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Backend (voice-call groundwork, no call engine yet):
- Profile.callsEnabled (Boolean default true) + migration
- canCall(caller,callee): mutual-follow AND callee.callsEnabled — server-side hard guard
- POST /api/me/calls-enabled (opt-out toggle), GET /api/chat/can-call/:userId
- expose callsEnabled in /api/auth/me
Frontend:
- "Allow calls" toggle in Profile privacy section (default on, optimistic+rollback)
- Me.callsEnabled + i18n DE/EN/FR/AR
Bundled DM UI work from this session:
- image lightbox is now a swipeable carousel over all shared images (+ counter)
- keyboard stays open after sending (input ref refocus)
- voice notes: Instagram-style waveforms (own=white/mint, other=black/grey),
removed the blue progress dot; lazy-load expo-media-library with clean fallback
- expo-linear-gradient + expo-media-library deps
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Magic-Mac-Hub (/api/magic/devices):
- Filter boundToPlan war zu eng \u2014 iPhone/iPad ohne aktiven Plan-Lock
fielen raus. Jetzt: alle UserDevice-Rows des Users ausser den
magic-enrolled, plus ProtectedDevice mit Dedupe.
Native /devices Page:
- MacBook erschien doppelt: einmal als UserDevice (registriert via
Magic-Mac, model=Mac14,9) und einmal als ProtectedDevice (alter
DNS-Flow). Dedupe per platform-key (mac/ios/android/win):
wenn UserDevice mit gleicher Plattform existiert, blende
ProtectedDevice aus.
- Slot-Counter zaehlt jetzt nach dedupe (totalRegistered).
- GET /api/magic/devices fetcht jetzt parallel listMagicDevices()
+ listProtectedDevices() und merged beide Quellen in eine
Response. Items haben neues 'source' Feld (magic|protected).
- ProtectedDevice (alter Native-DNS-Flow) wird auf gleiche
Shape gemappt: label->hostname, platform->model.
- Mac-App MagicDevice: source-Feld optional + resolvedSource
Fallback fuer Backwards-Compat. id mit source-Prefix gegen
Collisions zwischen Tabellen.
- DeviceHubView Row: protected-Geraete bekommen graues
'Native-App' Badge und Hinweis 'Verwaltung in der
ReBreak-App' statt Trash-Button (Release laeuft dort).
serverAssets approach didn't bundle the template into the Nitro
output (no .output-staging/server/chunks/raw/ dir, no asset-storage
mount in nitro.mjs). Logs confirm: '[Magic] Profile template missing
in serverAssets'.
Drop serverAssets entirely. Inline the template (~2KB) as a TS
constant in backend/server/utils/magic-profile-template.ts. Build-
robust, no FS/storage dependency at runtime. Canonical source of
truth remains ops/mdm/rebreak-mac-dns-filter.mobileconfig — keep in
sync manually until/unless we add a codegen step.
Previously read template via process.cwd() + 'ops/mdm/…' — but pm2
runs the bundled output from /root, not the repo root, so the path
resolved to /root/ops/mdm/… (does not exist) → HTTP 500 'Profile
template not found' after Mac registration.
Switch to Nitro's serverAssets (baseName 'mdm', dir '../ops/mdm')
which is bundled at build-time and read via
useStorage('assets:server'). cwd-independent + survives any deploy
layout change.
Nitro auto-import did not pick up findMagicDeviceByToken / listMagicDevices /
countActiveMagicBindings / createAdGuardClient on first build. Added explicit
imports as safety net.
User generiert 4-stelligen Code in der App, setzt ihn manuell als
Screen Time Passcode → ReBreak speichert ihn auf dem Backend.
Damit kann niemand Screen Time deaktivieren → deny-removal bleibt
aktiv → App nicht deinstallierbar ohne den Passcode.
Backend:
- Profile.screentimePasscode Feld (Migration add_screentime_passcode)
- POST /api/protection/screentime-passcode — Code speichern
- GET /api/protection/screentime-passcode — Code abrufen (nach Cooldown)
iOS UI (blocker.tsx):
- ScreentimePasscodeCard erscheint wenn Layer 1 + 2 aktiv (iOS only)
- Code-Generierung → Einmal-Anzeige → Deep-Link zu Settings → Screen Time
- Bestätigung speichert Code auf Backend, Card zeigt Confirmed-State
Locales: DE/EN/FR/AR screentime_* Keys
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
iCloud-Sign-In Pattern: wenn ein neues Gerät versucht sich anzumelden
und das Plan-Limit erreicht ist, kann der User auf einem bereits
angemeldeten Gerät bestätigen — Code wird auf BEIDEN Geräten gezeigt
für visuellen Vergleich (verhindert Code-Forwarding-Attacken).
Backend:
- New table device_approval_requests + supabase_realtime + RLS
- POST /api/devices/approvals — create (new device)
- GET /api/devices/approvals — list pending (existing devices)
- GET /api/devices/approvals/:id — status poll (new device)
- POST /api/devices/approvals/:id/approve — approve + atomic evict
- POST /api/devices/approvals/:id/reject — reject
- POST /api/devices/approvals/:id/email — trigger email fallback
- POST /api/devices/approvals/email/:token — magic-link approve (no auth)
- Email-Template via Resend (lyra-neutral, security-formal)
- 10min TTL, 6-digit numeric codes (crypto-random)
Frontend (rebreak-native):
- DeviceApprovalIncomingSheet — existing devices: code + device-picker + Allow/Reject
- DeviceApprovalPendingSheet — new device: code + spinner + 'Send via email'
- useDeviceApprovalRealtime — postgres_changes subscription
- DeviceLimitReachedSheet — neues CTA 'Auf anderem Gerät bestätigen'
- i18n DE/EN/FR/AR
Migration läuft automatisch via prisma migrate deploy bei push.