Check G10 · execution surface family · severity CRITICAL · automatic · one of the 22 GitLab checks
The question this check asks: does this project carry a .devcontainer/ whose configuration runs a command on your machine the moment you open the folder — or a GitLab Workspaces devfile whose postStart commands run on your organisation's own Kubernetes the moment a workspace is created from it?
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. GitLab's own tooling does not read this file — the Workspaces documentation never mentions it — but VS Code and the editors that follow the specification do, and a gitlab.com project is opened in those as readily as any other.
GitLab has its own version of the same idea. Workspaces (Premium and Ultimate) build a development environment from a devfile at .devfile.yaml, .devfile.yml or .devfile/<name>.yaml, and GitLab's documentation is specific about what that file may do: only exec and apply command types are supported, only preStart and postStart events, and "You can define custom postStart events in your devfile to run commands after the workspace starts." Those commands run on a Kubernetes cluster your organisation operates — any linux/amd64 cluster with the GitLab agent for Kubernetes — with the rights of the member who created the workspace, who needs Developer or above on both the workspace project and the agent's. GitLab's own warning is one sentence: "Create a workspace only from trusted projects."
G10 is the same check as C10, run by the same code on a file list gitlab.com answers in one call, plus one detector that belongs to this roster alone.
The devcontainer. RepoGates finds devcontainer.json in the file list, reads it through the repository files API — one request, 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 its own image is MEDIUM (5); one that names an image and editor settings and nothing else runs nothing on open and is a note on the report, not a finding. A file that could not be read is HIGH: unknown is a warning, not a pass. Any other .devcontainer/*.json is HIGH without parsing.
The devfile. A path matching .devfile.yaml, .devfile.yml or .devfile/*.yaml is an autoexec row at HIGH: “GitLab Workspaces run this devfile's postStart commands when a workspace is created from the project (not parsed)”. It is not parsed — the row says the file is there and what GitLab will do with it, and you read it.
Not a finding, on purpose: .gitlab-ci.yml. A pipeline runs on push and on a merge request, never when someone clones or opens the project. The exposure GitLab documents for it — a fork's merge request carrying code that tries to steal the parent project's secrets when the pipeline runs — is the maintainer's, not the downloader's, and a check on what happens to you when you fetch the project has no business flagging it.
A critical finding makes the verdict BLOCK-grade, but what happens next 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 finding is shown and proceeding is one click, recorded. The strict organisation profile blocks. Measured on 19 September 2026 across the 100 most-starred gitlab.com projects, a devcontainer appeared in 1 and a Workspaces devfile in 3 — gitlab-com/www-gitlab-com, gitlab-org/gitlab-development-kit and gitlab-org/gitlab-vscode-extension, all GitLab's own; GitHub's 100 most-starred, read the same day by the same engine, carried a devcontainer 16 times.
On GitHub, more than a quarter of the most popular repositories: the Phase 0 controls put a devcontainer in 28.8% of the 80 most popular, and that measurement is why the default policy warns rather than blocking a quarter of GitHub. On gitlab.com the class is rare — 4 of the top 100 — and the three devfiles belong to GitLab's own projects, which build their own workspaces and would be strange without one. A devfile whose postStart installs dependencies is the ordinary use of the feature; the finding names the path so that you read it, and the decision is yours.
It sees which lifecycle key is present in a devcontainer, not what the command does; and it does not parse the devfile at all. An initializeCommand that prints a banner and one that fetches and runs a script grade the same, as do a postStart that installs packages and one that reads a secret; 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 project root, the image either file builds from, or GitLab's default devfile used when the project has none. And it fires at verdict time: the devcontainer runs only when you open the folder in an editor that honours the specification and accept the prompt, the devfile only when a member of a Premium or Ultimate group creates a workspace from the project.
RepoGates runs the same 22 checks on a gitlab.com project that it runs on a GitHub repository, from gitlab.com's public API, under the key gl/{namespace…}/{project}. What that API withholds from an anonymous caller — fork status, a user namespace's account age, and OpenSSF Scorecard, which does not cover gitlab.com — is listed as not checked, never assumed. A self-managed GitLab is another hostname and is not assessed. The repository's code is not read on this tier: a PASS means the provenance and the declared execution surface are clean.
Without installing anything: open .devcontainer/devcontainer.json and any .devfile.yaml on gitlab.com and read the keys named above. From a terminal, with every / in the project path, and in a file path, written as %2F:
curl -s "https://gitlab.com/api/v4/projects/NAMESPACE%2FPROJECT/repository/tree?recursive=true&per_page=10000" | jq -r '.[].path' | grep -E '^\.devcontainer/|^\.devfile\.ya?ml$|^\.devfile/'
curl -s "https://gitlab.com/api/v4/projects/NAMESPACE%2FPROJECT/repository/files/.devcontainer%2Fdevcontainer.json/raw?ref=BRANCH" | grep -nE 'initializeCommand|onCreateCommand|postCreateCommand|postStartCommand|mounts|localEnv'
curl -s "https://gitlab.com/api/v4/projects/NAMESPACE%2FPROJECT/repository/files/.devfile.yaml/raw?ref=BRANCH" | grep -nE 'preStart|postStart|exec|apply'
Then, per the standard: read the devcontainer before Reopen in Container, pin images by digest, and never mount $HOME or the container engine's socket; and take GitLab at its word about workspaces — only from projects you trust, having read the devfile. A devcontainer whose initializeCommand fetches a script from a URL is the pattern to walk away from.
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 the same file sits in 28.8% of the 100 most popular GitHub repositories, the default personal policy warns and shows you the finding rather than blocking a quarter of GitHub. The rule is the same on gitlab.com, where the class is rarer: 4 of the 100 most-starred projects. Strict organisation profiles block.
What does the devfile row actually read? The path, and nothing inside it. A file at .devfile.yaml, .devfile.yml or under .devfile/ is a HIGH row saying that GitLab Workspaces will run its postStart commands when a workspace is created from the project; the commands themselves are not parsed. GitLab's own documentation limits the file to exec and apply commands on preStart and postStart events and warns to create a workspace only from trusted projects — the row is that warning, delivered before you fetch the project. A deep scan reads the file.
Does RepoGates stop git clone of a gitlab.com project? No. RepoGates does not see git clone, glab, package managers or curl run from a terminal, and the extension's board on gitlab.com pages is a later release. What reads a gitlab.com project today is the verdict API, the MCP server and the RepoGates plugin's hook on Claude Code's Bash tool, which sends a git clone https://gitlab.com/… or glab repo clone command line to preflight before it runs and answers on this check's dimension — a refusal or a question for that one tool call, not a wall around git.
C10 Devcontainer auto-execution — the GitHub original · G13 Editor and workspace config — .vscode/tasks.json runs on workspace trust, the same idea one layer up · G11 Nested bare repository — the one execution-surface finding the default policy blocks · G16 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 14 August 2026 (80 popular GitHub repositories); the containers.dev specification for initializeCommand; GitLab's Workspaces documentation as read on 18 September 2026; the 100 most-starred gitlab.com projects and GitHub's 100 as the control, read on 19 September 2026; weights and actions from the product's scoring and policy tables.