Skip to content
DocsPermissions & security
On this page

Permissions and approvals

Permissions decide which of the agent's actions wait for your answer. They are rules inside Haarness, not an operating-system sandbox: they catch an agent doing something you did not intend, not code that is trying to get around them. Read Trust boundaries before you rely on them.

What happens by default?

  • Runs without asking: reading and editing files in the project, shell commands, web fetches and MCP tools.
  • Asks first: a file outside the project, a .env file, an edit to Haarness's own configuration, image or video generation, switching agents, websites in your Chrome, and Haarness Cloud actions that spend money or delete something.
  • Who answers: a new chat starts in Auto, where a reviewer model approves routine requests once and leaves the rest to you. In Review you answer each request; in Yolo every request is approved once without asking.
  • Nothing is denied. Add your own rules to be asked about more, or to block things.

The same rules apply in the terminal UI, Desktop, haarness run, schedules, workflows and child agents.

What does the agent do without asking?

Inside your project the agent reads and edits files, runs shell commands, fetches web pages and calls MCP tools without asking.

It asks before it:

  • reads or writes a file outside the project folder with its file tools;
  • reads a .env or .env.* file (.env.example, and files inside node_modules, are allowed);
  • changes a file Haarness takes permissions, hooks, MCP servers or providers from, such as haarness.json, .mcp.json, .haarness/config.toml, .claude/settings.json or extension code, because that could widen its own permissions. This asks every time;
  • generates an image or a video, which costs money;
  • switches itself to another agent;
  • uses a site you set to Requires approval in Desktop's browser settings (every site is allowed until you do);
  • uses a website in your Chrome (every site asks, in each chat, until you allow it);
  • creates, starts, changes or deletes a Haarness Cloud workspace, agent or schedule, runs a Cloud schedule, or sends a Cloud workspace a message.

Three of those checks are narrower than they sound:

  • Shell commands are not confined to the project. Haarness asks about a path outside the project only for cd, rm, cp, mv, mkdir, touch, chmod, chown and cat, and for output redirects. python script.py, ls ~ or curl run without asking, and can reach anything your user account can.
  • The .env check covers the read tool only. cat .env in a shell is not asked about. Searches (grep, glob) leave .env files out and say how many they skipped.
  • The configuration check covers the file tools only. A shell command that writes haarness.json inside the project is not asked about.

To be asked about every command or edit, write a rule.

Review, Auto or Yolo: how much does a chat ask?

Every chat is in Review, Auto or Yolo. A new chat starts in Auto unless your own configuration sets another default (defaults.approval_mode).

Mode A request that needs an answer
Review Shows an approval card and waits for you
Auto Goes to a reviewer model, which approves it once, blocks it, or leaves it to you
Yolo Is approved once, without asking

Switch the chat you have open:

  • Terminal UI: press Ctrl+Y to step through Review, Auto and Yolo, or type /approvals review, /approvals auto or /approvals yolo.
  • Desktop: the mode button at the left of the message box opens Review, Auto and Yolo; ⌘⇧A (Ctrl+Shift+A elsewhere) steps through them.
  • Web app, iPhone and Android: the + button in the message box lists the three modes. A mode other than the default shows as a small pill next to the model; Yolo always shows, in red.
  • On an approval card: Once + Auto (u) in the terminal UI, or Allow once, then Auto in Desktop and on the phone, allows that request and then switches the chat to Auto.
  • From the command line: haarness --approvals yolo opens the chat in Yolo (review and auto work the same way), and haarness run --approvals … runs in a chat set to that mode. --auto is short for --approvals auto.

The mode belongs to the chat and is kept by the Haarness server. Every app attached to that server sees and applies the same mode: the terminal UI, Desktop and your phone through remote control. It stays on after you quit, and the server keeps answering for an Auto chat while no app is open.

  • Auto covers one chat and the agents it starts. Your other chats keep their own mode. Setting a chat back to Review makes its agents ask again.
  • Auto allows once each time. It never saves an "always" rule, so switching back to Review leaves nothing behind.
  • Turning Auto on also sends the requests already waiting in that chat to the reviewer.
  • /new in the terminal UI starts the new chat in the open chat's mode. In Desktop, the button with no chat open sets the mode for new chats Desktop starts in that project.
  • Some requests still come to you in Auto: the requests that always ask, a website in your Chrome, and an ask rule you wrote for something specific, such as "bash": {"git push *": "ask"}. (An ask for a whole tool, such as "bash": "ask", is reviewed.) Questions the agent asks you still wait for your answer.
  • Auto never overrides a deny rule, the plan agent's limits or a site you blocked.

