> ## Documentation index
> Fetch the complete documentation index at: https://haarness.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# 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](https://haarness.ai/docs/security/trust-boundaries.md)
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](https://haarness.ai/docs/security/permissions.md#which-requests-always-ask);
- 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](https://haarness.ai/docs/desktop/use-your-chrome.md) (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](https://haarness.ai/docs/security/permissions.md#write-your-own-rules).

## 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](https://haarness.ai/docs/cloud/remote-hosts.md). **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](https://haarness.ai/docs/security/permissions.md#which-requests-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`:

```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](https://haarness.ai/docs/security/permissions.md#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:

```sh
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`:

```sh
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.

```json
{
  "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](https://haarness.ai/docs/cli/agents-commands-skills.md).

## 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?](https://haarness.ai/docs/security/trust-boundaries.md#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.

```json
{
  "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](https://haarness.ai/docs/security/permissions.md#write-your-own-rules) 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](https://haarness.ai/docs/security/permissions.md#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](https://haarness.ai/docs/cloud/workspaces.md). Not on the computer that holds your credentials.

## Do scheduled and scripted runs ask?

| Run | Approvals |
| --- | --- |
| A [schedule](https://haarness.ai/docs/cli/schedules.md) | 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](https://haarness.ai/docs/server/sdks.md) 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](https://haarness.ai/docs/cloud/cli-and-sdk.md#2-start-a-workspace-with-a-task) 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](https://haarness.ai/docs/security/permissions.md#which-requests-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](https://haarness.ai/docs/security/permissions.md#approve-everything-yolo).

## 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](https://haarness.ai/docs/desktop/use-your-chrome.md), 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`](https://haarness.ai/docs/cloud/remote-hosts.md#continue-a-chat-on-your-phone) cannot answer from your phone;
  answer on the computer running Chrome, or through
  [Remote control › Hosts](https://haarness.ai/docs/cloud/remote-hosts.md#connect-your-computer), 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](https://haarness.ai/docs/security/trust-boundaries.md#what-can-an-agent-do-in-my-chrome).

## 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.

## Related

- [Trust boundaries](https://haarness.ai/docs/security/trust-boundaries.md) — what is and is not contained
- [Execution budgets](https://haarness.ai/docs/security/execution-budgets.md) — time limits and how much runs at once
- [Data and storage](https://haarness.ai/docs/security/data-and-storage.md) — what leaves your computer
