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

133 lines
7.4 KiB
Markdown

# 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.