Permissions
Powerful but not annoying. Ordinary project work is friction-free; anything destructive or unusually broad stays visible — and answering a prompt takes milliseconds.
Capabilities, not tool names#
KHAELOR evaluates every action against capabilities — what the action actually does — rather than opaque tool names:
| Capability | Meaning |
|---|---|
file.read | Read file or directory content, anywhere |
file.write.project | Create or modify a file inside the project |
file.write.outsideProject | Create or modify a file outside the project root |
process.execute | Run a foreground shell command |
process.background | Start a long-lived background process |
network.access | Command whose primary purpose is network I/O (curl, ssh, …) |
git.modify | Git command that mutates repository state (commit, push, reset, …) |
Paths are fully resolved (symlinks, ..) before classification — a
write to ./x/../../etc/hosts is correctly treated as outside the project. One
shell command can carry several capabilities at once (a curl | git apply pipeline
carries process.execute + network.access + git.modify);
deny beats ask beats allow across all of them.
allow · ask · deny#
Each rule maps a capability (and optionally a subject pattern — a command prefix or path glob) to one of three actions:
allow— runs silently.ask— the inline panel appears; you decide.deny— refused without a prompt; the agent is told why and steered to a different approach.
Rules are evaluated last-match-wins across four layers, later layers overriding earlier ones:
built-in defaults → user (~/.khaelor/config.json) → project (.khaelor/config.json) → session grants
Anything no rule mentions defaults to ask — the system fails closed.
The permission panel#
Never a bare Allow? y/n. The panel appears inline, already focused, and takes
three keys at most:
╭─ KHAELOR requests permission ──────────────────────────╯
│ Run process.execute │
│ npm install │
│ │
│ Working directory │
│ ~/dev/project │
│ │
│ ⚠ Installs packages (writes node_modules) │
│ │
│ [ Enter ] Allow once │
│ [ A ] Always allow "npm install *" in project │
│ [ Esc ] Deny [ Tab ] Details │
╱─────────────────────────────────────────────────────╯
| Key | Effect |
|---|---|
| Enter | Allow once — covers exactly this one call, nothing wider. |
| A | Always allow in this project — persists the exact pattern shown (e.g. npm install *) to .khaelor/config.json. When several candidate patterns exist, A cycles specificity (npm run dev ↔ npm run *) before you confirm. |
| Esc | Deny — with an optional one-line reason that is handed to the agent as course correction ("use pnpm in this repo"), not a dead end. |
| Tab | Details — every capability request in the decision, the matched rule and its source, resolved absolute paths. |
For file writes and edits, the panel body shows the actual diff that would
be applied, not just a path. For background processes it notes the process keeps running. While
a panel is open the status line reads ● Waiting for permission — and there is
no auto-approval timeout: silence is never consent. A pending request even
survives quitting; it reappears when you resume the session.
How "always allow" patterns are generated#
The persisted pattern is derived from conservative parsing of the command, using a
per-tool prefix dictionary: git push origin main suggests
git push *; npm run dev suggests the exact npm run dev.
Crucially, KHAELOR refuses to generalize what it cannot fully read: commands
with shell operators (&&, |, ;) or substitution
($(...), backticks) get no "always" option at all — only "allow once".
A standing grant is never wider than what the analyzer could verify.
The default policy#
Out of the box, KHAELOR is safe but not annoying:
| Action | Default |
|---|---|
| Reading files | allow — except secret-shaped files (*.env, *.pem, ~/.ssh/*), which ask |
| Writing inside the project | allow |
| Writing outside the project | ask |
| Shell commands | ask — with a read-only allowlist (git status/diff/log/show, ls, cat, head, tail, which, pwd, wc) so routine inspection never prompts |
| Background processes | ask (stdin to an already-approved process: allow) |
| Network commands | ask |
| Mutating git commands | ask |
The first-run experience teaches the loop once: approve npm test with
A and it never asks again in that project.
The hardline safety floor#
Beneath all configurable rules sits a short, built-in, non-overridable deny
list — checked against a de-obfuscated rendering of the command (quotes stripped,
~/$HOME expanded): rm -rf /, rm -rf ~,
mkfs, dd onto raw devices, fork bombs,
chmod -R 777 /, shutdown/reboot, force-pushes to
main/master when detectable, and similar catastrophes. No allow rule can permit
these. It is a floor against disaster, not the primary defense — the primary defense is the
ask-by-default policy above.
Configuring permissions#
Rules live in the permissions section of your config files
(Configuration covers locations and precedence). Both a
shorthand and an explicit form are accepted:
{
"permissions": {
"file.read": "allow",
"process.execute": {
"git status": "allow",
"git push *": "allow",
"*": "ask"
},
"rules": [
{ "capability": "file.write.outsideProject",
"pattern": "/Users/me/notes/*",
"action": "allow" }
]
}
}
- Patterns use
*wildcards; a pattern without*must match exactly. - Order matters within a file (last match wins), and project rules override user rules.
- Grants made with A are appended to
.khaelor/config.jsonatomically, immediately — they are ordinary rules on the next load and survive restarts by construction. /permissionsshows the full effective ruleset with the provenance of every rule, and supports deleting or reordering.
If a capability is denied wholesale (say file.write.*: deny), the affected
tools are removed from the model's tool list entirely — the agent never wastes a turn
requesting the impossible.
What permissions do — and don't — guarantee#
An honest limitation, stated plainly: permission enforcement gates what the agent asks
to do, not what an arbitrary binary does once running. network.access
detection is a UX signal, not an egress firewall. The mitigations are the ask-by-default
policy, the refusal to generalize unreadable commands, and the hardline floor. Every
permission decision — request, matched rule, grant scope, denial feedback — is a durable
event in the session log, so you can always
reconstruct exactly why an action ran.