Yolo approves every request that would otherwise ask you or the reviewer, once each time, with no card and no review. Use it for work you would approve anyway, in a project you trust.

  • It never saves an "always" rule: back in Review, the same command asks again.
  • Rules still decide first. A deny rule, the plan agent's limits and the requests only you can answer still hold: Haarness Cloud actions that spend money or delete something, a website in your Chrome, edits to Haarness's own configuration, and a shell line Haarness could not read. Questions still wait for your answer.
  • Turning Yolo on approves the requests already waiting in the chat and its agents.
  • Your own configuration can make Yolo the default for new chats; a project's configuration cannot, so a repository you clone never makes its chats approve everything.
  • --yolo is different: it approves for that one command and leaves the chat's mode as it was.

What Auto's reviewer does

In a chat in Auto, a request that would show you a card goes to a reviewer model first. Your rules, earlier "always" answers and hooks decide before it, so it only sees what is left to ask. It does one of three things:

  • Approves it once. Nothing is shown.
  • Blocks it. The call fails, and the agent reads the reason, for example Auto review blocked this action (Data exfiltration): It uploads your SSH key., and is told not to reach the same result another way. The step reads Stopped by Auto's reviewer.
  • Leaves it to you. The card appears with the reviewer's note, such as Auto review · Shared state: It pushes to main.

A review that fails, takes longer than 60 seconds or finds no model never approves: the card waits for you, with a note saying Auto review was unavailable. After 3 blocks in a row, or 20 in one turn, reviewing pauses for the rest of the turn and every request goes to you. Reviewing… shows while a review takes more than a moment.

Haarness also checks shell commands itself: when a command deletes or overwrites something outside the project that none of your messages named, or where Haarness cannot tell what it deletes, the request goes to you even if the reviewer approved it. This check only ever makes Auto stricter.

Your own ! commands in the terminal UI are approved once, without a review.

What does the reviewer see, and where does it go?

The reviewer reads your messages, the agent's tool calls with their arguments, and the request itself, with the working directory and Git branch. It never sees the agent's replies or what tools returned. Only your own messages count as your permission: text in a file, a web page or another agent's message cannot authorize anything.

It uses auto_review.model if you set one in your global configuration; otherwise your global small_model, when it is on the chat's provider; otherwise the chat's own model. Unless you name a reviewer model, a review goes only to the chat's own provider. Each review is a short model request, billed like any other. haarness doctor shows which model reviews.

Tune the reviewer or turn it off

Set auto_review in your global ~/.config/haarness/haarness.json:

{
  "auto_review": {
    "model": "anthropic/claude-haiku-4-5",
    "environment": ["origin is our GitHub organization; main is protected"],
    "allow": ["Pushing feature branches to origin"],
    "block": ["Deploys and package publishing"],
    "timeout": 60
  }
}
  • environment, allow and block are plain-language lines the reviewer reads as your wishes.
  • A project's haarness.json can only add block lines and lower timeout. Anything else it sets under auto_review is ignored, so a repository cannot loosen the reviewer or choose its model.
  • "auto_review": {"enabled": false} turns the reviewer off. Auto then approves, once, every request that would show a card, your specific ask rules included, but still not the requests that always ask.

Answer an approval

The card shows what the agent wants to run, edit or fetch, and the rule an "always" would save. When several steps run together, the one waiting for you is marked: ⚠ in the terminal UI, Needs approval in Desktop.

Answer Allows Kept until
Allow once This one call Now
Always (terminal UI) Calls matching the rule, in every chat on this Haarness server The server stops
This folder Calls matching the rule, in this folder You remove it
Repository Calls matching the rule, in this Git repository and all its worktrees You remove it
Reject or Deny Nothing. The call fails and the agent is told Now

Terminal UI: y allows once and n rejects. The answer goes a moment after the key, so if you keep typing (yes, but…) your letters become a message instead. Always (a), This folder (w), Repository (r) and Once + Auto (u) reach past this one request, so the first press only stages the answer and the card says exactly what it will grant: press Enter, or the same key again, to confirm. Repository appears only in a Git repository.

