- Book: AI That Acts
- The series: AI in TypeScript — 5 books, from your first LLM call to agents in production — all five here
- My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools
- Me: xgabriel.com | GitHub
A Claude Code plugin can now carry a mod: JavaScript or TypeScript that Claude Code runs inside its own process. Mods arrived in the terminal with v2.1.287 and are on by default. Anthropic's mods overview lists what one can reach once it loads. It can read and write files anywhere your user account can, start programs and make network requests. It can read environment variables and settings files, "including an API key you keep in either". It sees every prompt you send and every tool call Claude makes. It can "approve a tool call before you're asked."
The same page says it plainly: "Mods aren't sandboxed." If you turn on sandboxing, the sandbox isolates the Bash commands Claude runs, "and a process that a mod starts runs outside it."
So /plugin install some-mod@some-marketplace now means what npm install means: a stranger's code runs on your machine, as you. My take is that it deserves the review you give a new dependency.
What a mod can approve
Claude Code's permissions page spells out the order. A mod that handles the tool.check event answers after your permission rules and your PreToolUse hooks have decided, "and its answer can replace theirs":
-
An
askrule: the mod can approve a call that the rule would prompt you for. -
A
PreToolUsehook that blocked the call: the mod can approve it anyway, unless that hook is in managed settings. - Auto mode: a call the mod approves runs without a classifier check.
-
A
denyrule: on a machine with managed settings, or when you're signed in with a Team or Enterprise plan, deny rules hold over the mod by default. "Anywhere else, the mod can approve a call that a deny rule refuses."
Read that last line twice if you use Claude Code on a personal plan or an API key with no managed settings. What keeps deny rules final is a built-in guard named sec-default, and the admin docs say it loads only on a machine with managed settings or for a Team or Enterprise sign-in. Even where it loads, it covers Claude's tool calls and not the mod's own: "with Read(.env) denied, a mod can still read that file with $.fs.read or start a program that does."
Give the design its due, too. A mod can restyle much of the interface, but not the permission prompt, so "it can't change what a prompt shows you." In a directory you haven't trusted yet, no mod loads until you answer the trust prompt. And a tool that needs you, such as AskUserQuestion, still prompts after a mod approves it, from v2.1.292 on (permissions page).
One trap from early access. If you put CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=0 in a dotfile to keep mods off, it does nothing now. The overview says v2.1.287 and later ignore it, "so setting it to 0 doesn't keep mods off."
Plugins already ran code. Mods moved it inside
A plugin was never only instructions. The plugin security page opens with "A Claude Code plugin you install can execute arbitrary code on your machine with your user privileges." Settings hooks run as shell commands, a stdio MCP server runs as a process Claude Code starts, and a plugin's bin/ directory lands on the Bash tool's PATH. If you have been installing plugins without reading them, you were already trusting their authors with your account.
What mods change is where that code sits. A shell hook runs at one point in the lifecycle and returns. An MCP server waits for Claude to call one of its tools. A mod is loaded into Claude Code itself. It sees each prompt and tool call as it happens and can rewrite either, can submit a prompt "as if you had typed it", can call a model on your plan, and its answer on a permission check can override your rules and hooks, unless managed settings say otherwise.
That is why npm install is the right comparison. npm runs a package's preinstall, install and postinstall scripts when you install it, which is why looking at a package before you add it is ordinary hygiene by now. A mod is closer to an install script that stays loaded for the whole session, inside the program that holds your approvals.

