# iOS-Schutz / NEFilter – Architektur-Übersicht

> Stand: 2026-06-17
> Zusammengefasst aus `apps/rebreak-native`, `backend/server` und `apps/rebreak-magic`.

## 1. Ziel

Auf iOS gibt es zwei Schutz-Layer:

1. **NEFilter (Content Filter)** – aktiviert durch ein `webcontent-filter`-Profil (Sideload/MDM). Das ist der bevorzugte Weg für supervised/managed Geräte.
2. **Packet-Tunnel DNS-Sinkhole** – Fallback für unsupervised Geräte, sichtbar als VPN.

Dieses Dokument beschreibt, wie NEFilter aufgebaut ist und wie das Backend erkennt, ob es aktiv ist – **ohne** einen neuen Endpoint anlegen zu müssen.

---

## 2. Native iOS-Module (Expo)

Hauptdateien unter `apps/rebreak-native/modules/rebreak-protection/ios/`:

| Datei | Aufgabe |
|-------|---------|
| `RebreakProtectionModule.swift` | React-Native-Brücke, zentrale API (`isNeFilterActive`, `activateUrlFilter`, `getDeviceState`) |
| `RebreakContentFilter/FilterDataProvider.swift` | `NEFilterDataProvider`, prüft URLs gegen `blocklist.bin` |
| `RebreakPacketTunnelExtension/PacketTunnelProvider.swift` | VPN-DNS-Sinkhole Fallback |
| `RebreakPacketTunnelExtension/DnsFilter.swift` / `HashList.swift` / `DomainHasher.swift` | Domain-Blocking-Logik für den VPN-Pfad |
| `RebreakURLFilterExtension/` | NEURLFilter (iOS 26), aktuell nicht im Standard-Flow aktiv |

Konfiguration/Targets:

- `apps/rebreak-native/plugins/with-rebreak-protection-ios.js`
- `apps/rebreak-native/app.config.ts` (`appExtensions` Block für EAS)

---

## 3. Wie NEFilter erkannt wird

Die App liest den System-Status direkt aus:

```swift
// apps/rebreak-native/modules/rebreak-protection/ios/RebreakProtectionModule.swift ~782
AsyncFunction("isNeFilterActive") { () async -> [String: Any] in
  var enabled = false
  do {
    let manager = NEFilterManager.shared()
    try await manager.loadFromPreferences()
    enabled = manager.isEnabled
  } catch let e as NSError { ... }
  return ["enabled": enabled]
}
```

Wichtig:

- Build 19+ aktiviert die ContentFilter-Extension **nicht mehr selbst**.
- Das `webcontent-filter`-Profil installiert die Extension autonom.
- Die App fragt nur noch ab, ob sie aktiv ist.

### Supervised vs. Unsupervised

```swift
// RebreakProtectionModule.swift ~172
if supervised {
  return await Self.activateContentFilter()   // NEFilter
}
// ... PacketTunnel (VPN) Fallback
```

- `supervised = true` → `NEFilterDataProvider` (Content Filter)
- `supervised = false` → `NEPacketTunnelProvider` (VPN/DNS-Sinkhole)

---

## 4. Backend: Wie das NEFilter-Tracking funktioniert

### 4.1 App reportet Status

Die iOS-App postet den nativ gemessenen NEFilter-Status an einen **bereits existierenden** Endpoint:

```ts
// apps/rebreak-native/hooks/useProtectionState.ts ~152
if (Platform.OS === "ios") {
  protection.isNeFilterActive().then((res) => {
    apiFetch('/api/users/me/mdm-status', {
      method: 'POST',
      body: { mdmManaged: res.enabled },
    }).catch(() => {});
  });
}
```

### 4.2 Backend-Endpoint

`backend/server/api/users/me/mdm-status.post.ts`:

```ts
const { mdmManaged } = Body.parse(await readBody(event));
const result = await setMdmManaged(user.id, mdmManaged);
```

Dieser Endpoint ist **nicht neu** – er existiert schon und wird von der iOS-App genutzt.

### 4.3 Datenbank

`backend/prisma/schema.prisma` – `Profile`:

```prisma
mdmManaged    Boolean   @default(false) @map("mdm_managed")
mdmDetectedAt DateTime? @map("mdm_detected_at")
```

`setMdmManaged` in `backend/server/db/profile.ts`:

- Setzt `mdmManaged` auf true/false.
- Schreibt `mdmDetectedAt` nur beim ersten Mal true (Audit-Trail).

### 4.4 Auslesen

`backend/server/api/protection/state.get.ts`:

```ts
const mdmManaged = profile?.mdmManaged ?? false;
return {
  data: {
    protectionShouldBeActive,
    cooldown: { ... },
    plan,
    mdmManaged,
  },
};
```

