---
title: "OpenCode Denies `git`. It Runs `$(which git)` Just Fine."
pubDatetime: 2026-07-20T20:01:50.000Z
description: "A viral teardown of OpenCode, the 161k-star coding agent, shows its bash permission system parsing commands into a tree-sitter AST and still waving through env git, base64 pipes, and python subprocess. Textual command filtering is security theater, and the harness is the only trust boundary that matters."
tags: [security, ai-agents, coding-agents, sandboxing, opencode, 2026, 2026-q3, 2026-07]
---
> [!tldr] TL;DR
> A teardown titled ["Stop Using OpenCode"](https://wren.wtf/shower-thoughts/stop-using-opencode/) hit the front page of Hacker News on 20 July ([417 points, 288 comments](https://news.ycombinator.com/item?id=48978112)). It walks through OpenCode, the ~161k-star open-source coding agent, and shows its bash permission system defeating itself: a config rule of `"git *": "deny"` blocks `git status` but happily runs `env git status`, `$(which git) status`, and `echo Z2l0IHJlc2V0... | base64 -d | bash`. OpenCode parses your command into a tree-sitter AST to decide what is safe, and the parade of bypasses is total. There is also CVE-2026-22812: a default HTTP server with permissive CORS that let any website you visited POST arbitrary shell commands to your machine. The lesson is old and the author says it plainly: you cannot textually sanitize a shell command into safety. Sandbox the process with OS primitives or accept that `llm | bash` is exactly as dangerous as it sounds.

The whole thing rides on one config rule. The author forbids `git` commands, sensibly, because they had watched a model erase a session's work with `git checkout .`. So they wrote the documented incantation:

```json
"permission": { "bash": { "git *": "deny" } }
```

`git status` is denied. `echo hello && git push --force` is denied. And then the list of things OpenCode runs anyway reads like a CTF writeup: `env git status`, `/usr/bin/git status`, `$(which git) status`, `GIT=git && $GIT status`, an `alias cd=git` trick, a heredoc, and my favorite, base64-decoding `git reset --hard` into a pipe to `bash`. Every one of them slides straight past the filter.

## The pipe is the whole problem

OpenCode's security model tries to reason about what a bash command will do before running it. It parses the command into an abstract syntax tree using tree-sitter's bash grammar, walks the command nodes, and matches them against regexes compiled from your config. This is a lot of engineering to accomplish nothing. The moment a shell can spawn another shell, resolve a binary through `$PATH`, or decode a string at runtime, static inspection of the command text tells you nothing about the effects. The author's verdict is blunt: "Textual command filtering is entirely useless. It is fit for no purpose." That is correct, and it has been correct since long before coding agents existed.

The design assumes a model is a careless collaborator that will not actively route around a wall. Wrong assumption. These systems are trained to compensate for failure with persistence, which makes them adversarial to a guardrail even when nobody is being malicious. As the post puts it, "This is not a guardrail, it's thoughts and prayers."

## Where the file check simply isn't

The file-access controls fail the same way, one layer down. OpenCode keeps a hardcoded `FILES` set of commands it believes might touch the filesystem: `rm`, `cp`, `mv`, `cat`, and a handful more. Paths passed to anything on that list get resolved and validated. Paths passed to anything not on the list are not checked at all. So this prompts you for permission:

```
cat /tmp/logfile
```

And this does not:

```
python3 -c 'import shutil; shutil.rmtree("/")'
```

The permission grants that do exist are sticky in a dangerous way. Answer `Always` to a `python3 -c 'print("hello")'` prompt and OpenCode persists approval for the entire `python3` prefix, on disk, across sessions. The next invocation can be `python3 -c 'print(open("~/.ssh/id_rsa").read())'` and it runs without a peep. There is also a `CWD` set (`cd`, `pushd`, `set-location`, and friends) that bypasses bash permission checks entirely, even under a blanket `{"*": "deny"}`. And because shell redirections live as a sibling of the command node in the AST rather than a child, the path in `echo secrets > /somewhere` never gets validated. It would not matter if it did, since `echo` isn't in the `FILES` set to begin with.

## The part that actually got a CVE number

Everything above is a design failure rather than a disclosed vuln, and the author is careful to say so. [CVE-2026-22812](https://nvd.nist.gov/vuln/detail/CVE-2026-22812) is the exception. OpenCode shipped an HTTP server on by default that had fully permissive CORS, a POST endpoint for arbitrary shell commands, and a GET endpoint for arbitrary file reads. Any website you opened in a browser could knock on the well-known port and get user-level code execution on your box. The maintainers disabled the server by default, kept a CORS exception so that `opencode.ai` could still reach it, and let a [stale bot close the issue](https://github.com/anomalyco/opencode/issues/6355). A [separate report](https://github.com/anomalyco/opencode/issues/10939) that an auth command fetches and executes from a user-supplied URL met the same fate. On top of that, the tool connects to a remote model by default and the upgrade path pipes `https://opencode.ai/install` straight into `bash`.

## "Just use Docker" is not the answer

The standard reply to any of this is "just run it in a container." The teardown refutes that on its own terms: a container that holds everything you care about, with a shell inside it wired to the internet and a frontier model, is not protecting much. If the goal is narrowly "don't let it delete my root filesystem," the OS already ships the right primitives. Landlock on Linux, Seatbelt on macOS, and restricted tokens on Windows scope filesystem and network access at the kernel boundary, where the model cannot argue its way past a regex. For the `git` case specifically, the correct fix is boring: block the `git` executable and make `.git` read-only. Enforce effects, don't grep intentions.

To the author's credit as a critic, they engaged in the [HN thread](https://news.ycombinator.com/item?id=48978112) and posted an update: OpenCode has since disabled tool-call pruning by default, and bash redirect paths are now validated. Good. Neither change touches the core point, which is that the permission model is trying to solve an undecidable problem with string matching.

This is the same argument I keep landing on. The [harness is the trust boundary now](/posts/agent-harnesses-standard), and a coding agent is a machine for handing untrusted text a live shell, which is precisely the [third leg of the lethal trifecta](/posts/ai-agent-security-lethal-trifecta) installed by default. You do not close that gap by parsing bash into a syntax tree. You close it by not giving the process the capability in the first place. Until a coding agent treats OS-level sandboxing as feature zero instead of a footnote, the honest advice is the title of the post that started this: stop using it.

**Sources**

- Wren, ["Stop Using OpenCode"](https://wren.wtf/shower-thoughts/stop-using-opencode/) (annoying and alarming things, referencing OpenCode git `baef5cd4`)
- Hacker News discussion: ["Annoying and alarming things about OpenCode"](https://news.ycombinator.com/item?id=48978112) (417 points, 288 comments, 20 July 2026)
- [CVE-2026-22812](https://nvd.nist.gov/vuln/detail/CVE-2026-22812): default HTTP server with permissive CORS exposing shell and file-read endpoints
- OpenCode issues: [default HTTP server RCE](https://github.com/anomalyco/opencode/issues/6355), [auth command executes remote URL](https://github.com/anomalyco/opencode/issues/10939)

**Related on this blog**

- [Agent Harnesses: A Standard for a Word Nobody Agrees On](/posts/agent-harnesses-standard): why the config and permission layer, not the model, is the trust boundary
- [Three Indirection Steps From a Reverse Shell](/posts/ai-agent-security-lethal-trifecta): the lethal trifecta, and why a shell tool is the leg you cannot leave open
- [The Trap Fired in Seconds. The Humans Took 18 Days.](/posts/ghostty-agents-md-trap): what happens when an agent obediently reads and acts on files it was told to trust