The sandbox covers Claude's Bash commands. The mod sits inside, next to the stamp.
Mods do have one thing npm install scripts don't. A hook can only reach files, processes and the network through the mods API, and Claude Code "refuses to load a mod that uses the mods API in a way this command can't read" (admin docs). You can list what a mod calls without running it. Use that.
Review a mod like a new dependency
Get the source, then validate it before you install anything:
git clone "$MOD_REPO" some-mod
claude plugin validate ./some-mod
claude plugin validate reads the manifest and the hooks module without running them (create docs). Two lines in its output matter: hooks: lists the events the mod handles, and calls: lists the mods API methods its code calls. The admin docs say what each one means. The short version:
-
tool.callorprompt.submitin the hooks line: it sees every tool call or prompt, and can change them. -
tool.checkin the hooks line: it can approve or deny a tool call before a permission prompt appears. -
$.process.runor$.process.spawn: it starts programs as you, outside the sandbox. -
$.env.getor$.settings.read: it reads values that can hold API keys. Anenv reads:line names each variable. -
$.http.fetch: it makes network requests. -
$.model.complete: it spends your plan or your API key.
Match the list against what the mod claims to do. A mod that draws a chart of your context window has no reason to hook tool.check. And once calls: names $.process.run, the rest of the list stops telling you much, because the program it starts can do anything you can.
Then watch what changes after you've read it. The install docs say auto-update is on by default for most of Anthropic's official marketplaces and for ones added from claude.ai. It is off for the community marketplace and for third-party ones. The security page warns that an update means "the files you reviewed can change on disk." Leave auto-update off for third-party marketplaces, and pin one to a tag when you add it, as in the docs' your-org/plugins#v1.2.0. That keeps the marketplace at the version you read, so for a plugin stored in that repo, the code you read is the code that runs.
The off switches, and what each one costs
The overview gives three, from narrow to wide:
-
One mod: disable or uninstall its plugin from the Installed tab in
/plugin, or runclaude plugin disable <name>@<marketplace>in your shell. -
Every installed mod, for one session: start with
claude --safe-mode. It's wide on purpose. The CLI reference lists CLAUDE.md, skills, plugins, hooks, MCP servers, the status line and auto memory among what doesn't load. Good for checking whether a mod is behind something odd, not for daily work. -
Every installed mod, in every session: set
disableAllHooksin~/.claude/settings.json.
{
"disableAllHooks": true
}
That one costs you your settings hooks and custom status line too, so a PreToolUse hook you rely on goes with it. Neither --safe-mode nor disableAllHooks stops the mods built into Claude Code, such as the one that loads AGENTS.md. You turn most of those off one at a time in /plugin. The sec-default guard is the exception: the overview says it can't be turned off.
On a team, there's a narrower switch. The admin docs put an allowManagedModsOnly option on the built-in guard, in managed settings. Mods your users bring stop running their hooks, users can't undo it from their own settings files, and their settings hooks keep working:
{
"pluginConfigs": {
"cc-plugin-sec-default@builtin": {
"options": {
"allowManagedModsOnly": true
}
}
}
}
Where the guard lives when Hermes runs Claude Code
I build Hermes IDE, so weigh this section with that in mind. Hermes runs every agent's own interface "in a real terminal, exactly as it would anywhere else" (README). That is the design, and here it cuts both ways: Hermes puts nothing between Claude Code and its plugins. The claude that starts in a Hermes task is the one installed on your machine, with your settings and your plugins, so the mods you installed load there too.

The ⌘N agent picker: Claude Code (installed, 2.1.299) selected, Codex installed (0.145.0), and the other supported agents marked Not installed.
Hermes "never answers a permission prompt for you." A mod can, and when it approves a call before the prompt appears, there is no prompt. The attention inbox has nothing to show you, because nothing was waiting. What Hermes gives you is the record afterwards: each task on its own hermes/ branch and worktree, every agent turn saved so you can diff or restore it, and the Review Desk (⌘G) by turn or by file. That covers what landed in the task's worktree. It does not cover a file a mod writes somewhere else, a key it reads, or a request it sends. Hermes is not a sandbox.
So the guard goes where Claude Code reads it: the plugin's entry in /plugin, disableAllHooks in ~/.claude/settings.json, or managed settings on a team machine. Set it there once and it holds in Hermes, in your own terminal and in your editor.

The ⌘N New task sheet: Claude Code, the orders-api project, a new hermes/ worktree, Accept edits, model and effort, and the "Hermes will run" preview of the claude command.
The sheet shows the exact claude command before it runs. If you want one session with no installed mods at all, Hermes also opens plain shells in a real terminal, so you can open one and start claude --safe-mode there yourself, with the costs listed above. Hermes is free to download, source-available under BSL 1.1 on GitHub, and runs on macOS, Windows and Linux. It isn't affiliated with Anthropic or with Nous Research.
Would you install a stranger's mod?
Mods are worth having. Anthropic's own samples include blast-radius, which holds a risky shell command such as rm -rf or a force push and shows what it would change before you proceed. That is a guard you could not build with a prompt. The same access that makes it work is what a careless or hostile mod gets.
So install a mod the way you add a dependency to a service you're on call for. Before the next /plugin install, run claude plugin validate on the clone and read the calls: line.
Did you know mods were on in your Claude Code, and do you validate a plugin before you install it? Tell me in the comments.
If this was useful
A mod is tool calling seen from the other side: code that decides whether a function call runs, rewrites its arguments, or answers it without running anything. AI That Acts is the side where you build those calls yourself, in TypeScript: tool calling, functions, and a first working agent. If this post made you want to understand what is passing through tool.call, start there.
It's book 3 of AI in TypeScript, five books from your first LLM call to agents in production: the whole series is here.

Top comments (1)
Disclosure: we build Auten and ship a Claude Code plugin, so this is the kind of review we hope people run on ours.
One gap worth adding to the tag-pinning advice: pinning the marketplace to a tag freezes the files in the repo, not what those files fetch. A plugin whose MCP server is
npx -y some-packagewith no version pulls whatever npm has at session start, so the code that runs can change while the plugin repo still sits at the tag you read. When reviewing, read themcpServerscommand in the manifest and look for an exact version (some-package@1.2.3), and treat a bump there like a lockfile change. Even pinned, the package's own dependencies resolve fresh unless it ships a shrinkwrap, so the review reaches one level further than it looks. We pin ours for exactly this reason.Does
claude plugin validateprint the MCP launch command anywhere, or is the manifest the only place to see it?