Files
MontaukOS/docs/consent.md
T

9.1 KiB

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:

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