# Authority: who may name what (design, 2026-10-14)

A design, not yet built. It settles how sysk decides what a task, a shell or a job may reach:
the kernel's tree `/sys`, the network's `/net`, the disk's files. It came out of the networking
discussion (`docs/directions.md`, "Decided since"), when the shell was about to be reachable
from the network, and after two passes over the options and their prior art.

## The question underneath

A command typed at the keyboard is the person's. A command that arrives over the network, or that
a program writes into the shell (the shell reads s-expressions precisely so programs can drive
it), is not. Today the shell holds `(sys)` from boot, so anything that can send it a command can
read the kernel. Every design below is judged by one test: **does it still hold when the person
is not the one typing?**

## The design

```
                       manifest: profiles, needs, never-rules  (checked by tools/wire.rkt)
                                         |
        console --- terminal --- console shell  [profile: observer]  -- its tree:  /  /sys (read)
       (trusted                         |
         path)                          | names a path in a command
                                        v
                                  job  [gets exactly what was named, within its declared needs;
                                        the kernel's job check refuses anything more]

        network --- net --- network shell   [profile: guest]       -- its tree:  /guest  (nothing else)

        operator (control of tasks): assumed at the console, through the trusted path, for a session
```

Five parts, each the smallest that does its job:

1. **Profiles: named bundles of lends, in the manifest.** A profile says what a shell starts
   holding: `observer` is `/sys` to read and the disk's root; `guest` is one directory of the
   disk; `operator` adds control of `/sys`. *From Solaris' rights profiles.* Not roles with
   identities: a profile is a list of handles, nothing more.

2. **Authority follows the input channel.** There is a shell per input. The console's shell
   starts with `observer`; the network's with `guest`. What a command can reach depends on where it
   was typed, structurally: the network shell does not hold `/sys`, so no command it is sent can
   name it, and there is no tag on a command to forge. *From Unix's `/etc/securetty` (root only on
   the physical console) and Solaris' profile shells.*

