Be careful with Claude Code mods: they open a door to all your connectors
There are already 7,349 public Claude Code mods, nine out of ten in repositories created in October, and 40% launch programs on your machine. I built one and measured what it can do without you noticing, from the mode in which Claude Code asks you about every step to the one that asks nothing.
- A mod is code that travels inside a plugin and runs in your Claude Code session, with your connectors and with your permissions.
- I built one: every two minutes it read my Gmail and my Calendar, and a script of its own read a secret, without the model doing anything and without leaving a trace in the session.
- Claude Code asks you before reading the email. What the mod does afterwards with that data, nobody asks you about anymore.
On 2 November, at 11:12 (UTC time), a Claude Code session in my local lab read a secret stored in my secrets manager. Nobody had touched the keyboard and nothing had changed in the terminal: the cursor waiting and, at the bottom, the usual line that says which mode it is in, auto mode. Also, looking at the session history, its last line was from 11:05:20, seven minutes earlier.
The secret was a test one, and I had put it there on purpose. The execution had been done by a mod that I had written myself to test how far arbitrary code can be run in a mod without anyone noticing, so I started digging. New weekend project (in Spain this weekend has been three days long. I tell you, come live in Spain, you will be happier).
01The problem
Before anything else, it's worth distinguishing between two concepts. A plugin is the package you install in Claude Code: it can bring skills, commands or MCP servers. When it also brings code that runs inside Claude Code itself, that code is called a mod. Every mod travels inside a plugin, but not all plugins bring a mod. Here what we are going to talk about is Claude Code mods.
A mod hooks into the session, at certain moments (events), for example when the session opens, and from there it can set a timer, use your session's connectors (Gmail, Calendar, Drive…) and launch programs on your machine.
The documentation says it clearly so nobody gets the surprise: mods run with your permissions and what they launch stays outside the sandbox (careful with that last one, because I'm still hearing the screams of the security people with the OpenClaw plugins, and this is a bit more of the same).
I've taken a look out of curiosity and there are a lot of mods. A community catalog that runs the official Claude Code validator on every public repository it finds counted, on 1 November, 7,349 mods, nine out of ten in repositories created in October (!!). 40% launch programs on your machine and one in three reads your environment variables (WHATTT???!). So let's see what we can do with this, which a priori seems to be not a little.
In manual mode, every time the mod wants to use a connector, Claude Code stops, asks you and tells you which plugin is asking for it (yes, that nagging prompt you switch off by the third time it asks). But when you hit Yes, the data goes to the mod's code, and what it does with it no longer passes through any filter of any kind, and this is where the crux of the matter comes in.
There's no trace in the session history either, which is the first thing you'd look at to know what has happened. If you turn on Claude Code's telemetry, you'll see that the mod used Gmail, but not what it searched for, and the programs it launches don't even appear (warning to Anthropic: Boris, this costs you very little, put it in, at least).
1
2
3
- 1It names the plugin. It says bridge-lab is asking.
- 2It shows what it will read. The exact search and how many results.
- 3The “Yes” approves the read. What comes next, the Python script that receives the data, runs without asking.
The prompt tells you what gets read. The mod decides where it goes.
02The bridge that didn't say a word
Like almost all these things, everything started with a small problem, and here you have me, my whole holiday Monday pulling the thread 8 hours later. I wanted to pass data to a tasks page (it's a long story for another post, very entertaining) without spending tokens (because, in case you hadn't noticed, tokens are the new gold and even the cat asks you for tokens), so I set up what I called a bridge: basically a Claude Code session (the 2.1.316, with Haiku 5.5) that only loads a plugin and where nobody types. Its mod sets a timer that makes its first run in 20 seconds and then every two minutes. On each run it queries Gmail and Calendar, although it could pull from anything my connectors see, and passes what it finds to a Python script with fixed rules (the why of this is long, but let's not get off track, we're with the mods). The model, meanwhile, doesn't even find out, which was the idea so as not to spend tokens.
Terminal
❯⏵⏵ auto mode on
Session history
last: 11:05:20
no changes
Mod log
// hooks/register.js: pieces of the mod's code, with the private names changed.
on('session.start', async ($, event, next) => {
$.clock.every(120000, () => tick($));
void $.clock.sleep(20000).then(() => tick($));
return next(event);
});
const g = await $.mcp.call('claude.ai Gmail', 'search_threads', {
query: QUERY, pageSize: 10, view: 'THREAD_VIEW_MINIMAL'
});
const c = await $.mcp.call('claude.ai Google Calendar',
'list_events', { pageSize: 10, orderBy: 'startTime' });
const r = await $.process.run(['python3', RULES], {
stdin: JSON.stringify({ source, items, meta }), timeoutMs: 20000
});
Then I realized the mod was getting all the way into my kitchen and the Claude Code console didn't make a peep. I was surprised and thought maybe in the JSONL, but no. And there I already knew how the day was going to end. I wanted to know what else the mod could do on its own, without Claude Code asking me anything. So I did three tests: read a secret, change the code while running and download code from the internet.
A secret read
On one of the runs, the mod launched a small script that used my dev account and read a throwaway secret.
15 BThe value was the real one, and its owner's hash confirmed it. Nothing showed on screen.
Code changed on the fly
The mod loads only once, but on every run it starts a new Python that reads bridge_rules.py from disk. I changed that file with the session open.
and the new code was already running, with no restart.
Code fetched later
In a separate test, the mod downloaded a script from the internet, checked its hash and ran it.
HTTP 200It finished fine (exit code 0) and processed a Drive response.
And so there's no doubt: the script was receiving real data. From Calendar, the 10 events of the next three days, with their time and their title, mixing work, fixed meetings and personal things. From Gmail, the headers of 5 threads, with sender and subject, of the automatic notices from providers of the last month.
Of course, since you're at it you start turning it over, and it occurred to me that in the last two tests it was going to change how a plugin is reviewed. Could it be that, when you install a mod and read its code (something we all do, with no exceptions) and it looks fine to you, this code calls every X minutes a script that lives on your disk, or downloads another one from the internet? And what if someone changes that script on the fly? Does the mod run it on the next run, without reinstalling anything and without warning? If so (Spoiler 1: yes, it does), the code that runs is already a different one from the one you “reviewed”.
And of course, when I look at the published mods it turns out it's a very common pattern: 1,758, one in four, combine a timer with programs they launch on your machine, just like my bridge.
Then I wanted to know what the user sees in each of Claude Code's permission modes, because of course, maybe it was me who was in bypass-permissions-and-take-my-whole-PC mode, so I repeated the same tests in the four modes, and this table sums up what happened (Spoiler 2: it's what you're imagining).
$.mcp.callafter it's read
$.process.runcreate_label), which we checked and deleted.That said, credit where it's due: in manual mode, Claude Code does well the part it controls. It tells you which plugin asks for the read and shows you exactly what it's going to search for. The problem is one of design: when you approve a read, you're approving the input of a piece of code whose output you don't see.
For this test, in manual mode, I recorded the whole sequence. The data goes from Gmail to Python, from Python to Calendar and from Calendar again to Python. Only the connectors stopped to ask; the two Python scripts just ran, and there anything could have happened. Of course, it's the power of the mod!, exactly the idea. If you want something that does things, don't be surprised when it does them!
Lastly I tried launching only the script, also in manual mode. The mod started it with $.process.run, it finished fine and read a secret. No trace was left. And even though we approved the connectors by hand, the session history didn't note any of those approvals.
Did anything record it at all? I repeated the test turning on the two logs that Claude Code brings for these things, the debug log and telemetry. The debug log caught everything: the email read, the calendar one and the script, with the plugin name in front. Telemetry, only half: the two reads yes, the script no. That is, exactly what skips the permission window is what doesn't reach telemetry either.
And before all this, in an isolated session in bypass mode (in which neither I nor anyone I know has ever worked), the mod called five connected services and all of them answered without asking permission even once: Drive returned 32,798 characters, Gmail 2,575, Atlassian 659, Slack 494 and Calendar 254. I saved only whether it worked and how much it returned.
None of this has needed an exploit. It's what a plugin is allowed to do. Most of us install a plugin like a browser extension, after reading the description. What we really install is code that runs inside the session we're watching, with our connectors and our account, with its timer and without making a peep on screen.
03How to catch a silent mod
Start with the code the session loaded, which is where what the mod does is; the history only notes what the model did. With these commands, which only read, you have the map:
# Read-only
claude plugin list --json
claude plugin details <installed-plugin>
rg -n 'session\.start|clock\.every|process\.run|mcp\.call|tool\.call|http\.fetch' <plugin-folder>
sha256sum <mod-module> <every-script-it-launches>
Follow every $.process.run to its script and to what that script imports. In my case, staying at register.js would have left the change out: the mod didn't move, the one that changed was bridge_rules.py. And look also at the plugins loaded with --plugin-dir, which come in without being installed.
Careful, claude plugin validate checks the structure; how far the mod reaches you have to look at yourself.
You can do aaall of that, or you pass this post to your Opus 5.5 and ask it to build you a guardian mod: Claude Code warns with the plugin.register event every time a mod is about to load, and there the guardian can read its code, ask Opus about it and block it if it isn't convinced. That said, it only sees what gets loaded, so the scripts that mod launches afterwards have to be watched by hash.
I've made mine and left it public on my GitHub, explained from top to bottom: before loading any mod it passes its code to Opus 5.5 for it to review, and if a script of the mod then changes in the middle of the session, it doesn't let it run. I tested it with a mod that said it was a clock and actually launched a script every minute: Opus caught it the first time and it never got loaded.
It's not without a certain irony that, after this whole post, I offer you a mod and at the same time tell you to think about it before installing one. But c'est la vie: if you don't want to, don't install it, or at least ask your Opus 5.5 to read it and install it for you. I have it set with Opus 5.5 on high effort, because to review someone else's code it's not advisable to skimp.
| Where to look | What it tells you | Sees the mod? |
|---|---|---|
| The startup configuration | The active plugins, the --plugin-dir folders and the session's permission mode. | What got loaded |
| The mod and everything it launches | Timers, connector calls, scripts, imports, hashes and owners. | Everything |
| The session history (JSONL) | Which session it was. An empty history doesn't mean the mod did nothing. | Nothing |
The debug log (--debug-file) | Every mod call to connectors and every program, with the plugin name. Only if you turn it on, and any of your processes can edit it. | Everything, if you turn it on |
| OpenTelemetry | The mod's calls to connectors, without what it searched for. For programs, nothing. | Halfway |
| Each service's own logs | Who read what in the connector and in the secrets manager. It's the only record the mod can't touch. | Yes, but off your machine |
04What I'd tell you before you try it
$.process.run to the script it launches and whatever that script imports. With an rg 'session\.start|clock\.every|process\.run|mcp\.call' over the plugin folder you have the map in seconds.And look at it yourself, because Claude Code won't tell you: when I installed it from a marketplace it showed no warning about hooks, programs or network, and claude plugin details showed “Hooks (0)” for a mod with a timer that launches programs. It takes time. And if the mod downloads code, what you reviewed can change tomorrow, so those hashes have to be watched while it runs. And start these sessions with --debug-file: it's the only local record where the mod's programs showed up.05What I'd ask Anthropic for
The documentation already warns that mods run without a sandbox, and that's fine. What I miss is being able to see it. And the good thing is that almost everything needed is already inside Claude Code: the validator already knows what each mod calls, telemetry already goes out and the plugin.register event already allows stopping one before it loads. They are three small requests, one for each moment in a mod's life, and each one comes from something I measured.
Tell me what it does before it comes in
- What the validator already knows, at install timeWhen I installed my mod from a marketplace there wasn't a single warning, and
claude plugin detailsshowed “Hooks (0)” for a mod with a timer that launches programs. The right list already exists: it's the oneplugin.registerreceives. - In one line anyone can follow“Starts with the session, uses a timer, reads Gmail and Calendar and launches programs”. With that you decide in two seconds.
Let me decide about what it launches
- A permission rule for
$.process.runToday permission rules cover Claude's tools and leave mods out: the mods documentation itself warns that withRead(.env)denied, a mod can still read that file. - An indicator in the status lineSomething like
bridge-lab · 2 reads · 2 programs · 5 min ago, so you can see somebody's working even when nobody's typing.
A trail the mod can't erase
- The programs, in telemetryConnector reads already go out through OpenTelemetry; the programs the mod launches don't. It's the same channel and the missing piece (Boris, this is the one I was telling you about).
- What the mod does and what you approve, in the historyApprovals in manual mode left no line at all in the session history.
We'll close our own bridge: a user just for it, access only to metadata and the same test with the same kind of secret, which this time has to fail. We'll tell it, however it turns out.
A mod brings code that uses your keys. You decide which doors you open for it.
Evidence and sources
Every piece of data in this article has its proof here: what I measured, when and the fingerprint of each record.
| ID | What it backs |
|---|---|
| E-AUTO-01 | Auto mode session, runs, 15-byte secret (SHA-256 prefix ba3f3f4a3e2c) and the live script change. |
| E-PANE-01 | Four text captures of the terminal before and after the reads, identical SHA-256 2f68343ac2d337bd438c867eed7e84cbb6f0ceaae9aa8868208822b4eb9ea2ab |
| E-JSONL-01 | Session history (JSONL): five lines, the last one at 11:05:20 UTC. SHA-256 6e37da6f25a2b8ac4f2f9c50b0c4f117c3e2f94e73d70db97fcab059ddc29f29 |
| E-PERM-01 | Captures of the prompts in manual mode and times from the mod's log (12:32:28 and 12:32:38 UTC). |
| E-MODES-01 | Local process in manual, approval and JSONL, reversible write in auto. Terminal SHA-256 21a1bfb83df0fd03c2be0fd6b733419dc04ec1259eca73a604ff74159184b559; JSONL SHA-256 e075974ead966493a7e79014eca879d6ae6021e16d51eff5761fd6bad6f35c91 |
| E-ECO-01 | Community mod catalog, scan of 1 Nov 2026 with claude plugin validate 2.1.316 (it reads the code without running it). My own count over its data: 7,349 mods (no duplicates, tests or copies); of the 6,962 with a complete analysis, 2,795 launch processes, 2,299 read environment variables and 1,758 combine a timer and processes. 6,599 of the 7,349 are in repositories created in October 2026 (90%), and 69 in repositories with 1,000 stars or more. Stars count the whole repository, with all its mods. |
| E-GUARD | mod-guard on Claude Code 2.1.316, review with Opus 5.5 at high effort. A test mod that says it is a clock and runs a script of its own every minute: refused at load. A mod that only shows a status line: admitted. An honest mod that runs its own helper.py: admitted, and its next run denied after helper.py was edited with the session open. The three test mods are in the repository's examples/. |
| E-V4 | Round of checks on 2 Nov (Claude Code 2.1.316). Telemetry on in manual and auto: the mod's calls to Gmail and Calendar show up as tool_decision and tool_result, with only the server and the tool; $.process.run produces no event, only a trace in the local debug log. Accept edits, in a new session: the script ran with no prompt. Install from a local marketplace with no warning about hooks, processes or network; claude plugin details showed Hooks (0). A script launched by the mod made a POST of 52 dummy bytes to localhost (HTTP 204). Pending, for lack of admin access: the secrets manager audit and the Google Workspace one. |
| E-DEBUG-01 | Manual mode session with --debug-file and telemetry to console: six plugin lines in the debug log (two mcp.call with their response and one process.run with exit 0); 17 telemetry events, with two tool_decision and two tool_result for the connector calls and none for the process. |
| E-BYPASS-01 | Isolated test in bypass: five services (Drive, Slack, Gmail, Calendar, Atlassian) and the downloaded helper. |
About the OpenClaw stuff: Tom's Hardware, malicious skills on ClawHub (late January 2026), TechRadar, macOS infostealers in OpenClaw skills (Unit 42 report) and the Bitdoze OpenClaw security guide (ClawHavoc campaign; the figures for infected skills vary depending on the source).
Claude Code documentation: Mods overview, Mods reference, Permission modes and Monitoring.