# Safety Three layers, in increasing order of how much they actually protect you. **Path jail.** Every path a tool is given is resolved with `filepath.EvalSymlinks` and refused if it lands outside the working root, including through a symlink that points out of the tree. Cheap, and it catches a lot. **Approval prompts.** Read-only tools run unattended. `bash`, `edit_file`, `write_file`, `web_search` and `web_fetch` ask, with "yes / no / always this exact one / always this tool" and session memory. `/allowed` lists what you have agreed to. `--allowedTools` and `--disallowedTools` write the same decisions down ahead of time, in Claude Code's rule syntax — see [using it in place of `claude`](in-place-of-claude.md) — and a deny rule there is checked before everything else, `-yolo` included. Shell commands are parsed with `mvdan.cc/sh/syntax` so the prompt can tell you what a command will actually run — `git status; rm -rf ~` is shown as running both `git` and `rm`. **This is not a security boundary**, and Codey does not pretend otherwise: a command containing `$(...)`, backticks, or an interpreter with `-c` cannot be analysed at all, so it is marked opaque and "always allow this exact command" is refused for it. String-matching allowlists for shell commands do not work; this exists to make the prompt specific, not to make the decision for you. [Claude Code's permission rules](claude-code-settings.md) sit in front of all of that. Its deny rules hold in every mode, `-yolo` included, which makes them the one part of this layer you can lean on: a call they refuse does not run. Its allow rules are matched with the same parser, one simple command at a time, and an opaque command matches none of them — but the warning above applies to them with full force. `Bash(find:*)` allows `find . -exec rm {} +`, and no parser will tell you otherwise. Hooks sit outside all three layers, deliberately: a command in `"hooks"` runs on every event it is attached to, without a prompt, because you put it in your own config rather than the agent asking for it. The path jail is what keeps the agent from adding one — the config lives in `~/.config/codey/`, outside any working root you would sensibly point Codey at — but the flip side is that anything able to write that file has arranged to run commands here. `-no-hooks` is the switch that says no for one run. MCP servers are the same bargain: each is a program started with your environment when the session opens, and only its tool calls go through the prompts. `-no-mcp` is theirs. The one kind of server that does not come from a file you wrote — a repository's `.mcp.json` — waits for you to [trust it](mcp.md#trusting-a-project) first. **A sandbox.** This is the layer that actually contains a mistake, and the Codey cannot provide it for you. The honest recipe on Linux: ```bash podman run --rm -it \ -v "$PWD":/work:Z -w /work \ -e ANTHROPIC_API_KEY \ docker.io/library/golang:1.26 \ /work/codey -C /work -yolo ``` **Decide about egress explicitly.** The container above has network access, because Codey needs to reach the API — which means the agent's `bash` has it too. That is the line between "broke my repo" and "read my `.env` and made an HTTP request". If that matters for the tree you are pointing it at: - Treat every secret reachable from inside the container as exposed. - Use a token scoped to nothing else, and a clone rather than your working tree. - For real isolation, run Codey on the host and put only `bash` in a network-less namespace. That is not implemented here. `-yolo` skips every prompt. It is correct inside a container or against a throwaway clone, and wrong everywhere else. The eval runner sets it, because it is headless and operates on a temp directory it created.