Files
blocker/docs/PERMISSIONS.md
T
SellPL 5073c7321d docs: add ADR log, architecture, limitations, permissions, perf, testing
The documentation set CLAUDE.md sections 2, 6, 8 and 10.1 call for, written as
records of decisions actually made rather than as a description of intent.

**DECISIONS.md** — 17 ADRs. Two are deliberately left open and say so: ADR-6
(the desktop UI toolkit, which section 3 requires be decided on measurement and
which needs the owner's call, partly because Slint is GPL-3.0) and ADR-17 (the
licence, which is a legal commitment nobody should make on the owner's behalf).
Where CLAUDE.md gives a reason for a decision, that reason is recorded; where it
does not, the file says the reason is unrecorded rather than inventing one.

**LIMITATIONS.md** — all eight section 6 limits plus the section 1.5.4 safety
guarantees, stated as facts about the operating systems. The words
"unbypassable", "tamper-proof", "guaranteed" and "secure" appear nowhere, per
sections 6.1 and 6.7. Ends with a separate list of shortfalls in the *current
code* — storage has no I/O, the golden tables cannot yet be parsed, LockState
always returns Normal — so a known gap in this implementation is never mistaken
for a platform limit.

**ARCHITECTURE.md** — the layering, the purity contract and its mechanical
enforcement, why `next_wakeup` must be armed over inactive schedules too, the
overnight weekday-of-start rule, and the end-to-end flow for one block event.

**PERF.md** — targets and results as separate tables, so an aspiration is never
read as a measurement. The one real number recorded (0.279 MB stripped daemon) is
labelled as nearly meaningless, since the daemon is two print statements. Lists
the common causes of idle-wakeup regressions.

**PERMISSIONS.md** — per platform, what each permission is for and what degrades
without it. Flags the two invasive ones (Android accessibility, extension host
permissions) rather than burying them, and records that unprivileged eBPF is
*assumed* unavailable here because the sysctls were never read.

**TESTING.md** — the golden-table contract, the add-the-case-first rule, and why
clock tampering needs code fixtures instead of data.

Co-Authored-By: Claude Code <noreply@anthropic.com>
2026-10-01 15:22:24 +03:00

7.4 KiB

Permissions

Every permission the design requires, per platform, with what it is for, what degrades without it, and how invasive it feels.

Two permissions ask for more than a user expects. This file says so rather than burying it: accessibility service on Android, and browser extension permissions on desktop (ADR-7). Both grant the ability to observe and control other applications. There is no way to provide the feature without them.

Where an optional permission is refused, the documented behaviour is what happens — not a silent failure.


Android

Required

Permission Why Without it
BIND_ACCESSIBILITY_SERVICE Foreground-app detection. Section 4.1 specifies TYPE_WINDOW_STATE_CHANGED, which is event-driven and needs no polling. Nothing blocks. This is the single permission the product cannot work without.
SYSTEM_ALERT_WINDOW The block screen is an overlay that must appear over whatever the user just opened. No block screen. Blocking would have to become a kill, which is far more destructive and loses the user's state.
PACKAGE_USAGE_STATS Usage-limit and launch-count conditions (section 1.2), and historical statistics (section 1.6). Granted via Settings, not at install. Usage and launch-count conditions are unavailable. Time conditions still work.
BIND_DEVICE_ADMIN Uninstall protection under Strict Mode (section 1.4). Strict Mode cannot block uninstall. The other restrictions still apply.
FOREGROUND_SERVICE + FOREGROUND_SERVICE_SPECIAL_USE A persistent notification so the service is not killed silently. The service is far more likely to be killed by the OS, with nothing telling the user why.
RECEIVE_BOOT_COMPLETED Re-apply blocks after a reboot. Enforcement does not survive a reboot until the app is opened by hand.
POST_NOTIFICATIONS The persistent notification itself. The foreground service runs without its notification, and users cannot tell it is running.
QUERY_ALL_PACKAGES Enumerating installed apps for allowlist mode (section 1.1) and for resolving a desktop-file id to a name. Allowlist mode cannot enumerate, so it blocks nothing (fail open, section 1.5.4). Blocklist mode is unaffected.

On QUERY_ALL_PACKAGES: this one is Play-Store-sensitive and needs a policy justification. It is requested because allowlist mode requires knowing what is installed, and there is no narrower permission that does that job.

Optional

Permission Why Without it
ACCESS_FINE_LOCATION / ACCESS_COARSE_LOCATION Location conditions (section 1.2). The condition is hidden in the UI rather than offered and never firing. Mobile-only per section 6.4.
ACCESS_WIFI_STATE Wi-Fi SSID conditions (section 1.2). SSID conditions unavailable. Time conditions unaffected.
REQUEST_IGNORE_BATTERY_OPTIMIZATIONS Exemption from Doze, which would otherwise suspend enforcement. Works, but only while the screen is on, on most OEMs.

Behaviour when location is unavailable

Section 1.7 makes this configurable. The default is do not block: a lost GPS fix should not mean a locked device. The alternative — treat unavailable as present — exists for schedules whose purpose is to block leaving somewhere.

OEM autostart

Section 6.5: Xiaomi, Huawei, and Samsung kill background services in model-specific ways that a permissions dialog cannot fix. Autostart is granted in the vendor's own UI, not Android's.

Vendor Where
Xiaomi / Redmi Settings → Apps → Autostart
Huawei / Honor Settings → Battery → App launch, then enable auto-launch, secondary launch, and run in background
Samsung Settings → Apps → Blocker → Never sleeping apps
OnePlus / Oppo Settings → Battery → App battery management → Allow background activity

Not yet implemented. The troubleshooting screen arrives in M7. Until then these workarounds are not documented anywhere the user can find.


Linux

The X11 and KWin paths need no special permissions. That is the main advantage over Android and worth stating plainly.

Mechanism Needs Notes
KWin script over org.kde.KWin /Scripting Nothing Session D-Bus, available to any process in the session. Verified present on KDE Plasma 6.6.6.
X11 via x11rb Nothing DISPLAY access only. Blind to native Wayland apps.
NetworkManager SSID via D-Bus Nothing Session bus.
systemd --user service loginctl enable-linger for post-logout operation Without linger, the daemon stops when the user logs out, which is usually correct.
Process watching via /proc polling Nothing Used only by the degraded fallback.
Process watching via eBPF Likely CAP_BPF / CAP_SYS_ADMIN, or permissive sysctls Not verified on the development machine.

What an unprivileged daemon cannot do

Recorded because it is a platform limit, not a missing feature. See desktop/platform-linux/src/lib.rs::UNPRIVILEGED_LIMITS:

  • Prevent a process from launching. It can only react after the fact.
  • Mask a systemd unit, which needs root. Tamper resistance is Restart=always plus a watchdog, and nothing stronger.
  • Protect its own configuration from the user who owns the files. Lock state lives in the database instead (section 1.5.3).
  • Read /proc/<pid>/exe for processes owned by other users.
  • Stop a process already running as root.

Not verified: whether eBPF process tracing works unprivileged here. The relevant sysctls (kernel.unprivileged_bpf_disabled, kernel.perf_event_paranoid) were not read, so the eBPF path is assumed unavailable rather than known to work. /proc polling is the assumed fallback.


Windows

Nothing implemented yet, so nothing is measured or verified. From section 4.2:

Mechanism Needs Notes
SetWinEventHook(EVENT_SYSTEM_FOREGROUND) Nothing Foreground detection. Event-driven, no polling.
Minimise / close a window Nothing Standard user-session APIs.
Terminating a process owned by another user Elevation Same-user processes need nothing.
WLAN API notification callback Nothing Wi-Fi SSID conditions.
Windows Service watchdog Administrator, to install Restarts the per-user daemon if it dies.

Browser extension (desktop, ADR-7)

The extension is a separate permission surface from the app, and arguably the most alarming one — it is worth being precise about what it asks for.

Permission Why Without it
Host permissions for <all_urls> Read the URL of every page the user visits, to match domains and keywords. Nothing blocks in the browser.
webRequest or equivalent URL observation Same, plus redirect handling. Domain blocking degrades to what the user navigates directly.
nativeMessaging Ask the daemon whether to block. No blocking at all. The extension holds no blocking logic of its own, by design (ADR-7).

The extension contains no blocking logic. It asks the daemon, which returns a decision from the same engine that decides apps. That is why the permission looks broader than its behaviour: it observes every URL so it can ask about it, but it cannot block anything the engine does not decide to block.

The residual gap, stated plainly: an extension covers browsers it is installed in. curl, or a browser without the extension, is not covered. That is section 6.7 — friction, not a wall.