testsAndMisc/docs/superpowers/evidence/always-on-vpn-network-block-2026-08-10.json
Krzysztof kuhy Rudnicki db1d8f8247
Some checks are pending
Pre-commit checks / pre-commit (push) Waiting to run
Python tests / test (push) Waiting to run
Shell tests / shell-tests (push) Waiting to run
Shell tests / arch-tests (push) Waiting to run
Pin RethinkDNS as always-on VPN, the layer that reaches every app
Hiding the YouTube package stops the app. It does nothing about
youtube.com in Firefox, in a webview, or in any client nobody has
enumerated. This is that layer.

setAlwaysOnVpnPackage from the device owner is not the mechanism that
failed on 2026-08-09 -- that was `settings put secure always_on_vpn_app`,
which writes successfully, survives a reboot, and is ignored by
VpnManager. The DPM call takes effect: dumpsys now reports
mAlwaysOnVpnPackage=com.celzero.bravedns.

Lockdown stays off. It drops all traffic whenever the tunnel is down,
and the wipe checklist names it together with the DISALLOW_*
restrictions as the combination most likely to leave an unrecoverable
device. DISALLOW_CONFIG_VPN is deferred for the same reason: locking VPN
configuration before the filter is confirmed working would pin a broken
setup in place.

The exporter emits always_on_vpn_package only when the provider is also
in never_disable_prefixes, and RethinkDNS 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. The pin is re-asserted every pass and, unlike the uninstall
block, does not track the decision: the network filter must not lift
when the geofence says AWAY.

releaseDeviceOwner now unpins the VPN and clears the restriction before
clearing ownership, for the same reason it already unblocked uninstall.

Not yet active, and the commit does not pretend otherwise: the tunnel
needs a one-time in-app VPN consent tap that adb cannot grant, and
RethinkDNS is at first-run state because its wildcard rules live in
app-private storage that the backup never reached. They have to be
re-entered by hand.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014DqEd1uUhyT52KjaNeLanC
2026-08-10 21:40:04 +02:00

56 lines
4.8 KiB
JSON

{
"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."
]
}