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
| File | Where | Applies to | Who writes it |
|---|---|---|---|
| The home's | permissions.json in your ivralo home, or the path IVRALO_PERMISSIONS names | Every workspace | You, by hand. Nothing in ivralo writes it. |
| The repository's | .ivralo/permissions.json in the workspace, outside git | That repository only | You, 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.
| Key | Type | Default | What it does |
|---|---|---|---|
version | number | none | Accepted and not read. Write 1. |
allow | list of strings | [] | Shell command prefixes that run without asking. |
deny | list of strings | [] | Shell command prefixes that are always refused. |
timeout | whole number | 120 | Seconds 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_tools | list of tool names | [] | Tools that may run with nobody there to approve them. |
hosts | object with allow and deny | none | Hosts 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 pytestmatchesuv run pytest -q, butuv rundoes not matchuv runner.ghandgitcommands are also matched in their canonical form, so a global option between the words, such asgit -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.
| Name | What it lets through |
|---|---|
edit_file | Exact edits to a file. Also lets the symbol edits below through. |
write_file | Writing 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_import | The symbol-addressed edits, one name each. |
git_create_branch, git_stage_files, git_unstage_files, git_commit, git_revert_commit, git_restore_file | The 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
| Key | Combined |
|---|---|
allow, deny, allow_tools, hosts.allow, hosts.deny | The union of both files. |
timeout | The 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 key | Untrusted folder | Trusted folder |
|---|---|---|
deny | Applies | Applies |
hosts.deny | Applies | Applies |
allow, allow_tools, hosts.allow, timeout | Ignored | Apply |
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.