- ChatBubble: useActionSheet replaces custom Modal (native iOS popup, Android bottom sheet) - DM mode (isDM prop): hides like-count, shows Insta-style heart badge under bubble when liked - Group chat unchanged - Cleanup: remove unused Modal/Platform imports, sheet styles, actionsOpen state - deploy.sh: auto-detect ANDROID_HOME + auto-create local.properties for local Gradle - NEXT_RELEASE.md: DM reactions release note - Includes other staged work across binder-mac, marketing, ops/mdm, ios/
9.4 KiB
Unsupervised Sideload Profile — Test-Checklist
Datei: rebreak-iphone-unsupervised-sideload.mobileconfig
Generator: generate-unsupervised-profile.py
Distribution: Safari-Download via UNSUPERVISED-NGINX.conf
Architektur-Kontext
ReBreak hat zwei separate Schutz-Stacks je nach Device-Mode:
| Device-Mode | Layer 1 | Layer 2 (FC) | Layer 3 (Anti-Cooldown-Manipulation) |
|---|---|---|---|
| supervised + MDM | NEFilter (MDM-pushed) | Family Controls (App-Auth) | MDM-pushed com.apple.vpn.managed |
| unsupervised | NEPacketTunnel (App-managed) | Family Controls (App-Auth) | DIESES PROFILE (Sideload) |
Layer 3 auf unsupervised existierte vorher nicht — User konnte während Cooldown einfach App löschen oder VPN-Toggle aus, dann war Schutz weg. Dieses Profile schließt diese Lücke (so gut Apple's unsupervised-Constraints es erlauben).
Pre-Flight
- iPhone unsupervised (Settings → Allgemein → Info → KEINE "Dieses iPhone wird von ... überwacht"-Banner)
- iOS 16.0 oder neuer (
Settings → Info → Software-Version) - ReBreak-App via TestFlight oder Ad-Hoc installiert
- PacketTunnelExtension-Bundle vorhanden (verifizierbar: nach App-Start einmal Schutz manuell aktivieren → Settings → VPN zeigt "ReBreak Schutz"-Eintrag)
- Pre-existing ReBreak-Profile entfernt: Settings → Allgemein → VPN & Geräteverwaltung → falls vorhanden "ReBreak Schutz"-Profile → Entfernen
- Test-Removal-PIN bekannt (z.B.
482915)
Install-Test
# Auf Mac:
python3 generate-unsupervised-profile.py \
--removal-password 482915 \
--org "ReBreak Test" \
--output ~/Desktop/rebreak-schutz-test.mobileconfig
# AirDrop oder via Safari-Download an iPhone übertragen.
# Safari-Path (production-realistisch): Profile auf nginx hosten, iPhone öffnet URL.
- iPhone öffnet Settings-Profile-Install-Sheet automatisch
- iOS warnt "Nicht überprüft" (rot) — erwartet, weil unsigned. User muss "Trotzdem installieren" tippen.
- ConsentText wird vor Install gezeigt (Casino-Schutz-Erklärung)
- Device-Passcode-Prompt erscheint
- Install-Success-Screen
Verifikation: was greift?
✅ DNS-Lock (sollte funktionieren)
Settings → Allgemein → VPN, DNS & Geräteverwaltung → DNSzeigt "ReBreak DNS-Filter"- Toggle ist grau/disabled (ProhibitDisablement greift)
- Browser-Test:
https://lotto.de→ blocked (DNS-resolved-IP wird vom ReBreak-DNS-RPZ rejected) - Browser-Test:
https://google.com→ loads (whitelist-pass) - Captive-Portal-Test: WiFi-Login-Page (
captive.apple.com) → muss laden (sonst Network broken)
✅ VPN-Auto-Connect (sollte funktionieren)
Settings → VPNzeigt "ReBreak Schutz"-Eintrag- VPN-Connection-Status: connected (oder verbindet sich binnen 5s nach Network-Connect)
- Toggle "Bedarf verbinden" ist auf ON
❌ OnDemand-Toggle-Lock (Apple blockt das auf Sideload — DOKUMENTIERT)
- User kann "Bedarf verbinden"-Toggle in Settings → VPN → ReBreak Schutz → "i" → ausschalten
- Nach OFF: VPN bleibt aus bis User manuell connected
- Dies ist erwartetes Apple-Verhalten auf unsupervised+sideload. Memory-ref:
project_sideload_mdm_alternative_hypothesis.md - App-Side-Behaviour: NICHT überwacht (User-Decision 2026-05-26: kein Accountability-Webhook)
⚠️ PayloadRemovalDisallowed (Verhalten auf unsupervised verifizieren)
Settings → Allgemein → VPN & Geräteverwaltung → ReBreak Schutzöffnen- Prüfen: ist "Profil entfernen"-Button vorhanden oder disabled/ausgeblendet?
- Falls vorhanden + tappable: PayloadRemovalDisallowed greift auf unsupervised NICHT (memory-doku korrigieren, Profile-Lock kommt allein von RemovalPassword)
- Falls disabled: PayloadRemovalDisallowed greift auf unsupervised auch (gut!)
✅ RemovalPassword (sollte funktionieren)
- Versuch: "Profil entfernen" tippen
- iOS promptet PIN (
Removal Passcode) - Falsche PIN: Profile bleibt
- Korrekte PIN (
482915): Profile wird entfernt - Edge-Case: User-Memory-Test — kann User die PIN erraten? 6 Ziffern = 1M Versuche. iOS hat keine Rate-Limit auf RemovalPassword-Tries (Apple-Doku schweigt). Empfehlung: längere PINs (8+) für Production.
❌ App-Removal-Lock (greift NICHT auf unsupervised — DOKUMENTIERT)
- Long-Press ReBreak-App auf Home-Screen
- "App löschen" ist verfügbar (allowAppRemoval=false ignored)
- Dies ist Apple's supervised-only-Restriction-Regel. Auf unsupervised kein Lock erreichbar via .mobileconfig.
Edge-Cases
Airplane Mode
- Airplane Mode AN → VPN disconnects (erwartet)
- Airplane Mode AUS → OnDemand reconnects VPN automatisch
- WiFi+Cellular re-enabled simultaneously → OnDemand wählt korrektes Interface
Network-Switch (WiFi ↔ Cellular)
- Verbunden mit WiFi → VPN connected
- WiFi aus → Wechsel zu Cellular → VPN disconnects + reconnects über Cellular binnen ~3s
- DNS bleibt gelocked auch während Reconnect-Lücke (DoH-Payload ist VPN-independent)
Captive Portal
- Hotel-WiFi mit Login-Page
- iOS öffnet Captive-Portal automatisch (bypass VPN für login-domain)
- Nach Login: VPN connectet wieder
- Test mit echtem Café/Hotel-WiFi nötig — Simulation schwer
Profile-Survival nach App-Uninstall
- App via Long-Press → "App löschen"
- Profile bleibt installiert (Settings → VPN & Geräteverwaltung zeigt's)
- DNS-Lock bleibt aktiv (Browser-Test
lotto.de→ still blocked) - VPN-Eintrag bleibt, aber Connection-Status: failure (ProviderBundle nicht mehr resolvable)
- App-Reinstall: VPN-Connection wieder OK ohne Re-Install des Profils
- DIESER PUNKT IST DER KERN-PURPOSE: Anti-Cooldown-Manipulation. Auch wenn User die App löscht, bleibt der DNS-Lock und damit ein Mindest-Schutz aktiv.
Reboot
- iPhone neustarten
- VPN reconnects automatisch nach Boot
- DNS-Lock auch nach Reboot aktiv
Companion-Path: MDM-Enrollment für Full-Lock (optional)
Wenn der User zusätzlich Toggle-Lock will (= "Bedarf verbinden" NICHT abschaltbar):
- User browsed zu
https://mdm.rebreak.org/enrollin Safari - Installiert NanoMDM-Enrollment-Profile (removable, mit
MDM_TYPE=Device) - Gerät bleibt unsupervised — Device-Enrollment ist nicht Supervision
- Backend pushed
com.apple.vpn.managed-Payload via NanoMDMInstallProfile-Command - Jetzt empirisch verifizieren: ist
OnDemandUserOverrideDisabledauf unsupervised+device-enrolled wirksam? Bisher unverifiziert — Memory hat nur supervised+MDM-Empirie. Hypothese: ja, weil MDM-Channel ist der Differenzierer. - Falls ja: User-Toggle für "Bedarf verbinden" ist disabled (grau).
- Bypass-Surface: User kann MDM-Enrollment via Settings entfernen → VPN-Toggle wieder togglebar. Sideload-Profile bleibt aber stehen (separates Profile).
Bekannte Limitations (Apple-Walls — non-fixable ohne Supervision)
| Limitation | Kompensiert durch |
|---|---|
| allowAppRemoval unsupervised ignored | App-Side-Cooldown-Logik (User kann App löschen, aber DNS bleibt) |
| OnDemandUserOverrideDisabled unsupervised | Companion-MDM-Enrollment (optional, User-Decision) |
| PayloadRemovalDisallowed unsupervised ? | RemovalPassword (PIN-Friction) |
Update-Pfad
Wenn das Profile-Layout ändert (z.B. neue DNS-Server, geänderte VPN-Config):
- Bump
PayloadIdentifier-Suffix-Datum:org.rebreak.protection.iphone.unsupervised.YYYYMMDD - Frische UUIDs überall (Generator macht das automatisch)
- User muss altes Profile mit RemovalPassword entfernen + neues installieren
- Bei vielen Users: Web-Notification "Bitte Schutz erneuern" + Direkt-Link auf neuen Download
Apple's iOS akzeptiert kein Profile-Update-in-place ohne RemovalPassword-Re-Auth (Anti-Hijack-Design). Bei zu häufigen Updates User-Friction hoch → Profile-Schema stabil halten.
UI-Banner (geplant, falls Setup klappt)
apps/rebreak-native/app/(app)/blocker.tsx — wenn mdmManaged=false UND
isUnsupervised=true UND hasSideloadProfile=false:
┌────────────────────────────────────────────┐
│ 🛡️ Erweiterter Schutz verfügbar │
│ │
│ Du nutzt aktuell App-internen VPN + Family │
│ Controls. Für besseren Cooldown-Schutz │
│ installiere das Schutz-Profil — überlebt │
│ auch wenn du die App löschst. │
│ │
│ [Profil herunterladen] │
└────────────────────────────────────────────┘
Detection: App liest NETunnelProviderManager.loadAllFromPreferences() →
filter auf isOnDemandEnabled && ProviderBundle == eigen UND zusätzlich
einen zweiten Manager (das wäre das gepushte Profile). Oder: dns_settings-
Check via nw_path_monitor + DoH-Endpoint-Verify.