Devcontainer auto-execution

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

The question this check asks: does this repository carry a .devcontainer/ whose configuration runs a command on your machine the moment you open the folder — before the container exists, before you have read a line?

Why it matters

A dev container is sold as isolation, and mostly it is. The exception is written into the containers.dev specification: initializeCommand runs on the host, not in the container, because it exists to prepare the host before the container is built. Open a folder, accept Reopen in Container, and whatever that line says has already run as you. The vetting standard's advice is to treat any devcontainer.json as curl | sh on your laptop until you have read it — and to read the mounts too, because a mount of ${localEnv:HOME} hands the container your SSH and cloud keys.

The other lifecycle hooks — onCreateCommand, postCreateCommand, postStartCommand — run inside the container, which is better and still not nothing: a container with your repository, your credentials-bearing mounts and outbound network is a fine place to run a payload.

How RepoGates scores it

C10 finds devcontainer.json in the repository's file list, then reads it — one contents call, no clone — and grades what would run on open. An initializeCommand fires at severity CRITICAL (“runs on the HOST before the container exists”) and costs 15 points. The container-side hooks — onCreateCommand, postCreateCommand, postStartCommand and their siblings — fire HIGH (10 points); a configuration that only builds a Dockerfile is MEDIUM (5); one that names an image and editor settings and nothing else runs nothing on open and is not a finding, just a note on the report. A file that could not be read is HIGH: unknown is a warning, not a pass. Any other .devcontainer/*.json is HIGH without parsing. Until September 2026 every devcontainer was CRITICAL from the file list alone; the Flask repository, whose file runs only an onCreateCommand inside the container, is why it now reads the file.

A critical finding makes the verdict BLOCK-grade, but what happens to the download is the policy's decision, and the two layers disagree here on purpose: the default personal policy sets execution-surface findings to warn, so the download is held, the finding is shown, and proceeding is one click, recorded. The strict organisation profile blocks. The evidence note in the scoring table explains the light weight:

“28.8% of top-100 repos carry devcontainers — informative, not damning”

False positives we know about

More than a quarter of the most popular repositories on GitHub. In our Phase 0 controls the full engine would have hard-blocked 28.8% of the 80 most popular repositories, and every one of those blocks was this class — 32 critical devcontainer hits, plus one MCP configuration that legitimately uses npx -y. That measurement is why the default policy warns: a gate that blocks React is a gate somebody turns off by lunchtime. The finding names the file, so you read it, and the decision is yours.

What this check does not cover

It sees which lifecycle key is present, not what the command does. An initializeCommand that prints a banner and one that fetches and runs a script grade the same; the script itself, and the mounts, are what a deep scan reads. It does not see a devcontainer that lives somewhere other than .devcontainer/ or the repository root, a Dockerfile the configuration builds from, or a feature the configuration pulls in. And it fires at download time: the command itself runs only when you open the folder in an editor that honours the specification and accept the prompt.

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 repository you clone and open in an editor is outside this gate entirely.

Check it yourself

Without installing anything: open .devcontainer/devcontainer.json on GitHub and read four keys — initializeCommand, onCreateCommand, postCreateCommand and mounts. With the GitHub CLI:

gh api repos/OWNER/REPO/contents/.devcontainer/devcontainer.json --jq .content | base64 -d | grep -nE 'initializeCommand|onCreateCommand|postCreateCommand|postStartCommand|mounts|localEnv'

Then, per the standard: read it before Reopen in Container, pin images by digest, and never mount $HOME or the Docker socket. A devcontainer whose initialize step fetches a script from a URL is the pattern to walk away from.

Questions

Why is a devcontainer graded critical but only a warning? Two different layers answer two different questions. The engine grades what the file can do — initializeCommand runs on your host, which is a critical capability. The policy decides what to do about it, and because 28.8% of the 100 most popular repositories ship a devcontainer, the default personal policy warns and shows you the finding rather than blocking a quarter of GitHub. Strict organisation profiles block.

Does the check read what the devcontainer does? No. It sees the file in the repository's file list, in one API call and without a clone. A devcontainer that only picks a base image and one that runs curl piped to sh look identical here, which is another reason the finding warns: it tells you the file exists so you read it before Reopen in Container. A deep scan reads the file itself.

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 repository you clone and open in an editor is outside this gate.

Related checks

C13 Editor and workspace config.vscode/tasks.json runs on workspace trust, the same idea one layer up · C11 Nested bare repository — the one execution-surface finding the default policy blocks · C16 Install scripts — code that runs when you install rather than when you open.

Add RepoGates to Chrome Pricing

Numbers on this page: Phase 0 controls of 2026-08-14 (80 popular repositories; 32 critical devcontainer hits); the containers.dev specification for initializeCommand; weights and actions from the product's scoring and policy tables.