Desktop: Deny, Always for this folder and Allow once. When there is more to offer, Always for this folder becomes an Always allow menu with Always for this folder, Always for this repository (in a Git repository) and Allow once, then Auto.

Rejecting one request also rejects every other request waiting in the same chat, including those from its child agents. Some requests, such as the ones that always ask, can only be allowed once. A website in your Chrome offers Allow in this chat instead of the "always" answers; see Websites in your Chrome.

See the script a command runs

Approving bash deploy.sh approves whatever deploy.sh says. So when a shell command runs a script (./deploy.sh, bash build.sh, python3 tool.py, source .venv/bin/activate), the card shows the script too: under the command in the terminal UI, behind View script in Desktop, and its first lines in haarness run.

  • Only when Haarness can be sure what runs. The script must run first in the line, after nothing but plain cd and chmod steps joined by &&. Otherwise the card names the script with one line saying why its text is not shown, for example that an earlier step can change it.
  • Allow once checks it again. If the file changed after the card showed it, the command does not run and the agent is told to ask again.
  • "Always" does not. It remembers the command's rule, not the script, so a script changed later runs under that rule without asking.
  • Never shown: files that hold credentials (SSH keys, .netrc, .npmrc, .aws/credentials and the like), shell profiles such as .zshrc, and files your read rules keep from the agent.
  • What the script loads in turn (imports, other scripts) is not shown. Windows shows no script.

What does "always" remember?

An "always" answer saves a pattern, not the exact call. Read the rule on the card before you confirm it.

You approve An "always" also allows
git status --short Every git status command
rm -rf build Every rm command
npm run dev npm run dev with any arguments
A program Haarness does not know, such as ./deploy.sh staging Every run of that program
An edit to one file Every file edit
A web fetch Every URL
An MCP tool call Every call to that tool
Reading .env Only that file
A file outside the project Its folder and everything under it

A saved "always" turns your configured ask into an allow, but never overrides a deny. If your rules ask about every rm and deny rm -rf*, answering Always to rm build.log saves rm *: every other rm now runs without asking, and rm -rf src is still refused.

"Always" is shared by every chat on the server for this folder: other chats, child agents, other windows and Desktop.

Take back an "always"

There is no screen for this yet. Use these steps.

Always (a) lasts until the server for this folder stops. Run this in the project folder to stop it, along with anything still running there:

haarness serve --stop

This folder and Repository answers are saved in your Haarness data folder (~/.local/share/haarness by default). Stop the server first, then delete the rule with sqlite3:

cd ~/.local/share/haarness
# Repository answers
sqlite3 repository-approvals.sqlite3 \
  "SELECT repository, pattern FROM grants;"
sqlite3 repository-approvals.sqlite3 \
  "DELETE FROM grants WHERE pattern = 'bash:rm *';"
