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:

CapabilityMeaning
file.readRead file or directory content, anywhere
file.write.projectCreate or modify a file inside the project
file.write.outsideProjectCreate or modify a file outside the project root
process.executeRun a foreground shell command
process.backgroundStart a long-lived background process
network.accessCommand whose primary purpose is network I/O (curl, ssh, …)
git.modifyGit 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          │
╱─────────────────────────────────────────────────────╯
KeyEffect
EnterAllow once — covers exactly this one call, nothing wider.
AAlways 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 devnpm run *) before you confirm.
EscDeny — with an optional one-line reason that is handed to the agent as course correction ("use pnpm in this repo"), not a dead end.
TabDetails — 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:

ActionDefault
Reading filesallow — except secret-shaped files (*.env, *.pem, ~/.ssh/*), which ask
Writing inside the projectallow
Writing outside the projectask
Shell commandsask — with a read-only allowlist (git status/diff/log/show, ls, cat, head, tail, which, pwd, wc) so routine inspection never prompts
Background processesask (stdin to an already-approved process: allow)
Network commandsask
Mutating git commandsask

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.json atomically, immediately — they are ordinary rules on the next load and survive restarts by construction.
  • /permissions shows 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.