I'm still not installing Windows to make a management console happy.

I'm still working on the Linux desktop-agent idea. A company should be able to maintain the tools and settings its developers need, without handing everyone root or choosing Windows because it's easier to manage. Meanwhile, somebody is using that laptop, installing things and taking it home. That last part hasn't become any more convenient. Good. I don't particularly want a development machine I'm afraid to develop on. There's work to do.

I also want my computer to feel like my computer. The shortcuts, the terminal, where the windows go.... I'm going to spend an unreasonable amount of my life sitting in front of these things, so I reserve the right to be fussy.

I'm pinning down how much authority each job gets. Installing a development tool can involve inspecting the machine, downloading files and working through an upgrade. It shouldn't require trusting every bit of that logic with the whole computer.

Put the permissions beside the code

A company policy should be able to ask for its approved Python setup. An operator would carry the knowledge of how to inspect that installation, repair it or migrate an older version. Changing the requested version shouldn't mean repeating those installation decisions.

What may the operator do while working it out?

I'd put the request in a manifest beside its code:

capabilities {
  filesystem.read = ["/etc/os-release"]
  filesystem.write = ["/opt/company/python/"]
  facts.read = ["package.*"]
  network = ["packages.company.example"]
  exec = []
}

These are separate requests. Reading the operating-system version shouldn't grant access to SSH keys, and the package facts are information the agent has collected rather than permission to rummage through the filesystem. Permission to write beneath the managed Python directory shouldn't allow a change to /etc/passwd.

And that empty exec list is deliberate. I can see myself reaching for a shell here, run the installer, let it run its helper, stop writing host operations. An installer is still a program. So is its helper. Taking that route would leave me deciding what those commands may do as well as what the operator may do, including their own file and network access.

This operator would have to use the host operations it has been given, which could include a specific installation operation without exposing a general process launcher. No shell, then.

Who approves it? Company policy would set the maximum access, and the operator could ask for less, but changing its manifest couldn't give it more. A new destination or write path would need approval before that code could use it. I'd like to review that change as a request for authority rather than discover it buried in an installation script.

Give the logic somewhere to run

Piko's Go interpreter could run the operator. A small Go program. That's what I'm calling an operator kernel. It could use functions, loops and branches to work out what the machine needs without becoming another executable running with all the agent's privileges.

The host would supply the operations available to that program and each exposed operation would check the approved grant before reading a file, writing one or opening a connection. Anything outside that grant should fail before the privileged work. Separate jobs. The interpreter runs the decisions. The host does the privileged work. Exposing those operations gives me a place to control how the decisions reach the machine, and an operator gets the ones its job needs, not the agent's entire collection of native Go functions.

A file writer would be convenient. But approving a write path can still leave the operator replacing settings it has no business managing.

Proposed execution boundary. Operator Go logic runs in Piko and requests exposed host operations. The host checks each request against the approved manifest. Requests outside the grant stop there. Allowed operations proceed within Linux filesystem and network restrictions.
Figure 1 - Where the operator meets the machine. The design needs both checked host operations and Linux isolation.

I like keeping the complicated part in Go. An upgrade can need several steps, while a half-finished installation can need another route entirely, and Go already gives me functions, loops and branches for describing those cases. I don't want to add a new language construct whenever somebody finds another way to leave Python half installed. Changing that logic wouldn't require a new agent binary either.

Interpreted code can still call native code. The interpreter doesn't make that call safe by itself. Imports and native calls would need to respect that same boundary, with time and memory budgets around execution. Permission to manage one directory shouldn't include permission to hang the machine. I'd need those checks before trusting somebody else's operator code.

I started the interpreter to get live updates back while building pages. I wanted to see my CSS changes sooner.

Naturally, this now involves deciding who can modify a laptop.

Downloading a tool shouldn't open the network

Where may it connect? Contacting the package mirror is a reasonable part of this job, but general outbound access would let the operator contact anything else. That isn't what I asked for.

The host would check the destination when the code requests a connection. Behind that check, the worker could have a separate network namespace giving it a restricted view of networking, with an eBPF filter running a small program to check outbound traffic and limit the destinations it can reach.

What if Linux can't enforce the allowlist? Outbound access should remain blocked. That puts another boundary underneath the interpreter's checks. (“The filter isn't available, so you can go anywhere” would be a fairly spectacular interpretation of the manifest.)

The destination grant still says where the operator may connect, but what data it can read and send depends on its other permissions. That's why the file, fact and network requests belong together when I review them.

Three settings still aren't a whole file

Here's a flat SSH configuration:

PermitRootLogin no
PasswordAuthentication no
X11Forwarding no
LoginGraceTime 30

The policy could ask for three of those settings:

resource key_value_config "/etc/ssh/sshd_config" {
  settings = {
    PermitRootLogin: "no",
    PasswordAuthentication: "no",
    X11Forwarding: "no"
  }

  drift_mode = enforced
}

Changing PasswordAuthentication to yes should cause a correction on the next check. Changing LoginGraceTime to 45 should stay. A general file writer can't make that promise, so this job needs a host operation that edits the declared keys and preserves the rest. SSH's includes and conditional blocks need their own interpretation too, beyond this flat example.

I've only asked for three settings. Taking the rest would be quite a lot of enthusiasm for the job.

Look again before doing the work

The desktop won't hold still. A package manager can change a file, an update can stop halfway through, and a laptop can disappear offline for a week. Drift is normal here. Expecting otherwise would leave me permanently surprised by a laptop.

The agent would need to remember its responsibilities, observations and unfinished work. Before making a change, the operator should inspect the relevant state again. Still ours to change? Yesterday's successful read of a file shouldn't become permission to replace today's contents.

This follows the same idea as the smaller agents from Kube.ist. Someone requests a particular job without receiving all the authority needed to carry it out. The interpreter, approved manifest and host operations could separate much more involved jobs from the agent's authority too.

I could make this much easier for myself by declaring the whole machine mine, well, my agent's. It would make a miserable development computer.

I'd like to let people contribute the logic their tools need, without every operator becoming another privileged program I have to trust with everything. The developer should get the approved setup and keep using Linux. Maintaining that setup shouldn't require taking the rest of their computer away from them.

This entry came over from blog.haddon.me. I’ve tidied up the grammar and flow, bringing it closer to how I write now. I used AI to help with the grammar, structure and flow. I have dyslexia, and my writing often comes out as a rambling scrawl, so it can be hard to see what I was actually working on at the time. I think the tidied versions are easier to digest and more interesting to read. The artefacts got the same tidy-up while I was at it.

  • Read next

    Terms and conditions apply

    What am I promising when I call a calculation reproducible? I'm working out which choices belong to the implementation, and which belong in its contract.