Android self-bind protection auf nahezu MDM-Niveau ohne Device-Owner:
- Device-Admin (RebreakDeviceAdminReceiver) blockt Uninstall OS-seitig, aktiv ab
Boot ohne Prozess/a11y. Deaktivierung nur via 24h-Cooldown (removeDeviceAdmin in
forceDisable). a11y blockt die DeviceAdminAdd-Settings-Seite (Class-Match, auf
Samsung One UI per Logcat verifiziert).
- Boot-Receiver (RebreakVpnBootReceiver) startet VPN+a11y nach Reboot, damit der
Tamper-Lock ohne manuellen App-Start hochkommt.
- Manifest-Wiring (Device-Admin-Receiver, Boot-Receiver, RECEIVE_BOOT_COMPLETED,
device_admin.xml) ins with-rebreak-protection-android Config-Plugin verlagert →
ueberlebt 'expo prebuild' (android/ ist gitignored).
- a11y-Detection zurueck auf die funktionierende Version: zu breites 'loeschen'-
Uninstall-Keyword raus (blockte halbe Settings); a11y-Label jetzt 'ReBreak Schutz'.
- a11y-Deeplink behaelt den Samsung-Step-Guide (openAccessibilitySettings).
Session-Frontend in diesem Batch:
- Avatar-Placeholder: neutrales clarity-avatar-line SVG statt dominantem Blau.
- DiGA-Milestone folgt kumulativen protectedDays (erreicht rueckfall-anfaellige User).
- Dev-Build crasht nicht mehr ohne CallKit-Native-Modul.
- VPN-Permission-Dialog nur noch im Bypass-Fall.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
MDM-VPN-Pivot (Phase F.2 done):
- ops/mdm/profiles/rebreak-iphone-protection.mobileconfig auf v5 mit
com.apple.vpn.managed Payload + OnDemandUserOverrideDisabled. iPhone-User
kann ReBreak-VPN-Profile nicht entfernen und "Bedarf verbinden"-Toggle
ist disabled. allowEnablingRestrictions empirisch widerlegt für FC-Toggle-
Lock — out.
- DEV-removable Variante als Test-Profile dazu.
- Bootstrap-Tool (rebreak-supervise.sh) + Supervision-Identity-Setup-Doc.
- PHASES.md updated mit empirischen Befunden.
App-side MDM-Detect (Pfad-a Banner-Logic):
- modules/rebreak-protection: getDeviceState() returnt mdmManaged via
Heuristik NETunnelProviderManager.count > 1 (App selbst kann nur einen
eigenen erstellen, MDM-Push fügt einen zweiten hinzu).
- DeviceLayers.mdmManaged?: boolean Type.
- blocker.tsx: lockedIn-Bedingung erweitert um mdmManaged. Bei MDM-managed
iPhones wird der App-Lock-Card (FC-Authorization-Toggle UI) ausgeblendet
weil der per-App FC-Toggle nicht lockbar ist und durch den MDM-VPN-Layer
redundant.
Layer-2-Country-Curated-Pivot:
- backend: vip-swap.post.ts raus, suggest.post.ts rein. Curated-domains
durch admin (separate Tabelle/Pfad), getrennt von User-Custom-Domains.
- Admin-APIs für curated-domain Pflege (index.get + [id].patch).
- seed-country-blocklists Script für initiale Curated-Domain-Liste.
- protection/webcontent-domains.get refactored für Country-Curated-Pfad.
- Migration drop_vip_swap_fields.sql + schema.prisma adjusted.
- docs/concepts/layer2-country-pivot.md mit Architektur + Decision-Trail.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Neuer iOS-Layer-1-Filter: ein NEPacketTunnelProvider-DNS-Sinkhole — MDM-frei,
ab iOS 16, Parität zum Android-VPN-DNS-Filter. Ersetzt den Apple-seitig
blockierten NEURLFilter als Default. NEURLFilter-/PIR-Code bleibt inaktiv als
iOS-26-Upgrade-Pfad erhalten (User-Entscheidung).
Neues Extension-Target RebreakPacketTunnelExtension/:
- PacketTunnelProvider.swift — TUN-Setup (virtuelle DNS-IP 10.0.0.1, nur diese
Route ins TUN), Read-Loop, NXDOMAIN-Sinkhole, Upstream-Forward via
NWConnection zu 1.1.1.1, Blocklist-Reload via Darwin-Notification.
- DnsFilter.swift / HashList.swift / DomainHasher.swift — Swift-Ports der
Android-DNS-Filter-Logik. blocklist.bin-Format (sortierte big-endian UInt64,
SHA-256-Prefix) 1:1 beibehalten, mmap statt Heap-Load.
RebreakProtectionModule.swift:
- activateUrlFilter startet jetzt den Packet-Tunnel via NETunnelProviderManager
(Default-Layer-1, On-Demand-Auto-Reconnect aktiv).
- NEURLFilter-Code in activateNeUrlFilter ausgelagert (inaktiv, behalten).
- getDeviceState/disable lesen bzw. stoppen den Tunnel-Status.
with-rebreak-protection-ios.js: zweites app_extension-Target, klassischer
Embed-Pfad (dstSubfolderSpec 13), packet-tunnel-provider-Entitlement + App-Group.
app.config.ts: zweites appExtensions-Target.
NICHT auf echtem Gerät verifiziert — NE-Packet-Tunnel laufen nicht im
Simulator. Ungetestete Annahmen im Code mit "UNGETESTETE ANNAHME" markiert.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
syncWebContentDomains (gespiegelt von syncBlocklist): holt die Domain-Liste vom
Backend, cached sie als webcontent-domains.json im App-Group-Container, ETag/304,
Reapply nach Sync wenn FC aktiv. loadWebContentDomains liest cache-first, faellt
auf die gebuendelte gambling-domains.json zurueck (Offline-Seed). Getriggert am
selben Punkt wie syncBlocklist (useBlocklistSync).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
WebKit-interner Content-Filter via ManagedSettingsStore().webContent als
stilles Sicherheitsnetz. Blockt eine kuratierte, laenderabhaengige Top-
Gambling-Domain-Liste plus systemseitig Adult-Content (.auto-Variante).
Braucht NUR Family Controls — kein MDM, kein neues Entitlement, keine
Config-Plugin-Aenderung.
- gambling-domains.json: gebuendelte Starter-Liste (DE/GB/FR), je <=50
Domains (Apple-Hartlimit), klar als STARTER markiert. Via Podspec-
resource_bundles ins App-Bundle gepackt.
- applyWebContentFilter / clearWebContentFilter: zwei native AsyncFunctions.
Land via Locale.current.region, iOS 16+ gegated, FC-Auth vorausgesetzt.
- JS-Bridge (Module-Decl, types, web-stub, lib/protection.ts) + Actions im
useProtectionState-Hook. getDeviceState liefert webContentFilter-Layer mit.
KEINE Auto-Trigger-Logik — Layer 2 ist vorerst nur explizit aufrufbare
Capability. Siehe TODO(layer2-gating) im Swift-Modul und lib/protection.ts.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
State of work before Duo-style onboarding pivot. Includes work that will be
partly reverted in the next commit (see refactor follow-up).
Onboarding (will be partly reverted):
- Custom Tooltip+Glow spotlight (components/OnboardingHint.tsx)
- Spotlight wiring in app/profile/edit.tsx (nickname-input glow + step-progress
header, onSubmitEditing auto-save, save-handler routes to /(app)/blocker)
- Spotlight wiring in app/(app)/blocker.tsx (URL-filter LayerSwitchCard wrapped
+ auto-PATCH step='done' when filter activates)
- Routing-gate branches in (app)/_layout.tsx (welcome → /onboarding/welcome,
nickname → /profile/edit)
- Debug-Reset-Toggle in /debug (welcome|nickname|block|done buttons + redirect)
Will stay (reused in Duo flow):
- Welcome-Screen app/onboarding/welcome.tsx (will become Slide 1)
- Avatar-fix in profile/edit (Dicebear seed stays stable while typing)
i18n + RTL:
- Arabic locale (locales/ar.json, full translation incl. onboarding keys)
- I18nManager.allowRTL(true) + applyRTL helper in stores/language.ts
- Language-Picker option for العربية in settings
- New keys: onboarding.welcome.*, step_progress, nickname_spotlight.*,
block_spotlight.*, permission_denied.*, language.*, rtl_restart.* (de/en/fr/ar)
NEFilter Permission Recovery (iOS):
- Swift resetUrlFilter() — removeFromPreferences + fresh saveToPreferences to
bypass iOS's cached denied-state (NEFilterErrorDomain code 5)
- TS module def + lib/protection.ts wrapper
- components/PermissionDeniedSheet.tsx — branded recovery sheet with retry +
app-settings:// deep-link + fallback hint
- Wired in (app)/blocker.tsx handleActivateUrlFilter (code-5 detection)
Misc:
- Bug fix in onboarding/welcome.tsx: apiFetch body was double-stringified (sent
as JSON string instead of object → 400 invalid_step)
- Bug fix in profile/edit.tsx: avatar preview Dicebear seed switched from live
nickname (changed every keystroke) to stable me?.nickname
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
After an APK reinstall (or an OS low-memory kill that START_STICKY didn't recover
promptly), the VpnService dies but `filter_enabled` stays true. isVpnEffectivelyOn
then reports vpn:true (from the flag) → tamperLock:true → lockedIn:true → the green
"protection active" card with no toggles, while in reality nothing is filtering.
New native reconcileVpn(): if `filter_enabled` && !RebreakVpnService.isRunning &&
VpnService.prepare()==null → startVpnService(). Wired into _layout.tsx enforceProtection()
(runs on launch / foreground / 15s poll), called before reading combined state. No-op
on iOS/web. If the VPN consent was revoked, isVpnEffectivelyOn already clears the flag,
so that case self-resolves too.
Net behavior: while `filter_enabled` is true (user hasn't exited via the cooldown),
the app keeps the VPN alive. Exiting still goes through the cooldown → forceDisable →
filter_enabled=false → reconcile leaves it off. DiGA-compliant.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>