{ "intent": "Add the network-level layer: pin RethinkDNS as the device owner's always-on VPN so YouTube is blocked by host, in every app, not only by hiding the YouTube package.", "scope": [ "python_pkg/focus_policy/export.py", "python_pkg/focus_policy/tests/test_export.py", "focus_owner/android/app/src/main/kotlin/com/kuhy/focus_owner/DevicePolicyBridge.kt", "focus_owner/android/app/src/main/kotlin/com/kuhy/focus_owner/EnforcementRunner.kt", "focus_owner/android/app/src/main/kotlin/com/kuhy/focus_owner/MainActivity.kt", "phone_focus_mode/config.sh", "focus_owner/assets/policy.json", "Non-goal: DISALLOW_CONFIG_VPN, deliberately deferred until the filter is confirmed working", "Non-goal: the domain rules themselves, which cannot be scripted" ], "changes": [ "DevicePolicyBridge.setAlwaysOnVpn uses DevicePolicyManager.setAlwaysOnVpnPackage. This is not the mechanism that failed on 2026-08-09: that was `settings put secure always_on_vpn_app`, which writes, survives a reboot, and is ignored by VpnManager. The DPM call from a device owner actually takes effect.", "lockdownEnabled=false deliberately. Lockdown drops all traffic when the tunnel is down, and the wipe checklist names lockdown plus the DISALLOW_* restrictions as the combination most likely to produce an unrecoverable device.", "releaseDeviceOwner now also clears DISALLOW_CONFIG_VPN and unpins the VPN before clearDeviceOwnerApp, for the same reason it already unblocked uninstall: these are device-owner powers, so clearing ownership first would strand them with nothing privileged left to lift them.", "The pin is re-asserted every pass from EnforcementRunner.apply, unconditionally rather than tracking decision.isEnforcing -- the network filter must not lift when the geofence says AWAY.", "always_on_vpn_package is emitted by the exporter only when the provider is also in never_disable_prefixes, and com.celzero.bravedns was added there. Pinning a package the sweep can hide is the worst case: the restriction would point at a hidden app and cost general connectivity, not just the filter." ], "verification": [ { "command": "pytest python_pkg/focus_policy/ --cov", "result": "pass", "evidence": "129 tests, 100% branch coverage retained on every module. The new test asserts the field is empty when the provider is unprotected and set when it is." }, { "command": "dumpsys package com.celzero.bravedns | grep BIND_VPN_SERVICE", "result": "pass", "evidence": "com.celzero.bravedns/.service.BraveVPNService declares BIND_VPN_SERVICE, so it is a valid always-on target rather than a package that would silently no-op." }, { "command": "run an enforcement pass, then dumpsys device_policy", "result": "pass", "evidence": "logcat 'FocusOwner: alwaysOnVpn -> com.celzero.bravedns'; dumpsys reports mAlwaysOnVpnPackage=com.celzero.bravedns, mAlwaysOnVpnLockdown=false. RethinkDNS was also unhidden by the same pass, confirming the never-disable entry works." }, { "command": "ip addr show tun0 ; pidof com.celzero.bravedns", "result": "partial", "evidence": "The app is running (pid 8871) and the pin is registered, but there is NO tun0 interface -- the tunnel has not come up. Android requires a one-time in-app VPN consent tap that cannot be granted over adb. The pin is armed but the filter is not yet carrying traffic." } ], "risks": [ "THE NETWORK BLOCK IS NOT YET ACTIVE. The pin is set but the tunnel is down pending the VPN consent tap, and RethinkDNS is at first-run state: the 8 wildcard YouTube rules died with the wipe. They live in app-private storage, so phone_backup.sh never captured them and adb cannot restore them -- they must be re-entered by hand.", "Pinning RethinkDNS consumes Android's single VPN slot, so com.zaneschepke.wireguardautotunnel cannot run, and dns.kuhy.duckdns.org (bound to 10.8.0.1, WireGuard-only) is unreachable. The user chose this trade explicitly.", "DISALLOW_CONFIG_VPN is NOT applied yet, so the user can still turn the VPN off from Settings. Applying it before the filter is confirmed working would lock in a non-functional configuration.", "Whether DISALLOW_CONFIG_VPN also locks RethinkDNS's own in-app rule editor is untested. If it does, every domain rule must be final before the restriction is applied.", "The always-on pin has not been reboot-tested." ], "rollback": [ "In-app 'Release device owner' unpins the VPN and clears the restriction before releasing, all in one action.", "To unpin without releasing: remove com.celzero.bravedns from SYSTEM_NEVER_DISABLE, regenerate the asset (the exporter then emits an empty always_on_vpn_package), rebuild, install, run a pass.", "adb shell dpm set-always-on-vpn is not a rollback path; the DPM state is owned by the app." ] }