# Consent Prompts A program that has no entry in `0:/config/capabilities.toml` starts with no capabilities. For five of them, it can ask the user the first time it needs one, instead of failing. This works much like TCC on macOS: the desktop shows a prompt. For installed programs the answer is remembered; for any other binary it lasts until the process exits. | Capability | The prompt says the app wants to... | |-----------------|-----------------------------------------------------------------------| | `storage_admin` | format, partition and mount disks | | `network_admin` | change network settings and join Wi-Fi networks | | `set_time` | change the system clock and time zone | | `display_admin` | change the screen resolution and brightness | | `log_read` | read the system log, which can reveal what other apps and devices are doing | No other capability can be asked for. The eligible set is `CAP_CONSENT_ELIGIBLE` in `Api/Syscall.hpp`. ## How a request flows 1. A syscall gated on an eligible capability calls `Sched::Consent::Require()` (`kernel/src/Sched/Consent.cpp`) instead of `HasCapability()`. All of these checks sit at the top of their syscall in `Api/Syscall.cpp`, where the thread holds no locks and may block. 2. If the caller lacks the capability and is allowed to ask (see below), the kernel queues a request and blocks the calling thread. A second thread of the same process asking for the same capability waits on the same request. 3. The session's consent agent, the desktop, picks the request up with `SYS_CONSENT_NEXT` from its main loop. If `0:/config/consent.toml` already holds an answer for this program and capability, the desktop replies without showing anything. Otherwise it opens the prompt. 4. `SYS_CONSENT_REPLY` either grants the capability or records a denial. The waiting thread wakes, and its syscall proceeds or returns `SYS_ERR_PERMISSION`. If nobody answers within `CONSENT_TIMEOUT_MS` (120 s), the request is denied. If the agent exits, every pending request is denied. If the app exits, its requests are cancelled. ## Who may ask A process may ask for a capability only when all of these hold: - The capability is within its **consent ceiling**: the part of its parent's delegable set, recorded at spawn, that is eligible for consent. - The session has a registered agent. Registering (`SYS_CONSENT_REGISTER`) requires `CAP_COMPOSITOR`, so only the desktop can be the agent. - The process is in the agent's session and is not the agent itself. - The agent still holds the capability as delegable. - It has not already been refused that capability during its lifetime. These rules have some consequences: - **Standard sessions never prompt.** A standard session holds none of the eligible capabilities, so every ceiling under it is empty. - **Console sessions and SSH sessions never prompt.** They have no agent (sshd is not in the desktop's session), so a remote user cannot put a prompt on the local screen. - **A sandboxing launcher still works.** If a parent spawns a child with nothing delegable, the child's ceiling is empty and it cannot ask. - After a refusal the process gets `SYS_ERR_PERMISSION` immediately every time, so it cannot flood the user with prompts. ## Why this keeps the capability model's invariants 1. **Authority only narrows down the process tree.** A grant never exceeds the process's consent ceiling, which is exactly what its parent could have given it at spawn. The grant must also be within the agent's own delegable set. Consent-granted capabilities are added only to the effective set, never to the delegable set, so a consented app cannot pass them on to anything it launches. Consent delays a delegation the parent could already have made; it never exceeds one. The kernel re-checks all of this under the scheduler lock when it applies the grant, so a grant can never land on a process slot that has exited and been reused. 2. **Capabilities are kernel-owned.** The kernel identifies the program by the path it loaded, never by a user name. ## Program identity A stored answer is keyed on the program's path, and a path identifies a program only if nobody else can replace the file. On the system volume, writing anything under `0:/os` or `0:/apps` requires `CAP_SYSTEM_IMAGE` (`Fs/ProtectedPaths.cpp`), which no session holds. So a program there is **installed**, and its path is a trustworthy identity. The kernel decides this, not the desktop: each `ConsentRequest` carries `imageProtected`, computed from the resolved image path with the same rule that guards writes. Paths are normalized by the same resolver the write check uses, so `0:/os/../users/x.elf` is not mistaken for an installed program. Any other binary, such as one in a user's home directory or on a data volume, could be swapped for another at the same path. Its answers are never stored: it is asked each time it runs, and the answer lasts until that process exits. The same rule applies to static grants. `Sched::Spawn` starts any image outside `0:/os` and `0:/apps` with no capabilities, whatever its launcher asked for, and logs a warning. Otherwise a program with no authority could overwrite a granted binary in, say, a home directory, and then run with the grant the next time the shell or desktop launched it. A program installed elsewhere gets its authority through consent instead. The Capabilities app flags such grants. ## The prompt The prompt is a normal window inside the desktop, not a full-screen overlay. It is placed over the requesting app's window when that app has one, and at the centre of the screen otherwise. It shows: - the app's name: its menu label when the path belongs to an installed app, otherwise the file name - what it wants to do - the full path The prompt window belongs to the desktop itself (`programs/src/desktop/consent.cpp`), not to the window server, so no process can map its pixels or send it input. `SYS_WINMAP` and `SYS_WINSENDEVENT` require `CAP_COMPOSITOR` anyway. This leaves clickjacking, where another window is raised over the prompt or steals focus just as the user clicks. To prevent that, **Allow** stays disabled until the prompt has been focused and on top for 700 ms without interruption. Losing focus or being covered restarts the delay. | Action | Result | Saved? | |--------------------------------|---------------------------------|--------| | Allow (click, once armed) | Granted | Installed programs only | | Don't Allow (click, or Return once armed) | Denied | Installed programs only | | Escape, or the title-bar close | Denied for this process only | No | | Timeout, logout | Denied for this process only | No | For a program that is not installed, the prompt also says that the answer applies only until the program exits. Only one prompt is shown at a time. Further requests wait in the kernel's queue, which holds up to 16 requests. ## Stored decisions Decisions are stored in `0:/config/consent.toml`: ```toml [decision.1] path = "0:/apps/wifitool/wifitool.elf" capability = "network_admin" decision = "allow" ``` A decision applies when the path and the capability match. Only installed programs are ever stored, and the desktop looks up only requests the kernel marked `imageProtected`. A new answer for the same path and capability replaces the old one. Updating an installed program keeps its decisions, because installing it already required `CAP_SYSTEM_IMAGE`. The file is a protected path that requires `CAP_USER_ADMIN` to write, the same as `capabilities.toml`: anything that can edit it could grant itself access. The **Consent** tab of the Capabilities app lists the stored decisions and can **Forget** one, so the program is asked again next time. A copy that is already running keeps a granted capability until it exits. ## Syscalls | Number | Name | Caller | Purpose | |--------|------------------------|--------|---------------------------------------------| | 191 | `SYS_CONSENT_REGISTER` | agent | Become the session's consent agent (needs `CAP_COMPOSITOR`) | | 192 | `SYS_CONSENT_NEXT` | agent | Take the oldest queued request (`ConsentRequest`) | | 193 | `SYS_CONSENT_REPLY` | agent | `CONSENT_ALLOW` / `CONSENT_DENY` a delivered request | ## Not covered yet - **Elevation for standard users**, for example entering an administrator's password in the prompt. This needs a different source of authority, because a standard session has nothing to delegate. - **Console sessions.** A text prompt in the terminal could be faked by anything that prints to it. - **Per-user decisions.** The table is system-wide, like the grants. - **Revoking from a running process.** Forgetting a decision only affects the next launch. - **App-supplied reasons.** The prompt describes the capability in the system's own words. A program cannot add text of its own.