3. **Naming a path lends it.** `wc /notes/shopping` lends that one file to `wc`, read-only, until
   the job ends. This is already how jobs get files (sysk 031); `/sys` joins it unchanged, so
   `cat /sys/task` at the console just works. *From the powerbox pattern (CapDesk, Polaris, the
   macOS sandbox's file picker): designation is authorization.* Sound because of part 2: only the
   console's shell can designate `/sys`.

4. **Programs declare what they need.** A program's contract in the catalogue gains a clause:
   what it reads or controls, by argument (`cat` reads its path argument) or by fixed path (`top`
   reads `/sys/task`). The shell lends only what is declared, and the kernel's job check refuses
   a lend beyond it, as it refuses a parameter out of range today. Even a confused shell cannot
   over-lend. *From macOS entitlements, OpenBSD's `pledge` and `unveil`, Fuchsia's component
   manifests.*

5. **`never` rules on authority, checked before anything runs.** A few lines in the manifest:

   ```
   (never (reaches guest /sys))           ; nothing the network shell starts ever holds /sys
   (never (reaches guest (control /sys))) ; nor control of it
   ```

   Handles move only by lending, down a tree, with rights that only narrow, so who can ever hold
   what is decidable (the take-grant model, 1977). `tools/wire.rkt` decides it over the manifest,
   and the job check over each job, and refuses with the path that breaks the rule. *From
   SELinux's `neverallow`, checked here against the real wiring rather than a description of it,
   and from seL4's capDL: the manifest is already a complete, checked statement of every handle.*

And one later part:

6. **`operator`, the one elevation.** Control (a `ctl` write that stops a task or restarts a service,
   [services.md](services.md)) is never held by
   default. At the console, `!sh /sys ctl` asks the terminal, which shows the request on a line
   only it can write and takes a key no program can type into it; granted, a nested shell holds
   control until `exit`, with a prompt that says so. Built when there is a `ctl` file to control.
   *From Solaris (root as a role you assume, never log in as) and Windows' secure attention
   sequence.*

## The six rules for every tree (from directions.md), and where each is enforced

| Rule | Enforced by |
| --- | --- |
| No ambient root | profiles (1): a shell holds only its profile |
| Walks only go down | every server (holds today) |
| Rights only narrow | lending (holds today); declared needs (4) bound them further |
| Every server proven total | sysp, per server (holds for `/sys`) |
| Reading and controlling are separate | separate rights; control only through `operator` (6) |
| Narrow views by default | a job gets what it named; `/sys` shows no addresses |

## The second pass: what changed, what was dropped

**Changed: `never` rules are about authority, not information.** The first draft said
`(never (flow /sys -> /net))`. Over the wiring that cannot be checked soundly: data travels through
servers. The console's shell may `cat /sys/task > /notes/t`, and if the network shell could read
`/notes`, kernel state would reach the network with no channel between them. Checking information
flow properly needs labels on every file that follow the data (multi-level security), which is a
large mechanism for a small system. Instead:

- `never` rules say who can ever **hold** what. That is exact and decidable.
- Information is separated by **partitioning storage**: the network shell's profile holds
  `/guest` and nothing else on the disk, so what the console writes elsewhere is out of its reach.
- A crossing is a person's deliberate act at the console (copying a file into `/guest`), which is
  the person's to make.

Timing and other covert channels are out of scope, as they are for every system here but seL4's
proofs of noninterference.

**Checked: the trusted path cannot be imitated.** A job's output reaches the screen through the
shell and the terminal. The terminal marks its own prompts in a way job output cannot produce (it
escapes the marker in anything it did not write), and a key press only answers a request the
person made with `!`. A program can draw a fake prompt; it cannot make a real one, or answer one.

**Kept apart: the network shell needs authentication.** With part 2, a stranger reaching the
network shell gets only `/guest` and the pool's jobs. That is contained, not acceptable: anyone
who can send a packet can use it. QEMU's user networking reaches it only from the host, which is
enough for the first demo; keys come before it is reachable from anywhere else.

**Dropped:**

| Option | Why not |
| --- | --- |
| The shell holds `/sys` always (today) | ambient: anything that can drive the shell reads the kernel |
| A prompt for every use (`sudo` as the rule) | prompt fatigue; UAC's lesson. Kept only for control (6) |
| `su`, a mode holding everything | replaced by narrow, visible, session-long `operator` |
| Several roots (`A:`, `sys:`) | syntax over the same table of handles; one tree keeps tools uniform and stand-ins possible (`directions.md`) |
| Visible but locked (`permission denied`) | advertises, and invites identity thinking; absent is the honest answer |
| Labels on every object (SELinux, MLS) | policy grows without bound, drifts on copy; partitioning gets the separation that matters here |
| Learning mode, an off switch | `audit2allow` and `setenforce 0`: policy written by accepting denials. A refusal names its rule instead |
| Users and passwords | one person at one console; the question is presence, which the trusted path answers |

## `/net` (2026-10-14, after sysk 048)

The network's tree is served by the net task, over sysn's stack (`directions.md`). What a holder
of it may do comes in four levels, each harder to get than the last:

| Level | What | Who gets it |
| --- | --- | --- |
| **Read** | `/net/eth0/status`, `addr`, `arp`, `stats`; a conversation's `status` | the console shell's `observer` profile; never `guest` |
| **Talk** | one conversation, to one peer | a job, only the conversation its command named |
| **Listen** | announcing a port, taking connections | services the manifest declares (`svcd`), never a job |
| **Control** | `/net/eth0/ctl` (address, up or down), routes, flushing ARP | `operator`, through the trusted path |

**Talking is the dangerous one.** A job that can open conversations can send what it reads
anywhere. So no job holds `/net/udp/clone`. The shell opens and connects for it, as Plan 9's
`dial` does, and lends it **only the conversation's `data`**: `(ping 10.0.2.2)` gets a pipe to
10.0.2.2 and nothing else, and cannot redirect it, since connecting is a write to `ctl`, which it
does not hold. Naming the peer is granting it, as naming a path is (part 3); the net server needs
per-fid rights and lending, as `fsd` has (sysk 031).

**Declared needs** (part 4) gain `(needs (talk ARG))`: the program may hold a conversation to the
peer its argument names. The kernel's job check refuses any other lend, so a program that
declares nothing has no network, whatever the shell does.

**`never` rules** (part 5) close what must stay closed, before anything runs:

```
(never (reaches guest /net))        ; the network shell cannot use the network to go further
(never (reaches job (listen)))      ; no job opens a port
(never (reaches job (raw /net)))    ; frames and raw IP
```

**Quotas.** A client that clones until the conversation table is full denies everyone else. The net
server counts conversations per holder (the console shell, each job's lease) against limits the
manifest sets, and answers `busy` past them.

**Raw access is capture, not introspection.** Reading frames is sniffing; writing them is
spoofing. If it is ever wanted, it is an `operator` stream with a loss marker, recorded in
`/sys/audit`.

**Order.** The first milestone, `ping` from the console, needs Read and Talk. Steps 1 to 3 above
(profiles, declared needs, `never` rules) still come before the network shell, when commands
first arrive from outside.

## Prior art, in one place

| From | Taken |
| --- | --- |
| Plan 9 | per-task namespaces; `/net`; no superuser |
| Solaris | rights profiles; root as an assumed role; `/proc/<pid>/ctl`; zones' narrowed `/proc`; the trusted stripe |
| Unix `/etc/securetty` | authority by where you are typing |
| CapDesk, Polaris, macOS sandbox | the powerbox: naming is granting |
| macOS, OpenBSD, Fuchsia | programs declare their needs |
| SELinux | negative rules checked ahead of time; servers enforce for their own objects |
| seL4 capDL, take-grant | the whole system's handles in one checked statement; reachability decidable |
| KeyKOS, EROS | confinement: a job is a closed world (sysk 017) |
| Windows | what to avoid: links in a privileged namespace, prompt fatigue; and the secure attention sequence |
| Linux `/proc` | what to avoid: introspection grown without limit, leaking addresses |

## Order of work

| Step | What | Needs |
| --- | --- | --- |
| 1 | Profiles in the manifest; the shell starts with `observer` instead of a bare `(sys)` | wire.rkt, the manifest |
| 2 | Declared needs in program contracts; the job check enforces them | the catalogue, `job-check` |
| 3 | `never` rules over the manifest and each job | wire.rkt, `job-check` |
| 4 | The network shell, profile `guest`, `/guest` on the disk | sysn, the net task |
| 5 | `operator` through the trusted path | a `ctl` file worth controlling |
| 6 | `/sys/audit`: grants, elevations, refusals | step 5 |

Steps 1 to 3 change nothing a person sees at the console today and close the hole before step 4
opens the network.
