What a project allows
Modes, command rules, tool lists, confirmation, and the exeora.toml a machine can add.
A new project allows everything, which is what every project did before this setting existed. Turning a policy on is always a decision someone made, never something that happened to a project on its own.
The policy is set from the dashboard and applies from the next tool call. Nothing has to reconnect.
Modes
| Mode | What it means |
|---|---|
allow_all | Every tool, and any command. The default. |
read_only | Reading, listing and searching. No edits, no writes, and no commands at all. |
allow_list | Reads and edits, and only the commands you name. |
Command rules
Entries in the allow and deny lists are rules, matched against the words of the command:
| Rule | Matches | Does not match |
|---|---|---|
npm | npm, npm test, npm run build -- --watch | npx test |
git push | git push | git push --force, git status |
git * | git, git push origin main | gitk |
cargo build * | cargo build, cargo build --release | cargo test |
A single word means the program and any arguments. Two or more words with no star is the exact form, which is the only way to say "this and nothing else".
It is not a glob. * is honoured as the final word and nowhere else, because a syntax that resembles a glob without being one gets misread rather than learned.
Refusing commands
A deny list is checked before the allow list and applies in every mode, including allow_all. That is the only reason it is worth having: allow_list already refuses everything it does not name, so the sentence only allow_all can express is "anything, except sudo".
Why shell syntax is refused
Commands run through a shell, so npm test; sudo rm -rf / is one command whose first word is npm. A list compared against words and nothing else would let that straight through while appearing not to.
So whenever a list is in force, which means allow_list or any project with a deny list, a command carrying a shell metacharacter is refused outright. Turning shell on opts out of that, which reduces both lists to suggestions, and the dashboard says so.
Which tools exist
Naming tools is the granularity the modes cannot express. read_only is the only per-tool statement a mode makes and it is all or nothing, so "edit files, never run a command" has no other way to be said.
Left unset it means every tool, including any added later, so a project that never restricted its tools does not silently refuse the next one to exist.
Confirming a change
approve asks a person before anything that edits, writes or runs, naming the file or quoting the command. Reads are never interrupted: a prompt nobody can decline is one people learn to click through.
Who gets asked depends on the client:
- A client speaking MCP 2026-07-28 is asked in the conversation the call came from, which is the best place for it.
- Everyone else, which today includes Claude and ChatGPT, is asked out of band: the terminal running
exeora connect, if that machine has one, and the dashboard, at the same time. The first answer wins.
Nobody answering within ninety seconds refuses the call, because an unattended machine should fail rather than hang.
exeora.toml
A project may carry one in its root. It can only narrow what the account allows, never widen it, so whoever controls a machine can restrict an agent further and cannot grant themselves anything.
mode = "allow_list" # allow_all | allow_list | read_only allow = ["npm", "git *"] deny = ["sudo", "rm *"] shell = false approve = true tools = ["read_file", "grep", "run_command"]
Write one with:
exeora init
Every key is optional, and leaving one out means the file has no opinion about it rather than asking for the strictest value. A file that says only mode does not also switch the shell off.
Narrowing runs per field, each in its own direction: the stricter mode wins, the allow and tool lists intersect, the deny lists unite, and shell survives only if both sides permit it. Deny uniting where allow intersects is the same rule seen from the other end, since refusing is the strict direction.
A file that cannot be parsed is reported on the terminal and ignored. Refusing every call over a typo would stop a project dead, and ignoring it silently would remove a restriction someone believed they had.