# This folder answers: one database per project folder
for db in workspaces/*/haarness.db; do
  echo "$db"
  sqlite3 "$db" "SELECT workspace, pattern FROM permission_rules;"
done
sqlite3 workspaces/<id>/haarness.db \
  "DELETE FROM permission_rules WHERE pattern = 'bash:rm *';"

The next launch starts the server again without them.

Write your own rules

Put rules in haarness.json: ~/.config/haarness/haarness.json for all your projects, or haarness.json or .haarness/haarness.json in a project.

{
  "permission": {
    "edit": "ask",
    "bash": {
      "*": "ask",
      "git status*": "allow",
      "git diff*": "allow",
      "git push*": "deny"
    },
    "external_directory": {
      "~/Downloads/*": "allow"
    }
  }
}
  • The last matching rule wins. Put the broad rule first and the exceptions after it.
  • A tool name with one value covers every call: "edit": "ask".
  • A tool name with patterns matches against the call: the command for bash, the path for read, edit and external_directory, the URL for webfetch.
  • * matches anything, including /. ~ and $HOME expand to your home folder.
  • Values must be exactly allow, ask or deny. In JSON, anything else (even Deny) is ignored without an error.
Name Covers
edit Writing, editing and patching files (write and apply_patch mean the same)
bash Shell commands
read Reading files
webfetch Fetching URLs
external_directory Paths outside the project
media_generate Image and video generation
mcp__<server>__<tool> One MCP tool
mcp_resource_read Reading an MCP server's resources, matched as <server>:<uri>
* Everything not covered by a later rule

How bash rules read a command. Each command in a line meets the rules on its own: git add -A && git push is checked as git add -A and as git push. A deny or ask also catches the command behind a wrapper or inside a shell string, so an rm * deny refuses sudo rm x, timeout 5 rm x, xargs rm, find . -exec rm {} +, bash -c 'rm x' and eval "rm x". An allow never reaches through them. What a script file or python -c runs is not read.

Paths are read where they really lead. A read or edit rule meets the file a link points to, and a path written relative or absolute alike.

Haarness's own configuration opens only to a rule that names the file, such as "edit": {"haarness.json": "allow"}. "edit": "allow", "*": "allow" or a folder pattern does not open it.

config.toml with a [permissions] table is still read, but its rules are not applied in the order you write them: plain tool = "…" lines come first, then each table, with patterns sorted alphabetically. Use haarness.json when order matters.

Agents can carry their own rules too. See Agents, commands and skills.

Can a project change my rules?

Yes. Project rules are applied after your global rules, so a haarness.json in a repository you clone can turn your asks into allows. It cannot loosen Auto's reviewer, only add block lines to it. See Can a repository I clone run code?

Turn a tool off completely

A tool that is off is never offered to the model, and a call to it is refused.

{
  "tool_availability": {
    "webfetch": false,
    "mcp__github__delete_repository": false
  }
}

Names must be exact; wildcards do not work here. In the terminal UI, /tools turns a tool on or off for the current folder and remembers it.

Which requests always ask?

These ask a person every time, with no "always" choice. Auto, --yolo and hooks cannot answer them, and neither can allow rules, except one that names a configuration file (below). A deny rule still refuses them.

  • Haarness Cloud actions that spend money or replace or delete something: creating, starting, changing or deleting a Cloud workspace, agent or schedule, running or resuming a schedule, and sending a Cloud workspace a message. Reading, stopping (putting to sleep) or renaming a workspace, and pausing a schedule, do not ask.
  • Edits to Haarness's own configuration with the file tools: haarness.json, opencode.json, .mcp.json, .haarness/config.toml, .haarness/extensions.json and extension code, .claude/settings.json, a plugin's hooks/hooks.json and plugin.json, and the same files in your global configuration folder, including its prompts/. Only a rule that names the file lets such an edit run.
  • A shell command too long or too deeply nested for Haarness to read whole, when a bash ask rule might cover it, and any line too large to read at all. The card says Haarness could not read all of this command. A bash deny that might cover such a command refuses it instead.

Two kinds of website request also ask past Auto and --yolo:

  • A site set to Requires approval in Desktop's browser settings asks every time, unless you turn on Remember site approvals.
  • A website in your Chrome asks until you allow it for that chat, and allow rules and hooks cannot answer for you. See Websites in your Chrome.

A schedule cannot wait for you: it refuses these requests at once, tells the agent why, and the run goes on.

Approve everything: --yolo

--yolo (also spelled --dangerously-skip-permissions) approves requests without asking and without Auto's reviewer, for the command you give it to:

Command What it approves How long
haarness run --yolo The run's requests, with its agents and workflows, once each Until the run ends
haarness --yolo Requests in the chats you work in from that terminal UI, once each Until you quit it
haarness workflow run --yolo The workflow's requests, once each While the command waits
haarness serve --yolo Every request in every chat on that server, from any app, saving an "always" rule for each Until the server stops

Like Auto, it does not override a deny rule, the plan agent's limits, a site you blocked, the requests that always ask, or a website in your Chrome.

  • Only haarness serve --yolo approves for everyone. The server that haarness, haarness run or Desktop start for a folder keeps running after them, and it keeps asking: the terminal UI and Desktop you open there later, and your other runs, are not affected, and no rule is saved.
  • A message you sent from haarness --yolo keeps approving its own requests until its turn ends, even if you quit first. The status band shows approvals: --yolo while it is on.
  • haarness workflow run --yolo --detach is refused: once the command returns, nothing is left to approve for the workflow.
  • --yolo and --auto cannot be combined. --auto switches the chat to Auto, which stays on for every app until someone switches it back; --yolo lasts only for the command.

Use Auto and --yolo where the damage is bounded: a container, a throwaway worktree, a Cloud workspace. Not on the computer that holds your credentials.

Do scheduled and scripted runs ask?

Run Approvals
A schedule Approved automatically, once each, without a review. The agent cannot ask you questions
haarness run --yolo Approved automatically, once each
haarness run --auto Auto's reviewer answers. What it leaves to you is asked at the terminal; with no terminal it is refused and the run goes on
haarness run at a terminal Asked at the terminal; you can also answer from the terminal UI or Desktop
haarness run with no terminal, such as in CI Refused, and the run stops and says which rule would allow it
/loop Asks, like a normal message
A script using the SDKs or the API The chat's mode decides, as for a person. What reaches a person waits until your code answers permission.requested; the SDKs never approve anything on their own. A Cloud workspace's first message can set the chat's mode
A workflow's agents Approved automatically if the turn that started it was, or under --yolo
  • A schedule saves no "always" rule, so what it approved is not allowed in your other chats afterwards. The requests that always ask, and websites in your Chrome (unless you set the site to Always allow for Chrome), are refused at once with the reason, so the agent can tell you and the run goes on.
  • haarness run --auto with no terminal refuses each request that needs a person and tells the agent why. Five refusals in a row stop the run. It exits with status 3 when it refused anything, so a script can tell that from a clean run (0) and a failed one (1).
  • haarness run --yolo saves no "always" rule either: see the table above.

Websites in Desktop's browser

When the agent drives the browser in Desktop, Agent site permissions in Desktop's browser settings decide what it may do. Browsing, downloads and uploads are set separately, to Always allow, Requires approval or Block. By default all three are allowed everywhere.

  • A site rule is an exact origin, such as https://example.com, or https://*.example.com for its subdomains. The wildcard does not cover example.com itself, and the scheme and port must match.
  • An exact rule beats a wildcard; among wildcards, the longest wins. No approval overrides Block.
  • Requires approval asks every time, even under Auto or --yolo, unless you turn on Remember site approvals (off by default). Then the usual "always" answers apply.

Websites in your Chrome

When an agent works in your own Chrome, it uses your accounts, so Chrome has stricter rules than Desktop's browser:

  • Every site asks first. Browsing, downloads, uploads and scripts each default to Requires approval, for every site.
  • Answers are per chat and per site. Allow in this chat covers that site, for that kind of action, in that chat only, until the Haarness server for the project stops. Allow once covers one action. No answer carries over to another chat, and none is written to your settings.
  • Scripts ask on their own. A site allowed for browsing still asks before the agent runs a script there, because a script can read anything on the page.
  • Answer where you are. The terminal UI, Desktop and the Haarness for Chrome popup in Chrome's toolbar all show the request; the popup offers Allow in this chat and Deny. A request that also needs another approval, such as a file outside the project for an upload, is answered in Haarness.
  • Only a person answers. Auto, --yolo (on any command, including haarness serve), hooks, and allow rules in haarness.json, even "*": "allow", never approve a Chrome site. A deny rule still refuses.
  • Not from a shared chat. A chat you share with /rc cannot answer from your phone; answer on the computer running Chrome, or through Remote control › Hosts, which opens that computer itself.
  • Not in unattended runs. In a schedule, and in the helper agents and workflows it starts, the agent's Chrome action fails at once instead of waiting; haarness run with no terminal stops and names the site.
  • Your Chrome settings decide the rest. Chrome site permissions in Settings → Browser can set a site to Always allow or Block. They apply to every project on this computer, and a project's settings can only make them stricter. A site you Block for Desktop's browser is blocked in Chrome too; Always allow there does not carry over.
  • Remember site approvals does not apply to Chrome.

What an allowed site lets the agent do is in Trust boundaries.

Child agents

A child agent cannot do anything its parent is denied. It uses the parent's approvals, follows the parent chat's Review, Auto or Yolo mode, and its "always" answers are shared with the parent. An agent definition can add its own allow rules, so review custom agents you install.

Plan agent

The plan agent cannot edit files, except its own plan files under .haarness/plans/, and cannot use extension tools, generate media, use the browser beyond looking at open pages, or change Cloud workspaces. It can still run shell commands, and those can change files.