8.3 KiB
Apps, manifests and the permission manager
How a program gets its authority. The kernel only knows capabilities; this is the userspace layer that decides who holds which, on the user's behalf (see the vision's Capability Management and App Manifest notes).
Apps
Every program a user runs is an app: a manifest plus a binary.
An app is identified by its id (kos.snake), never by a file name or a
process id. The id is what decisions, the app's data folder and (later) its
encryption keys hang off.
System servers (log, display, terminal, storage, …) are not apps: init starts them from its own table, with exactly the capabilities listed there.
Where authority comes from
An app's capabilities have exactly two sources:
- Its manifest and the user's consent. Services the app declared
(
display,storage, …), granted only by the permission manager, only as far as the user approved. - Its launcher. Whoever starts an app may pass along what it already holds: the terminal as its output, a read-only grant to the current folder, a file the user picked. A launcher can only give what it has.
cat declares nothing and gets only what the shell passes it. Snake
declares display. Nothing gets anything "just in case".
Disk layout
/apps/<id>/ a system app: manifest.toml and its binary, shared by every user
/users/<user>/ one user's folder (encrypted, under their own key)
home/ their files; the shell's `/`
apps/<id>/ an app they installed
data/<id>/ an app's private folder (its `storage`, its own key)
permissions/<id> their decisions, one file per app id
settings/ init's record of their choices (the theme)
/bin/ system servers and the shell (init starts these)
/themes/ the themes the display offers (`*.toml`)
/factory/ plaintext starter content a user's folder is
seeded from (home/, permissions/, settings/, data/)
Users each decide for themselves, even about the system's apps, and what
one installs the others do not see. An app id a system app has cannot be
installed. Only the signed-in user's folder opens. Only the permission
manager reaches their apps/, data/ and permissions/ (and /apps,
read-only). The shell's storage grant is their home/, so neither it nor
anything it runs can read an app's data or rewrite a decision.
Manifest
apps/<id>/manifest.toml, a strict subset of TOML:
[app]
id = "kos.snake" # required: lowercase reverse-DNS
name = "Snake" # required
version = "1.0.0" # required: major.minor.patch
binary = "snake" # required: a file in the app's folder
command = "snake" # optional: what the shell runs it as (default: binary)
author = "K-OS" # optional
icon = "icon.png" # optional: a file in the app's folder
memory = "3 MiB" # optional: how much it may use, KiB or MiB,
# 64 KiB to 16 MiB (default 512 KiB)
[capabilities]
required = ["display"] # cannot work without; denied = not installed
optional = [] # works without; the user decides each
[appearance]
accent = "#e91e63" # optional: its primary buttons and active states;
# the display adapts its lightness to each theme
The subset: [table] headers, key = "string", key = ["a", "b"] on one
line, # comments. Strings hold printable ASCII without " or \. It is
strict: an unknown table, key or capability name, a duplicate, or a
missing required key rejects the manifest. Nothing is ignored silently.
Capability names come from the server catalog (display, storage,
network, telephony, …). A name is valid even if this device lacks the
server; a required one it lacks means the app cannot be installed here, an
optional one is simply not granted.
| Capability | What the app gets |
|---|---|
display |
a window of its own, named from its manifest: its pixels, and the keyboard while it is in front |
storage |
the user's data/<id>/, read-write (its own folder only) |
permissions |
a management copy of the permission manager's endpoint (below) |
session |
init's session copy: who is signed in (WHO), the theme, and signing out |
launch |
a launcher copy of the permission manager's endpoint: APPS and LAUNCH only |
permissions, session and launch are system only: only an app on the system
image (with a factory decision) has them. Installing an app that asks for
either, required or optional, is refused (SYSTEM_ONLY). Settings
(kos.settings) holds the first two, Home (kos.home) the third.
Home is the session's desktop: the permission manager starts it when the
session starts (and again if it dies, five times at most), and it lists
every app with a window and opens the one pressed, each in its own
window, as many as there is room for (8 windows, 24 MiB between the
apps). Its copy of the endpoint is badged LAUNCH_BADGE: it may list and
launch, but pass nothing along, so what it opens gets no output and no
folder. The shell still runs apps and waits; name & starts one without
waiting (and without the current folder, which nobody would take back).
An app is named to LAUNCH by its command or its id.
Decisions
The user's permissions/<id>, the same TOML subset:
[decision]
granted = ["display"]
Written by the permission manager when the user decides; apps on the
system image ship with their decisions already recorded under
factory/permissions/ (pre-approved by whoever built the image), and init
seeds them into each new user's permissions/; a system app added to the
image later is seeded into existing users' at their next sign-in (decisions
already there are left alone). Changing one later is that
user's call, through the permission manager.
Permission manager
A system server with no hardware. Its logic (checking manifests, deciding what an app gets, keeping decisions) is pure and generic over two ports:
Consent: asks the user. Today: a yes/no prompt on the terminal.Store: reads manifests and decisions, writes decisions, installs files. Today: the storage server.
Both have mocks; the logic is tested on the host with cargo test.
Install
From a folder the launcher passes as a read-only grant:
- Read and check its
manifest.toml; refuse if the id is installed. - Required capabilities this device lacks: refuse.
- Ask the user about the required ones together (no = not installed), then each optional one.
- Copy the binary and manifest to the user's
apps/<id>/, createdata/<id>/ifstoragewas granted, record the decision.
Launch
The launcher names an app (by command or id), passes its arguments and the capabilities it wants to pass along (output, current folder). The permission manager:
- Looks the app up and reads its decision.
- Gives init a read-only grant to the binary; init loads it from that grant into a new, unstarted process (init needs no path, and nothing is spawned that its requester could not read).
- Gives the process the granted services and what the launcher passed, writes its arguments page, starts it.
- Watches it die, then releases its storage grants.
- Replies with a handle (the process capability, call-only) the launcher can wait on and kill.
Managing
APPS lists every installed app, a line each: id, name, then the
required, optional and granted capabilities as bit masks, tab separated.
An app granted permissions gets a copy of the permission manager's
endpoint badged MANAGE_BADGE: it may only LIST, APPS, ALLOW and
REVOKE, never install or launch. The copy is revoked when the app dies.
Revoke
The user withdraws a capability: the decision changes and the next launch
gets less. A running app that held it is stopped: all its access dies
at once. (Capabilities like display could now be taken back alone, as
storage's grants and the display's windows are issued per app; stopping
the app is still what revoking does.)
Not yet
- A trusted path for the consent prompt: today an app could draw a fake prompt on the terminal (it cannot answer one; apps cannot type).
- Suspicious-combination warnings, simple/granular consent, per-host or other server-side scopes.
- Signing, updates, uninstall.