feat(protection): Layer 2 in FC-Aktivierung einhaengen + URLFilter-Extension-Version
Layer-2-webContent-Filter laeuft jetzt automatisch bei activateFamilyControls/ activate mit (Helper applyWebContentLayer). URLFilterExtension CFBundleVersion/ ShortVersion an die App angeglichen. Apple-DTS-Report einreichfertig. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
627ddce995
commit
ad51ce5099
@ -260,6 +260,7 @@ public class RebreakProtectionModule: Module {
|
||||
AsyncFunction("activateFamilyControls") { () async -> [String: Any] in
|
||||
var error: String? = nil
|
||||
var enabled = false
|
||||
var webContentCount = 0
|
||||
if #available(iOS 16.0, *) {
|
||||
// Retry-Loop: FamilyControls XPC-Daemon kann auf den ersten Call
|
||||
// mit NSCocoaErrorDomain:4099 antworten (Communication-Failure, oft
|
||||
@ -283,6 +284,12 @@ public class RebreakProtectionModule: Module {
|
||||
if !lockActive {
|
||||
error = "denyAppRemoval_not_active"
|
||||
}
|
||||
// Layer 2 — der webContent-Filter ist Teil des FC-Schutzes:
|
||||
// greift mit, sobald FC aktiv ist (stilles Sicherheitsnetz für
|
||||
// den Fall, dass der User Layer 1 / VPN abschaltet). Best-effort
|
||||
// — ein Fehlschlag hier kippt die FC-Aktivierung NICHT.
|
||||
let wc = Self.applyWebContentLayer()
|
||||
webContentCount = wc.count
|
||||
} else {
|
||||
enabled = false
|
||||
}
|
||||
@ -304,7 +311,7 @@ public class RebreakProtectionModule: Module {
|
||||
} else {
|
||||
error = "iOS 16+ required for FamilyControls"
|
||||
}
|
||||
var result: [String: Any] = ["enabled": enabled]
|
||||
var result: [String: Any] = ["enabled": enabled, "webContentDomains": webContentCount]
|
||||
if let error = error { result["error"] = error }
|
||||
return result
|
||||
}
|
||||
@ -367,6 +374,8 @@ public class RebreakProtectionModule: Module {
|
||||
store.application.denyAppRemoval = true
|
||||
store.application.denyAppInstallation = false
|
||||
SharedLogStore.append("🔒 denyAppRemoval = true")
|
||||
// Layer 2 — webContent-Filter als Teil des FC-Schutzes mit-aktivieren.
|
||||
_ = Self.applyWebContentLayer()
|
||||
}
|
||||
|
||||
return [
|
||||
@ -457,36 +466,13 @@ public class RebreakProtectionModule: Module {
|
||||
]
|
||||
}
|
||||
|
||||
// 1) Land bestimmen — Locale.current.region (Fallback: erstes Element,
|
||||
// sonst "DE"). region ist iOS 16+; .regionCode als Pre-16-Fallback.
|
||||
if let region = Locale.current.region?.identifier, !region.isEmpty {
|
||||
resolvedRegion = region.uppercased()
|
||||
} else if let region = Locale.current.regionCode, !region.isEmpty {
|
||||
resolvedRegion = region.uppercased()
|
||||
}
|
||||
SharedLogStore.append("🌍 [applyWebContentFilter] region=\(resolvedRegion)")
|
||||
|
||||
// 2) Domain-Liste für das Land aus dem gebündelten JSON laden.
|
||||
let domains = Self.loadWebContentDomains(forRegion: resolvedRegion)
|
||||
if domains.isEmpty {
|
||||
SharedLogStore.append("⚠️ [applyWebContentFilter] keine Domains für \(resolvedRegion)")
|
||||
return [
|
||||
"enabled": false,
|
||||
"appliedCount": 0,
|
||||
"region": resolvedRegion,
|
||||
"error": "no_domains_for_region",
|
||||
]
|
||||
}
|
||||
|
||||
// 3) blockedByFilter setzen. .auto(_, except:) blockt die gelisteten
|
||||
// Domains PLUS systemseitig Adult-Content gratis mit. Hartlimit 50.
|
||||
let webDomains = Set(domains.prefix(WEBCONTENT_MAX_DOMAINS).map { WebDomain(domain: $0) })
|
||||
appliedCount = webDomains.count
|
||||
|
||||
let store = ManagedSettingsStore(named: ManagedSettingsStore.Name(rawValue: MS_STORE_NAME))
|
||||
store.webContent.blockedByFilter = .auto(webDomains, except: [])
|
||||
enabled = true
|
||||
SharedLogStore.append("🛡️ [applyWebContentFilter] webContent.blockedByFilter=.auto — \(appliedCount) Domains (\(resolvedRegion))")
|
||||
// Gemeinsame Layer-2-Logik (applyWebContentLayer) — exakt dieselbe,
|
||||
// die auch bei der FC-Aktivierung mitläuft.
|
||||
let wc = Self.applyWebContentLayer()
|
||||
enabled = wc.enabled
|
||||
appliedCount = wc.count
|
||||
resolvedRegion = wc.region
|
||||
if !enabled { error = "no_domains_for_region" }
|
||||
} else {
|
||||
error = "iOS 16+ required for webContent filter"
|
||||
SharedLogStore.append("❌ [applyWebContentFilter] \(error!)")
|
||||
@ -806,6 +792,34 @@ public class RebreakProtectionModule: Module {
|
||||
|
||||
// ─── Helpers ────────────────────────────────────────────────────────────────
|
||||
|
||||
/// Wendet den Layer-2-webContent-Filter an: Land bestimmen → Domain-Liste
|
||||
/// laden → `webContent.blockedByFilter = .auto`. Gemeinsame Logik für die
|
||||
/// explizite `applyWebContentFilter`-Function UND die FC-Aktivierung
|
||||
/// (`activateFamilyControls`/`activate`) — Layer 2 ist Teil des FC-Schutzes
|
||||
/// und läuft mit, sobald Family Controls aktiv ist. Voraussetzung: FC
|
||||
/// authorisiert (der Aufrufer prüft das).
|
||||
@available(iOS 16.0, *)
|
||||
private static func applyWebContentLayer() -> (enabled: Bool, count: Int, region: String) {
|
||||
var resolvedRegion = WEBCONTENT_FALLBACK_REGION
|
||||
if let region = Locale.current.region?.identifier, !region.isEmpty {
|
||||
resolvedRegion = region.uppercased()
|
||||
} else if let region = Locale.current.regionCode, !region.isEmpty {
|
||||
resolvedRegion = region.uppercased()
|
||||
}
|
||||
let domains = loadWebContentDomains(forRegion: resolvedRegion)
|
||||
guard !domains.isEmpty else {
|
||||
SharedLogStore.append("⚠️ [webContent] keine Domains für \(resolvedRegion)")
|
||||
return (false, 0, resolvedRegion)
|
||||
}
|
||||
// .auto(_, except:) blockt die gelisteten Domains PLUS systemseitig
|
||||
// Adult-Content. Hartlimit 50 Domains.
|
||||
let webDomains = Set(domains.prefix(WEBCONTENT_MAX_DOMAINS).map { WebDomain(domain: $0) })
|
||||
let store = ManagedSettingsStore(named: ManagedSettingsStore.Name(rawValue: MS_STORE_NAME))
|
||||
store.webContent.blockedByFilter = .auto(webDomains, except: [])
|
||||
SharedLogStore.append("🛡️ [webContent] blockedByFilter=.auto — \(webDomains.count) Domains (\(resolvedRegion))")
|
||||
return (true, webDomains.count, resolvedRegion)
|
||||
}
|
||||
|
||||
/// Lädt die kuratierte Gambling-Domain-Liste für ein Land aus dem
|
||||
/// gebündelten JSON (gambling-domains.json). Das JSON wird von der Podspec
|
||||
/// als RebreakProtectionResources.bundle ins App-Bundle gepackt.
|
||||
|
||||
@ -17,9 +17,9 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>0.3.4</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>13</string>
|
||||
<key>EXAppExtensionAttributes</key>
|
||||
<dict>
|
||||
<key>EXExtensionPointIdentifier</key>
|
||||
|
||||
@ -1,8 +1,9 @@
|
||||
# Apple DTS / Feedback — NEURLFilter `serverSetupIncomplete` on a development-signed build
|
||||
|
||||
**Zweck:** Vorlage für einen Apple Developer Technical Support Incident (TSI) bzw. einen
|
||||
Beitrag im Developer-Forum-Thread 791352 ("Getting a basic URL Filter to work").
|
||||
Stand: 2026-05-21. Einfach Inhalt anpassen/kopieren und einreichen.
|
||||
**Zweck:** Einreichfertiger Bericht für (1) einen Feedback-Assistant-Bug-Report und
|
||||
(2) einen Apple Developer Technical Support Incident (TSI). Stand: 2026-05-21.
|
||||
Vor dem Einreichen die Platzhalter `pir.staging.rebreak.org` und `<user token>` durch die echten
|
||||
Werte ersetzen. Einreich-Anleitung siehe Abschnitt „How to submit" am Ende.
|
||||
|
||||
---
|
||||
|
||||
@ -37,10 +38,12 @@ calling `/config`, and what "server setup" the framework considers incomplete.
|
||||
|
||||
```swift
|
||||
try manager.setConfiguration(
|
||||
pirServerURL: URL(string: "https://<our-host>")!, // no trailing slash
|
||||
pirPrivacyPassIssuerURL: URL(string: "https://<our-host>")!, // same host serves both
|
||||
pirServerURL: URL(string: "https://pir.staging.rebreak.org")!, // no trailing slash
|
||||
pirPrivacyPassIssuerURL: URL(string: "https://pir.staging.rebreak.org")!, // same host serves both
|
||||
pirAuthenticationToken: "<user token>",
|
||||
controlProviderBundleIdentifier: "org.rebreak.app.URLFilterExtension")
|
||||
manager.localizedDescription = "ReBreak URL Filter" // added for WWDC-sample parity
|
||||
manager.prefilterFetchInterval = 86400 // added for WWDC-sample parity
|
||||
manager.isEnabled = true
|
||||
manager.shouldFailClosed = true
|
||||
try await manager.saveToPreferences()
|
||||
@ -54,8 +57,8 @@ urlFilter = {
|
||||
FailClosed = YES
|
||||
AppBundleIdentifier = org.rebreak.app
|
||||
ControlProviderBundleIdentifier = org.rebreak.app.URLFilterExtension
|
||||
pirServerURL = https://<our-host>
|
||||
pirPrivacyPassIssuerURL = https://<our-host>
|
||||
pirServerURL = https://pir.staging.rebreak.org
|
||||
pirPrivacyPassIssuerURL = https://pir.staging.rebreak.org
|
||||
AuthenticationToken = <user token>
|
||||
pirPrivacyProxyFailOpen = NO
|
||||
pirSkipRegistration = NO
|
||||
@ -77,7 +80,7 @@ urlFilter = {
|
||||
|
||||
```
|
||||
ciphermld Request to fetchConfigs has started for useCases ['org.rebreak.app.url.filtering']
|
||||
ciphermld error Failed to fetch configs. URL: https://<our-host>/config Status Code: 401
|
||||
ciphermld error Failed to fetch configs. URL: https://pir.staging.rebreak.org/config Status Code: 401
|
||||
ciphermld error queryStatus(for:options:) threw an error:
|
||||
CipherML.CipherMLError.serverError("{\"error\":{\"message\":\"No private token\"}}")
|
||||
neagent requestStatusForClientConfig… XPC complete, error: com.apple.CipherML Code=1800
|
||||
@ -151,3 +154,24 @@ Standard `apple/pir-service-example` `PIRService` as the PIR + Privacy Pass issu
|
||||
a development-signed app with an `NEURLFilterControlProvider` extension, and the
|
||||
`setConfiguration` call shown above. Happy to provide a sysdiagnose and full
|
||||
`neagent`/`ciphermld` logs.
|
||||
|
||||
---
|
||||
|
||||
## How to submit (internal note — not part of the report text)
|
||||
|
||||
File in this order:
|
||||
|
||||
1. **Feedback Assistant** (`feedbackassistant.apple.com`) — file as a bug against
|
||||
*NetworkExtension / NEURLFilter*. Attach a **sysdiagnose** taken right after
|
||||
reproducing, plus the full `neagent` + `com.apple.cipherml` Console export.
|
||||
Note the resulting **FB number**.
|
||||
2. **Developer Technical Support Incident** (developer.apple.com → Account →
|
||||
Support → *Technical Support* / „Request Technical Support") — paste this
|
||||
report, reference the **FB number** from step 1, and ask the four
|
||||
„Questions for Apple". A DTS engineer replies directly (effectively the
|
||||
written line to Apple — there is no plain support-email channel for this).
|
||||
Costs one Technical Support Incident (2 included per membership year).
|
||||
|
||||
**Attach:** sysdiagnose (`.tar.gz`), the full `neagent`/`ciphermld` log export,
|
||||
and — if asked — a minimal sample project. Replace `pir.staging.rebreak.org` and
|
||||
`<user token>` with the real values first.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user