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 — 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 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. Only the servers
in Codey's own config are started; a repository's .mcp.json is never read.
A sandbox. This is the layer that actually contains a mistake, and the Codey cannot provide it for you. The honest recipe on Linux:
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:
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.