Jeder Consumer kann also über `GET /api/protection/state` prüfen, ob der aktuelle User MDM/NEFilter-geschützt ist.

---

## 5. Der DNS-Token-Handshake (separater Weg)

Es gibt noch `ProtectedDevice` + `lastDnsQueryAt`, aber das ist ein **anderer Mechanismus**:

- Genutzt für: Desktop-DNS-Schutz, Android-VPN, iOS Packet-Tunnel-Fallback.
- Funktioniert über: `dnsToken` im DoH-Profil.
- Endpoint: `POST /api/devices/protected/handshake` (server-to-server, aufgerufen vom DoH-Server).
- DB: `backend/server/db/protectedDevices.ts`.

Warum nicht für NEFilter?

- Der `NEFilterDataProvider` lädt `blocklist.bin` direkt aus der App Group.
- Er führt keine DNS-Queries gegen einen per-Device-DoH-Endpoint aus.
- Darum kann er nicht über den DNS-Handshake tracked werden.

**Fazit:** Für den NEFilter/Sideload-Profil-Pfad ist `profile.mdmManaged` die einzige sinnvolle Backend-Quelle.

---

## 6. Was das für ReBreak Magic bedeutet

Aktuell setzt `backend/server/api/magic/devices/[deviceId]/mdm.get.ts` `lockProfileInstalled` aus `deviceProtectionState(nefilter)`. Das funktioniert nur, wenn ein Client aktiv reportet.

Die iOS-App macht das bereits über `POST /api/users/me/mdm-status`. Für Magic/Mac gilt:

- Wenn ein iPhone per USB erkannt wird, kann Magic prüfen, ob das Lock-Profil installiert ist (`installedProfileIDs` enthält `org.rebreak.protection.contentfilter.sideload`).
- Um das Backend zu aktualisieren, **muss nicht zwingend ein neuer Endpoint** angelegt werden.
- Mögliche Optionen:
  1. Magic ruft ebenfalls `POST /api/users/me/mdm-status` mit `mdmManaged: true/false` auf (User-Scope, nicht Device-Scope).
  2. Magic ruft den existierenden `POST /api/devices/protection-state` mit `protectionType: "nefilter"` auf (Device-Scope).
  3. Backend `mdm.get.ts` könnte zusätzlich `profile.mdmManaged` berücksichtigen.

Option 2 ist bereits vorhanden und am geräte-spezifischsten.

---

## 7. Wichtige Dateien im Überblick

### Native iOS

- `apps/rebreak-native/modules/rebreak-protection/ios/RebreakProtectionModule.swift`
- `apps/rebreak-native/modules/rebreak-protection/ios/RebreakContentFilter/FilterDataProvider.swift`
- `apps/rebreak-native/modules/rebreak-protection/ios/RebreakPacketTunnelExtension/PacketTunnelProvider.swift`
- `apps/rebreak-native/modules/rebreak-protection/ios/RebreakPacketTunnelExtension/DnsFilter.swift`
- `apps/rebreak-native/modules/rebreak-protection/ios/RebreakPacketTunnelExtension/HashList.swift`
- `apps/rebreak-native/modules/rebreak-protection/ios/RebreakPacketTunnelExtension/DomainHasher.swift`
- `apps/rebreak-native/plugins/with-rebreak-protection-ios.js`
- `apps/rebreak-native/app.config.ts`

### App-Logik

- `apps/rebreak-native/app/(app)/blocker.tsx`
- `apps/rebreak-native/hooks/useProtectionState.ts`
- `apps/rebreak-native/lib/protection.ts`

### Backend

- `backend/server/api/users/me/mdm-status.post.ts`
- `backend/server/api/protection/state.get.ts`
- `backend/server/db/profile.ts`
- `backend/server/api/devices/protection-state.post.ts`
- `backend/server/db/device-protection.ts`
- `backend/server/api/devices/protected/handshake.post.ts`
- `backend/server/db/protectedDevices.ts`
- `backend/server/api/magic/devices/[deviceId]/mdm.get.ts`

---

## 8. Zusammenfassung

- NEFilter wird vom iOS-System über ein `webcontent-filter`-Profil aktiviert.
- Die App erkennt ihn via `NEFilterManager.shared().loadFromPreferences()`.
- Der Status fließt über den **bereits existierenden** Endpoint `POST /api/users/me/mdm-status` in `profile.mdmManaged`.
- `GET /api/protection/state` liefert diesen Status zurück.
- Kein neuer Backend-Endpoint nötig – nur der richtige Client-Aufruf bzw. die Verknüpfung in Magic/Mac.
- `ProtectedDevice`/`lastDnsQueryAt` ist für den DNS/Packet-Tunnel-Pfad, nicht für NEFilter.
