Skip to content
ivralo
Sections

Docs / Reference

permissions.json

permissions.json is where you widen or narrow what ivralo may do without asking: shell command prefixes, the hosts it may reach, and the tools an unattended run may use. This page lists its keys and how the two files combine.

The two files

FileWhereApplies toWho writes it
The home'spermissions.json in your ivralo home, or the path IVRALO_PERMISSIONS namesEvery workspaceYou, by hand. Nothing in ivralo writes it.
The repository's.ivralo/permissions.json in the workspace, outside gitThat repository onlyYou, by hand; the approval dialog's p adds one entry; /permissions remove <n> takes one out.

Either may be absent. When IVRALO_PERMISSIONS points at the repository's own file, there is one file and it is read once. /permissions in the session and ivralo doctor show what each file contributes.

Keys

The file is one JSON object. Every key is optional.

KeyTypeDefaultWhat it does
versionnumbernoneAccepted and not read. Write 1.
allowlist of strings[]Shell command prefixes that run without asking.
denylist of strings[]Shell command prefixes that are always refused.
timeoutwhole number120Seconds a shell command may run when the call names no time of its own. At least 1; anything above 600 is cut to 600.
allow_toolslist of tool names[]Tools that may run with nobody there to approve them.
hostsobject with allow and denynoneHosts the shell, the web tools and the Python worker may reach without asking, or never.

An unknown key, a wrong type or a bad entry is reported by ivralo doctor, and that entry is dropped. The rest of the file still applies. A file that is not valid JSON is reported as unreadable.

allow and deny

An entry is a command prefix, matched word by word. Case and runs of spaces do not matter.

  • uv run pytest matches uv run pytest -q, but uv run does not match uv runner.
  • gh and git commands are also matched in their canonical form, so a global option between the words, such as git -C repo push, hides nothing.
  • A command made of several parts (a && b, a pipe) is judged part by part, and the whole command is as strict as its strictest part.

The built-in deny list sits under both files and nothing lifts it: programs that reach the network (curl, ssh, nc), the system's package managers, anything that rewrites git history, every spelling of git push, and merging or approving a pull request. An allow entry that the built-in list denies anyway is reported by ivralo doctor; the built-in list wins. See Shell commands.

An allow entry approves a command, never the host the command reaches. A command that names an unknown host still asks.

allow_tools

The only way a run nobody is watching, such as ivralo ask in a pipe, a schedule or a job, may edit a file or change git. The list is closed: any other name is reported and dropped.

NameWhat it lets through
edit_fileExact edits to a file. Also lets the symbol edits below through.
write_fileWriting a whole file, including a new one. edit_file never implies it.
replace_symbol, replace_range, insert_before_symbol, insert_after_symbol, delete_symbol, add_importThe symbol-addressed edits, one name each.
git_create_branch, git_stage_files, git_unstage_files, git_commit, git_revert_commit, git_restore_fileThe git writes, one name each.

A send, such as mail or a chat message, is never on this list: it always asks. A push is not either; only server mode grants it to an unattended run. See Server mode.

hosts

{ "hosts": { "allow": ["pypi.org", "10.0.4.12"], "deny": ["metadata.google.internal"] } }

An entry is a host name or an IP address. A name covers its subdomains: example.com covers api.example.com. Wildcards, ports, network masks and URLs are refused. An address written another way (127.1, a hex form) is refused with the form to write instead.

In the approval dialog, p on a call that asked because of its host writes that host into the repository's hosts.allow. See What leaves the machine.

How the two files combine

KeyCombined
allow, deny, allow_tools, hosts.allow, hosts.denyThe union of both files.
timeoutThe repository's, when it is not 120; otherwise the home's.

A deny in either file denies, because deny is read before allow. So a repository's file can add a deny the home does not have, and a home's deny cannot be lifted by a repository.

The timeout rule has one corner: a repository file that writes 120 by hand does not override a home that raised it. Write the number you mean in the file you mean.

In a folder you have not trusted

A repository you cloned could ship a .ivralo/permissions.json that lifts the gate. Until you trust the folder, only the parts of its file that narrow apply:

Repository keyUntrusted folderTrusted folder
denyAppliesApplies
hosts.denyAppliesApplies
allow, allow_tools, hosts.allow, timeoutIgnoredApply

In an untrusted folder the dialog does not offer p, and says why. See IVRALO.md for what else trust changes.

Example

A home file that lets the project's tooling run, reaches the internal package mirror, and lets a nightly schedule commit its own edits:

{
  "version": 1,
  "allow": ["uv run pytest", "uv run ruff", "uv add", "make test"],
  "deny": ["python -c", "rm -rf"],
  "timeout": 300,
  "allow_tools": ["edit_file", "git_stage_files", "git_commit"],
  "hosts": {
    "allow": ["pypi.internal.example.com"],
    "deny": ["169.254.169.254"]
  }
}

A repository file beside it that only adds:

{
  "allow": ["npm run lint"],
  "deny": ["npm publish"]
}

Together, in a trusted folder: five allow entries, three deny entries, a 300-second timeout from the home, and the home's tools and hosts.