Nested bare repository

Check C11 · execution surface family · severity CRITICAL · automatic · one of the 22 checks

The question this check asks: is a bare git repository — a directory carrying its own config and HEAD — committed somewhere inside this project's tree?

Why it matters

The repository vetting standard calls this the most under-appreciated vector on its list, and the mechanism explains why. Git refuses to commit a directory named .git, and that is the whole of its defence: a bare repository — the same files without the working tree — commits fine as vendor/thing.git/. When you later run git status inside that directory, or any tool does on your behalf, git walks up, finds it, and honours its config — including core.fsmonitor, which names a command to run. No hook to install, no prompt to accept. The class was published in 2022 and was live in 2026: Zed (CVE-2026-44465), the GitHub Copilot CLI (CVE-2026-45033), and an unpatched path in Emacs.

How RepoGates scores it

C11 reads the repository's file list — one API call, no clone — and fires when any path matches *.git/config or *.git/HEAD below the root. The finding is CRITICAL, costs 40 points, and reads “a bare repo is committed inside the tree — its config can run commands via core.fsmonitor (CVE-2026-45033 class)”. A repository that trips nothing else scores 60 and reads amber, the verdict is BLOCK, and the default personal policy blocks — the download is cancelled before it reaches your downloads folder, overridable unless the repository is also campaign-listed. The strict organisation profile locks this action so no policy edit can lower it. The evidence note in the scoring table:

“severe RCE class, 1/150 FP in controls”

False positives we know about

One, in 150 control repositories: a project that vendors another repository verbatim, bare directory and all — the shape of git tooling's own test fixtures. That rate, against silent execution on an ordinary git command, is the profile that earns a block. The board names the path, so a fixture you recognise is an allowlist entry and a vendor/x.git you do not is the finding doing its job.

What this check does not cover

It matches path names. A bare repository committed under a directory whose name does not end in .git is not matched, though git would still find it; the standard's global fix below covers that case and this check does not. It does not read the nested config, so it cannot say whether core.fsmonitor is set — a nested bare repository with a harmless config fires the same finding, which is the price of a check that needs no clone. And it fires at download time: the command runs only when a git operation is executed inside the extracted tree.

And RepoGates as a whole gates browser downloads only — it does not see git clone, package managers, curl, or fetches made by AI agents outside the browser. A ZIP carries the nested repository's files exactly as a clone would, so what this check protects is what you extract.

Check it yourself

Without installing anything: on GitHub, search the repository's file list for a directory ending in .git. With the GitHub CLI:

gh api repos/OWNER/REPO/git/trees/HEAD?recursive=1 --jq '.tree[].path' | grep -E '\.git/(config|HEAD)$'

Then remove the class from your machine entirely — the standard's "fix once, globally", shipped in git 2.38 and still not the default:

git config --global safe.bareRepository explicit

Questions

Why does this one block when devcontainers only warn? Two measurements. A devcontainer sits in 28.8% of the most popular repositories; a bare repository committed inside a tree appeared in 1 of 150 control repositories. And the consequence differs: a devcontainer runs when you accept a prompt, a nested bare repository runs on git status with no prompt at all. Near-zero false positives plus silent execution is the profile of a block, and the strict organisation profile locks it so policy cannot lower it.

Is there a fix that does not need RepoGates? Yes, one line: git config --global safe.bareRepository explicit. It shipped in git 2.38 and is still not the default. With it set, git refuses to treat a bare repository it stumbles into as one. Do it today; the check is for the machines that have not.

Does RepoGates stop git clone? No. RepoGates gates browser-initiated downloads — Download ZIP and release-asset archives. It does not see git clone, package managers, curl, or fetches made by AI agents outside the browser. A ZIP carries the nested repository's files exactly as a clone would, so the finding applies to what you extract.

Related checks

C12 Submodule definitions — the other way git itself runs code, during clone · C10 Devcontainer auto-execution — the execution-surface finding that warns instead · C19 Hidden Unicode — the other check the default policy blocks on.

Add RepoGates to Chrome Pricing

Numbers on this page: CVE-2026-44465 and CVE-2026-45033 as published; the 2022 advisory on buried bare repositories; Phase 0 controls of 2026-08-14 (150 repositories, one hit); weights and actions from the product's scoring and policy tables.