Machine Operations
Daimond can run programs on your computer (a build, a test suite, a linter, a script) inside a compartment the kernel enforces. It is free, it is two commands at a terminal, and it is Linux only. Read the box below before you type either of them.
Before you start
Three things decide whether any of this will work. Checking them takes a minute; discovering them afterwards takes an hour, because the error the browser reports names none of them.
1. A browser installed from a .deb
A snap or flatpak browser confines the programs it starts, and that confinement reaches Daimond's hand. The home interface a snap gets does not grant the hidden directories at the top of your home directory, and the hand's journal lives at ~/.local/share/daimond/hand/journal/, behind one of them. It cannot open the file it would have recorded its reason in, so it exits without a word and Chrome reports only Native host has exited. There is nothing in that sentence to act on and nothing to configure: the confinement is what a snap is for. Moving the journal does not help either, because the browser hands the program its own environment and DAIMOND_HAND_JOURNAL_DIR never reaches it.
You need not work this out yourself. The installer looks at every browser profile on the machine, writes into the ones that can use it and names the ones that cannot; where every browser it finds is confined it stops, rather than leaving you an install that appears to have worked. --check reports the same, and flags a confined browser even with a usable one installed beside it, because the one you are actually using decides.
To see what you have before you start:
snap list 2>/dev/null | grep -Ei 'chrom|brave|vivaldi|edge'
flatpak list 2>/dev/null | grep -Ei 'chrom|brave|vivaldi|edge'
Anything either command names is confined. What works is Chrome, Brave, Vivaldi or Edge installed as a .deb, or a Chromium .deb from somewhere other than Ubuntu's archive.
apt install chromium-browser gives you the snap. Ubuntu's chromium-browser package has been a stub that installs the snap since 20.04, so the usual way of getting a browser without the Software app lands you in the same place. Reach for one of the four .debs above instead.2. Run that browser once before you install
The installer writes one small file into the browser's profile directory, and that directory is created the first time the browser starts. Install a browser, never open it, and install.sh reports finding nothing (correctly, because there is nothing there yet).
3. Linux, with Landlock in the kernel
The compartment is Landlock and seccomp, and both are Linux. Landlock arrived in Linux 5.13; Unconventional systems sets out what older and unusual machines get. The hand does build and run on macOS and Windows and will introduce itself to the page, but it reports that it can fence nothing, and Daimond then refuses every command rather than run one unfenced. The refusal names what is missing: macOS wants a sandbox profile applied through sandbox_exec, or App Sandbox entitlements if the hand ever ships in a bundle; Windows wants a Job Object to bound the process tree and an AppContainer SID to bound what it may open. Neither is built.
Installing it
Two commands at a terminal, from the top of the Daimond repository, then two things in the browser. They add up to one small JSON file per browser profile plus a binary you built: nothing listens on a port, no daemon runs, and there is no secret to steal.
-
Build the hand
cargo build --release --manifest-path hand/Cargo.toml--manifest-path, not-p: the hand is a cargo workspace of its own, so-preports no such package and builds nothing. -
Grant a folder, and register the hand
hand/install/install.sh --workspace ~/workPut your own folder in place of
~/work. It bounds everything any command can read or write, so choose one for the work: the script refuses your home directory and/outright, since granting either grants everything. It has to exist already.That one run does three things, each with a failure you would otherwise meet later:
- Writes your folder into
root.txt, at~/.local/share/daimond/hand/journal/root.txt. The hand will not serve a page until it has been told, and it never guesses, a guessed folder being a guess about what a command may touch. To change it later, run the same command with a different folder. - Creates that journal directory at mode 700. It holds the record of every command every Diamond has run, so the hand refuses to write it anywhere other users can read; a directory made under the usual umask is 755, and the hand would not start.
- Writes the registration into every usable browser profile, and prints which. That file is what lets the browser start the hand at all. It is JSON and nothing else: the script builds nothing, downloads nothing, starts nothing and needs no root.
DAIMOND_HAND_ROOTis a trap. The environment variable does the same job asroot.txtand takes precedence, and it will not work: your browser starts the hand with its own environment, and yours is not in it. Use the file. - Writes your folder into
-
Load the extension
The previous step printed the path. Open
chrome://extensions, turn on Developer mode, choose Load unpacked, and select theext/directory.It carries a fixed public key, so its id is the same on every machine (which is how the registration written a moment ago can already name it), and the manifest names a single origin,
https://daimond.oxedyne.com, as the only page allowed to speak to it. No other page in your browser can reach it at all. -
Restart the browser
It reads those profile directories only when it starts, so a file that appeared while it was running is invisible until then. This is the commonest reason a correct install looks broken.
Then check it
hand/install/install.sh --check
Changes nothing, and prints one line per thing that has to be true: a usable browser, the registration, the binary, the journal directory and its mode, root.txt, the journal sitting outside the granted folder, and the extension. A failing line carries its fix on the line below it. Work from the top, since a later line often fails only because an earlier one did.
The last two steps are yours
Nothing above chose anything on your behalf. The two things left are the two that are decisions.
Open the same folder in Daimond. On the Workspace panel, use the Machine chip to open the folder you granted. It has to be the same one: the hand writes a token into <folder>/.daimond/workspace.id and names it when it introduces itself, and the page reads that file through the handle it already holds and compares. A workspace that is the browser's own storage, or a different folder from the one in root.txt, has its commands refused with a line saying which. The token lives inside .daimond because the compartment always denies that directory, so a command cannot read the token and cannot answer for a folder it is not in.
Allow the first command. The first time Daimond wants to run something, a Daimond Hands window opens and asks. No browser permission covers “may run programs on this computer”, so that window is the approval and the extension records your answer itself, which is also what makes it revocable: the browser has nothing to take away. Until you allow it, nothing runs.
The window names the folder and the page that asked, and says in one line how far a command can reach on this machine. What this covers, and what it does not opens the rest: what the compartment stops, what it does not, and how to withdraw the permission.
When it does not work
Start with install.sh --check again: it walks every check below in order and stops at the first thing that is wrong. The three steps it automates are useful anyway, being what to reach for when --check passes and Daimond still says the hand is not there.
Run the hand yourself
hand/target/release/daimond-hand < /dev/null
starts the hand exactly the way your browser will, with no browser at the other end, so whatever it would have said to the browser it says to you. Three answers:
daimond-hand: the page closed the pipe.Configured and ready. Whatever is wrong is on the browser's side, and restarting it is the first thing to try.- This hand has not been told which folder it may work in:
root.txtis missing. Runinstall.sh --workspaceagain. - …is readable by users other than its owner: the journal directory is not 700, and the hand will not write the record of your commands where another account can read it.
chmod 700it.
Anything else is a whole sentence naming the path, the cause and the fix, the hand having nowhere to print a code anyone could look up.
hand/target/release/daimond-hand --report
prints what the compartment can enforce on this kernel and, at greater length, what it cannot. Read the second list rather than skimming it: on a kernel below Linux 7.1 it includes a way out of the compartment entirely. It also names the folder from root.txt, so a stale one shows up here and not at the first command.
Read the journal
tail -n 5 ~/.local/share/daimond/hand/journal/hand-*.jsonl
One JSON object per line, in the order things happened: the handshake, every command, every refusal with its reason. If the hand started at all it wrote something here. An empty directory after a failed attempt means it never got far enough, which on Ubuntu almost always means the confined browser at the top of this page.
Ask the extension
In the browser, on the Daimond tab, open the developer console and run:
await DaimondHand.status()
It answers with JSON: whether a hand is paired, which folder it says it was granted, what it can enforce, and, where the answer is no, a reason written as a whole sentence and not a code. Daimond shows the model that same sentence, so it explains what the daimon has been told.
What it actually is
Not a virtual machine, not a container, not emulation. A command starts as an ordinary process, owned by you, on your own kernel, against your own files, with capabilities removed first. Nothing is simulated and nothing copied in, so a build here does the work the same build does in your own terminal. Four parts, each doing one job:
- The page asks. Daimond works out what this turn may touch and sends the whole rule with the request. It never sends a shell line:
argvis an array, and there is nothing to inject into. - The extension relays. Daimond Hands is the only thing the page can hand a command to, and one origin may speak to it.
- A small local program runs it. The hand builds the compartment out of the rule it was sent, applies it to itself, and then becomes the command. It is no supervisor watching a child; nothing is left of it once the command starts.
- Your browser starts the hand. The whole of the introduction, and why there is no port and no password. A background service on a port would be reachable by every page you visit, defended only by a secret you had pasted somewhere.
Daimond implements no tools
There is no built-in ls, no built-in grep, no bundled toolbox and no shell. ls is /usr/bin/ls, the one already on your machine, at the version you installed.
Every program on the computer is reachable, the compartment adding the system to it read-only: /usr, /bin, /sbin, /lib, /lib32, /lib64, /libx32, /etc, /opt, and the harmless devices /dev/null, /dev/zero, /dev/full, /dev/random and /dev/urandom. Without them nothing starts, not even cat: the loader has to read the shared objects, and the process has to open something for the standard streams.
A bare name is looked up on PATH, which is /usr/local/bin:/usr/bin:/bin unless a granted toolchain has added to it. What that finds is resolved to an absolute path and checked against the compartment like anything else, so finding is not permission: a PATH pointing outside finds a program the command may not run, and the refusal names it.
["cargo", "test", "--lib"]) and a semicolon, pipe, redirection, backtick, $(…) or && arrives at the program as a literal argument. To chain two commands the daimon runs the tool twice, which is better anyway: it reads the first result before choosing the second.What a command can and cannot do
Files
- Read and write inside the granted folder. That, minus Daimond's own
.daimonddirectory, is the writable world. - A private temporary directory. Writable, with
TMPDIRpointing at it, removed when the run ends, unreachable by any other run, and the one place outside your folder a command may write./tmpis outside the compartment, and a build that cannot write a temporary file fails part-way through for a reason nobody can read. - The system, read-only.
/usr,/etc,/optand the rest of the list above. - Everything else is refused by the kernel, not by a check somebody wrote. A file one folder outside the grant comes back
Permission denied, and the daimon is shown that refusal instead of the file.
One consequence to learn before it looks like a broken tool. Where the whole granted folder is writable (a chat not scoped to a Diamond), that folder itself cannot be listed, and a file cannot be created directly in it. Landlock cannot grant a directory and withhold part of it, so the children are granted one by one and the directory is not. Everything one level down works normally.
Named local sockets are refused, always
A command cannot create a unix socket, whatever else it was allowed. The cost is not small: a local database reached over a socket file, docker, anything wanting X11, and ssh-agent. socketpair is untouched, which is what builds actually use, so a from-scratch cargo build is unaffected.
Below Landlock ABI 9 (Linux 7.1), connecting to a socket file is not governed by the compartment at all. Measured: with the whole fence in force and the network refused, one message to the session bus starts a process that was never fenced, and that process reads a file the fence denies. That is no leak at the edge of the compartment but a way out of it, so it is closed unconditionally: reaching the session bus has nothing to do with whether a build was allowed to fetch a crate.
ssh does not work
Two independent reasons, and closing either alone would not be enough. ~/.ssh is not in the compartment: every path in one is built from the folder you granted, so nothing outside that folder can be named, and the app cannot add one. And the agent socket is a unix socket, refused by the paragraph above. git push over SSH fails for both reasons at once. Granting your home directory would remove the first reason along with most of the point of the compartment, which is why the installer refuses it.
Everything that touches only the repository does work: git status, diff, log, add and a local commit are ordinary file operations inside the folder you granted.
A toolchain needs a grant
Open a Diamond and the Workspace panel shows a Toolchains row with five buttons: Rust, Node, Python, Go, Git. All five start off, so cargo is refused until you turn Rust on: ~/.cargo and ~/.rustup sit under your home directory, and your home directory is not in the compartment.
Git is the odd one out, because it puts no program within reach: git is already in the hand's read-only base, which is why git status and a local commit work with no grant at all. What the button adds is your own configuration, read-only: your name, your email, and the hooks core.hooksPath names, so a commit is attributed to you and a pre-commit hook actually runs. Your stored passwords and ~/.ssh are denied by name, even though the compartment would not reach them anyway.
Three things about that are deliberate:
- It is never inferred from what was asked to run. A compartment that widened itself to fit the requested binary would be a compartment the model chooses, and the whole arrangement rests on its not being one.
cargois reachable because you granted the Rust toolchain, and for no other reason. - It is per Diamond. A grant belongs to one Diamond, alongside the folders that Diamond holds, and is taken back the same way.
- It is a short list of named directories, never the home directory. Rust grants
~/.cargo/binand~/.rustupread-only, makes the registry, the git checkouts and the package-cache lock writable because cargo cannot build without writing them, and denies~/.cargo/credentials.toml, wherecargo loginwrites your crates.io token. The other four are drawn the same way, and each leaves out the file holding a secret: a registry token for the language toolchains, and for Git the passwords a credential helper stores in plain text.
The network
Whether a command may reach the network depends on the permission mode and on what the turn has already read. Under Ask and Guarded, once a turn has taken in content from outside (a web page it read or fetched, an email, the output of an earlier command), every command in that turn runs with TCP refused, and the daimon is told why, so it reports the cause instead of a broken project. Under Bypass the network is kept whatever the turn has read.
What is refused is TCP bind and connect, which is what Landlock governs. UDP and raw sockets are outside it.
32-bit binaries are killed, not filtered
The system-call filter is compiled for one architecture and checks the architecture first: the compatibility ABI numbers its calls differently, and a filter built for the wrong table would refuse the wrong things and report success. A program with a different personality is therefore killed outright instead of filtered. That fails closed, and a build that execs a 32-bit helper will not work here.
The permission ladder
One setting, three rungs, and the axis is what Daimond does without asking. It starts on Guarded.
Ask every time
Every command is put to you before it runs, with the command and the directory on the prompt, and Daimond asks once in each conversation before it reaches the web. For watching each step, and for a machine holding something you cannot lose.
Guarded
The default. Commands run without asking. A turn that has read outside content loses the network, and Daimond asks once, in that conversation, before reaching anywhere the model chose. One yes covers every site until the conversation ends; a new chat or a fresh daimon asks again, and an unusually long address is always put to you on its own.
Bypass
Nothing is asked. Commands run, pages are fetched, and a build needing the network still has it on the turn's tenth command as well as its first. Chosen once, deliberately, then quiet.
A rung never changes what is possible. The compartment a command runs inside, the system-call filter under it, the folders a Diamond is scoped to, a granted toolchain, the marking on content from outside, and the journal are identical on all three. A rung changes only what you are asked.
It is a word in the chat header beside the model, because a mode you have to remember is one you will be wrong about. Click it to change it, or press Ctrl K (Cmd K on a Mac) and type the rung's name.
Unconventional systems
The compartment is built out of a fixed list of absolute paths, and that is what decides whether a given machine works. Run --report on yours before assuming either way.
Will not work
- NixOS and Guix. Programs live in
/nix/storeor/gnu/store, neither granted, and/usr/binholds almost nothing. A bare name resolves throughPATHto a store path outside the compartment, and the command is refused by name. The app cannot widen it today. - A kernel without Landlock. Before Linux 5.13, or compiled out, or absent from the active LSM list, or removed by a hardening patch. The hand reports
fence:noneand Daimond refuses every command rather than run one unfenced. Check thatlandlockappears in/sys/kernel/security/lsm. - An architecture other than x86-64 or aarch64. The filter's syscall numbers are written out per architecture and there is no third table. The hand will not guess one, so there is no filter, and without a filter no command runs.
Partly
- Linux 5.13 to 6.6. Files are fenced, but Landlock has no network rules before Linux 6.7, so a command that would have to run with the network withheld is refused outright, with a sentence saying so, instead of running with the network it was supposed to lose. Bypass works, and Guarded works until the turn reads something.
- Homebrew on Linux, Flatpak and Snap. None of
/home/linuxbrew/.linuxbrew, the Flatpak trees or/snapis granted, so a program installed that way is refused, while what your distribution put under/usris reachable in the same session. That is about the programs a command runs; a browser packaged that way is a separate problem, and a fatal one. See Before you start. - Version managers other than the two that are known. The toolchain paths are a fixed list, and
~/.nvmand~/.pyenvare the only version managers on it.asdf,mise,volta,rbenvandsdkmaninstall under~/.asdf,~/.local/share,~/.volta,~/.rbenvand~/.sdkman, none of which anything grants, including the Python grant, which covers~/.local/binand~/.local/liband pointedly not~/.local/share.
Fine
- Debian, Ubuntu, Fedora, Arch, openSUSE with their own packages, on a kernel new enough for
--reportto name a Landlock ABI. - musl and Alpine, given the same.
- BusyBox coreutils. A multi-call binary decides what it is from the name it was invoked by, and the hand execs the resolved binary under the name that was asked for, so it still knows which tool it is meant to be.
A terminal, too
The Terminal panel opens a real terminal on the same machine, inside the same compartment, and you type into it. It is separate from a command run by the daimon, and it exists because some programs ask their questions of a terminal and not of standard input. Keystrokes are never written to the journal, for that same reason. It needs a hand paired, like everything else on this page.
What is recorded
Every run (what was asked, what it was refused, what it returned) is appended to the journal in ~/.local/share/daimond/hand/journal/, as ordinary JSON lines you can read. The entries are hash-chained, and the journal sits outside every compartment, because the hand refuses a fence that would reach it. So a command cannot rewrite the record of itself, and no permission mode turns this off.
What is not protected
--report prints two lists, and the second is the point of running it. Every entry in it was measured on a running kernel rather than inferred, and it changes with your kernel, so read your own. The entries fall into a few groups:
- Asking about a file is not opening it.
statstill answers outside the compartment, so sizes, timestamps, ownership and the presence or absence of a file are readable. The compartment governs opening. - The system grant is wide.
/usr,/etcand/optread-only is what lets a command run at all, and it also means every world-readable file under/etccan be read and every tool on the machine executed./etcin particular holds a great deal of machine-identifying detail. - The filter is a deny-list. It removes named capabilities from a command and is no syscall sandbox. Anything it did not name is permitted, including a call a future kernel adds.
- A filter cannot follow a pointer. It sees a syscall number and six registers, never what they point at: no path, no address, no filename. So a refused
chmodis refused everywhere and an allowed one allowed everywhere.chmod 777is refused;chmod 644on a private key is not, because 644 adds no write and is the mode cargo sets on everything it unpacks. - Timestamps are not protected anywhere. A command can rewrite any file's times, on any part of the machine, because cargo cannot unpack a crate from the registry without doing it. That is the clearest place where keeping builds working cost coverage.
- Things already open, and names already made. A file descriptor opened before the compartment took hold keeps working, and a hard link made earlier is a second name for the same file: a command cannot create one into a denied subtree, and cannot undo one that exists.
- The one exception to “only inside your folder” is the private temporary directory described above.
Taking it back
Three different things, and all three come up.
- Withdraw the permission. Click the Daimond Hands icon in the toolbar. Running commands on this computer is listed there beside the sites you have approved, with a Revoke button. Revoking stops whatever is running at that moment, immediately, not at the end of the current build.
- Take the folder away. Delete
root.txt. The hand then refuses to serve at all, whatever the browser says, and the refusal is a whole sentence and not a silent nothing. - Remove the registration.
hand/install/uninstall.sh, or--dir DIRfor one directory. That deletes the file the installer wrote, so the browser can no longer start the hand. The binary is left where it is; the script did not put it there.