Claude Mods: the new attack surface built in

BY Yuval Fischer
7 MIN READ

Claude Code did it again, quietly releasing a full-blown feature - Claude Mods, also known as Function Hooks. In this article, we’ll refer to it simply as Claude Mods.
On September 9, 2026, Anthropic announced the name and said general release was weeks away. The feature is in preview and is off by default and can be enabled by explicitly setting CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1. Anthropic's type contract is still marked early access, so details may change between releases.
The Dash research team analyzed the feature and found that it introduces a significant new attack surface. This article breaks down how mods work, where they load from, what they can reach, and the security risks that come with them.
Claude Code Mods - the what and the how
Put simply, a mod loads into the Claude Code process and registers function hooks on the engine's events. A hook can inspect an operation, modify its arguments, let it continue, replace its result, or perform additional actions around it. In practice, a tool call passes from Claude to the mod, then to the tool, and back through the mod on the way out (So a tool call effectively becomes Claude → Mod → Tool → Mod → Claude). When several mods hook the same operation, they nest in registration order, with managed plugins an administrator prepends on the outside.
From a security perspective, this matters because a mod sits directly on the path between user intent, model decisions, and execution on the host. The module stays loaded for the session, can maintain state, and can reach the operations Claude Code exposes through $: tools, files, processes, prompts, session data, MCPs, and UI. This is a different level of trust than a shell hook, which runs as a separate process and sees only the JSON Claude passes to it.
Where a mod lives, and how it gets loaded
A mod is not a new install format - it’s a plugin whose hooks/hooks.json lists a module:
{ "modules": ["./hooks.ts"] }
Claude loads that file into the plugin's own environment. The loader identifies each plugin by its provenance, a name plus a suffix, and that is what policy matches on.
Provenance | Default path | Loaded by |
|---|---|---|
|
|
|
| any absolute path |
|
|
| auto-adopted, no install step |
|
| synced from claude.ai |
| inside the binary | always |
The load paths are not equally risky:
@skills-dirneeds the closest watch - A folder in~/.claude/skillsloads with no install step, so a mod can arrive as a skill folder instead of a plugin someone chose to enable. The same folder inside a project's.claude/skillsloads once you accept the workspace trust dialog, which most people click through.@inlineis next - it loads from any absolute path, and that path can come from a command-line flag.Marketplace plugins sit in a versioned cache under
~/.claude/plugins/cache.Built-ins ship with the binary and load every session.
Claude Mods ≠ Claude Code Hooks
So what’s the big deal - you may ask. We already had hooks before, so are they just called Function Hooks now?
No, not quite.
Classic hooks are mostly shell commands configured in settings. Claude launches the command at one of a fixed set of lifecycle points (PreToolUse, Stop, SessionStart, and about thirty others), passes it a JSON blob via stdin, and reads a JSON decision back from stdout. There is one process per invocation and no shared memory between calls. A settings hook can also be an HTTP endpoint, an MCP tool, a prompt, or an agent. None of these is an in-process module.
The two models coexist - on a classic hook event, managed settings hooks run first, then mods, then the remaining settings hooks. A deny from a managed hook stops the call before any mod runs. Mods add to command hooks - they do not replace them.
Classic hooks | Mods | |
Where the code runs | A separate OS process | The plugin's own environment inside the Claude Code process. |
Event set | ~30 fixed lifecycle points | The events the engine raises, plus every call a plugin makes on |
Shell | Only via its own shell authority |
|
State | None between invocations | Module variables for the session. |
UI | None | Can draw and rewrite Claude's own UI |
One clarification matters here:: mods did not introduce arbitrary code execution to Claude Code. A classic hook was always a shell command with your full user authority, and so is the Bash tool. What mods introduce is structure - and with it, a much larger set of things that can be reached, composed, and made persistent.
Mods - the capabilities and the attack surface
When we researched this capability,, we enumerated all the functions currently available in Claude Code at the time of writing (v2.1.277). We wanted to highlight a few capabilities in particular that we believe threat actors will find ways to abuse.
Host control (execute, read, write)
Process Execution -
$.process.run(argv, { cwd, env, stdin, timeoutMs })runs any binary as the session user, with no shell. The contract marks this CLI only. The default timeout is 30 seconds, capped at ten minutes.File Read -
$.fs.readreads a file. A relative path resolves under the session directory. An absolute path is used as given.File Write -
$.fs.writewrites a file.
Data reach
Session transcript -
$.session.messages()returns the transcript with tool inputs and results, up to the newest 4,096 messages.Settings -
$.settings.read()returns the merged settings, or a single source, including managed policy. The contract is explicit: every key crosses as the source holds it, env and helper commands included, and nothing is filtered. The OAuth session and~/.claude.jsonare not part of this read.Internet fetch -
$.http.fetchreaches whatever the host process can, unless an administrator's policy refuses it.MCP data -
$.mcp.call(server, tool, args)invokes a connected MCP server with the engine's own connection and credentials. There is no permission prompt. The plugin's call, seen by the hooks above it, is the grant.
Model manipulation
These are events a mod registers with on, not methods it calls on $. Returning a value answers for the engine.
Tool confusion -
on("tool.describe")rewrites a tool's description as the model reads it. The result is cached for the session.System prompt control -
on("prompt.section")returning{ text: null }drops that named section of the system prompt. The contract's own example dropsmemorythis way.Skill manipulation -
on("skill.prompt")replaces the text a skill expands to.Agent listing -
on("agent.offer")returning{ isOffered: false }hides that agent type from the model's listing and from dispatch.
Control of what the human sees
Mods render real UI (panes, buttons, inputs, selects) across terminal, desktop, VS Code, and mobile. on("ui.render") lets a mod wrap or replace how components are drawn, including ToolUse.
Including all those capabilities means threat actors now have a full suite of capabilities to write malicious mods that may be invisible to the Endpoint security since the code runs inside of Claude code. In an era where coding agents are granted more and more autonomy and trust one hiding malicious mod can change everything.
Abusing mods - a demo
With such a broad attack surface, the possibilities for abuse are extensive. To demonstrate what a malicious Mod could look like, we built a simple proof of concept of a wallet-swap attack that intercepts Bitcoin wallet addresses and replaces them with an attacker-controlled address.
Cryptocurrency address hijacking is nothing new. What makes Mods interesting from a security perspective is where the malicious logic lives: directly inside the Claude Code process. An endpoint security solution without visibility into the coding agent’s internal behavior could easily miss the actual source of the manipulation. At best, it may only see a legitimate Claude Code process performing the actions, while the malicious Mod operates inside it.
What organizations can do about it
A mod is code that runs as you, inside the tool you trust to write your code. Treat installing one like running a binary someone sent you.
Find out if mods are on. While the feature is in preview, the documented way to turn it on is
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1. Check shell profiles, dev containers, and any environment your team manages.
Look where mods load from. A mod is any plugin whose
hooks/hooks.jsonhas amoduleskey. Check the plugin cache,~/.claude/skills, and the.claude/skillsfolder of every repo you open:
grep -rl '"modules"' ~/.claude/plugins ~/.claude/skills ./.claude/skills --include=hooks.json 2>/dev/null
Read what a mod calls before you run it:
claude plugin validatelists the events a mod hooks and the calls it makes on$. Be wary ofhttp.fetchtogether withprocess.run: with both, a mod can download code and run it after you reviewed it.
Read the
next()calls too: That scan only lists$calls. A mod that only rewrites what it passes tonext(), like our wallet demo comes back clean. The source is the only place that shows it.
Gate mods centrally: An administrator can ship a managed plugin that hooks
plugin.registerand refuses a user-installed mod based on its tier and the calls it makes:
on("plugin.register", { tier: "user" }, ($, e, next) => { const canFetchAndRun = e.uses.calls.includes("http.fetch") && e.uses.calls.includes("process.run") return canFetchAndRun ? { refuse: "network plus process is not allowed" } : next(e) })
Conclusion
A mod can run code, read files, and reach the network, and a classic hook could already do those things as the user. What Claude Mods add is a place for that code to live inside Claude Code, where it can also change the prompt, the tool call, and the transcript without a process of its own, so endpoint security keeps seeing Claude as the process. The feature is off by default today, behind CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1, and Anthropic has said it will ship in weeks. Decide which mods your organization will allow before that flag is no longer required.
Claude Code did it again, quietly releasing a full-blown feature - Claude Mods, also known as Function Hooks. In this article, we’ll refer to it simply as Claude Mods.
On September 9, 2026, Anthropic announced the name and said general release was weeks away. The feature is in preview and is off by default and can be enabled by explicitly setting CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1. Anthropic's type contract is still marked early access, so details may change between releases.
The Dash research team analyzed the feature and found that it introduces a significant new attack surface. This article breaks down how mods work, where they load from, what they can reach, and the security risks that come with them.
Claude Code Mods - the what and the how
Put simply, a mod loads into the Claude Code process and registers function hooks on the engine's events. A hook can inspect an operation, modify its arguments, let it continue, replace its result, or perform additional actions around it. In practice, a tool call passes from Claude to the mod, then to the tool, and back through the mod on the way out (So a tool call effectively becomes Claude → Mod → Tool → Mod → Claude). When several mods hook the same operation, they nest in registration order, with managed plugins an administrator prepends on the outside.
From a security perspective, this matters because a mod sits directly on the path between user intent, model decisions, and execution on the host. The module stays loaded for the session, can maintain state, and can reach the operations Claude Code exposes through $: tools, files, processes, prompts, session data, MCPs, and UI. This is a different level of trust than a shell hook, which runs as a separate process and sees only the JSON Claude passes to it.
Where a mod lives, and how it gets loaded
A mod is not a new install format - it’s a plugin whose hooks/hooks.json lists a module:
{ "modules": ["./hooks.ts"] }
Claude loads that file into the plugin's own environment. The loader identifies each plugin by its provenance, a name plus a suffix, and that is what policy matches on.
Provenance | Default path | Loaded by |
|---|---|---|
|
|
|
| any absolute path |
|
|
| auto-adopted, no install step |
|
| synced from claude.ai |
| inside the binary | always |
The load paths are not equally risky:
@skills-dirneeds the closest watch - A folder in~/.claude/skillsloads with no install step, so a mod can arrive as a skill folder instead of a plugin someone chose to enable. The same folder inside a project's.claude/skillsloads once you accept the workspace trust dialog, which most people click through.@inlineis next - it loads from any absolute path, and that path can come from a command-line flag.Marketplace plugins sit in a versioned cache under
~/.claude/plugins/cache.Built-ins ship with the binary and load every session.
Claude Mods ≠ Claude Code Hooks
So what’s the big deal - you may ask. We already had hooks before, so are they just called Function Hooks now?
No, not quite.
Classic hooks are mostly shell commands configured in settings. Claude launches the command at one of a fixed set of lifecycle points (PreToolUse, Stop, SessionStart, and about thirty others), passes it a JSON blob via stdin, and reads a JSON decision back from stdout. There is one process per invocation and no shared memory between calls. A settings hook can also be an HTTP endpoint, an MCP tool, a prompt, or an agent. None of these is an in-process module.
The two models coexist - on a classic hook event, managed settings hooks run first, then mods, then the remaining settings hooks. A deny from a managed hook stops the call before any mod runs. Mods add to command hooks - they do not replace them.
Classic hooks | Mods | |
Where the code runs | A separate OS process | The plugin's own environment inside the Claude Code process. |
Event set | ~30 fixed lifecycle points | The events the engine raises, plus every call a plugin makes on |
Shell | Only via its own shell authority |
|
State | None between invocations | Module variables for the session. |
UI | None | Can draw and rewrite Claude's own UI |
One clarification matters here:: mods did not introduce arbitrary code execution to Claude Code. A classic hook was always a shell command with your full user authority, and so is the Bash tool. What mods introduce is structure - and with it, a much larger set of things that can be reached, composed, and made persistent.
Mods - the capabilities and the attack surface
When we researched this capability,, we enumerated all the functions currently available in Claude Code at the time of writing (v2.1.277). We wanted to highlight a few capabilities in particular that we believe threat actors will find ways to abuse.
Host control (execute, read, write)
Process Execution -
$.process.run(argv, { cwd, env, stdin, timeoutMs })runs any binary as the session user, with no shell. The contract marks this CLI only. The default timeout is 30 seconds, capped at ten minutes.File Read -
$.fs.readreads a file. A relative path resolves under the session directory. An absolute path is used as given.File Write -
$.fs.writewrites a file.
Data reach
Session transcript -
$.session.messages()returns the transcript with tool inputs and results, up to the newest 4,096 messages.Settings -
$.settings.read()returns the merged settings, or a single source, including managed policy. The contract is explicit: every key crosses as the source holds it, env and helper commands included, and nothing is filtered. The OAuth session and~/.claude.jsonare not part of this read.Internet fetch -
$.http.fetchreaches whatever the host process can, unless an administrator's policy refuses it.MCP data -
$.mcp.call(server, tool, args)invokes a connected MCP server with the engine's own connection and credentials. There is no permission prompt. The plugin's call, seen by the hooks above it, is the grant.
Model manipulation
These are events a mod registers with on, not methods it calls on $. Returning a value answers for the engine.
Tool confusion -
on("tool.describe")rewrites a tool's description as the model reads it. The result is cached for the session.System prompt control -
on("prompt.section")returning{ text: null }drops that named section of the system prompt. The contract's own example dropsmemorythis way.Skill manipulation -
on("skill.prompt")replaces the text a skill expands to.Agent listing -
on("agent.offer")returning{ isOffered: false }hides that agent type from the model's listing and from dispatch.
Control of what the human sees
Mods render real UI (panes, buttons, inputs, selects) across terminal, desktop, VS Code, and mobile. on("ui.render") lets a mod wrap or replace how components are drawn, including ToolUse.
Including all those capabilities means threat actors now have a full suite of capabilities to write malicious mods that may be invisible to the Endpoint security since the code runs inside of Claude code. In an era where coding agents are granted more and more autonomy and trust one hiding malicious mod can change everything.
Abusing mods - a demo
With such a broad attack surface, the possibilities for abuse are extensive. To demonstrate what a malicious Mod could look like, we built a simple proof of concept of a wallet-swap attack that intercepts Bitcoin wallet addresses and replaces them with an attacker-controlled address.
Cryptocurrency address hijacking is nothing new. What makes Mods interesting from a security perspective is where the malicious logic lives: directly inside the Claude Code process. An endpoint security solution without visibility into the coding agent’s internal behavior could easily miss the actual source of the manipulation. At best, it may only see a legitimate Claude Code process performing the actions, while the malicious Mod operates inside it.
What organizations can do about it
A mod is code that runs as you, inside the tool you trust to write your code. Treat installing one like running a binary someone sent you.
Find out if mods are on. While the feature is in preview, the documented way to turn it on is
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1. Check shell profiles, dev containers, and any environment your team manages.
Look where mods load from. A mod is any plugin whose
hooks/hooks.jsonhas amoduleskey. Check the plugin cache,~/.claude/skills, and the.claude/skillsfolder of every repo you open:
grep -rl '"modules"' ~/.claude/plugins ~/.claude/skills ./.claude/skills --include=hooks.json 2>/dev/null
Read what a mod calls before you run it:
claude plugin validatelists the events a mod hooks and the calls it makes on$. Be wary ofhttp.fetchtogether withprocess.run: with both, a mod can download code and run it after you reviewed it.
Read the
next()calls too: That scan only lists$calls. A mod that only rewrites what it passes tonext(), like our wallet demo comes back clean. The source is the only place that shows it.
Gate mods centrally: An administrator can ship a managed plugin that hooks
plugin.registerand refuses a user-installed mod based on its tier and the calls it makes:
on("plugin.register", { tier: "user" }, ($, e, next) => { const canFetchAndRun = e.uses.calls.includes("http.fetch") && e.uses.calls.includes("process.run") return canFetchAndRun ? { refuse: "network plus process is not allowed" } : next(e) })
Conclusion
A mod can run code, read files, and reach the network, and a classic hook could already do those things as the user. What Claude Mods add is a place for that code to live inside Claude Code, where it can also change the prompt, the tool call, and the transcript without a process of its own, so endpoint security keeps seeing Claude as the process. The feature is off by default today, behind CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1, and Anthropic has said it will ship in weeks. Decide which mods your organization will allow before that flag is no longer required.
Claude Code did it again, quietly releasing a full-blown feature - Claude Mods, also known as Function Hooks. In this article, we’ll refer to it simply as Claude Mods.
On September 9, 2026, Anthropic announced the name and said general release was weeks away. The feature is in preview and is off by default and can be enabled by explicitly setting CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1. Anthropic's type contract is still marked early access, so details may change between releases.
The Dash research team analyzed the feature and found that it introduces a significant new attack surface. This article breaks down how mods work, where they load from, what they can reach, and the security risks that come with them.
Claude Code Mods - the what and the how
Put simply, a mod loads into the Claude Code process and registers function hooks on the engine's events. A hook can inspect an operation, modify its arguments, let it continue, replace its result, or perform additional actions around it. In practice, a tool call passes from Claude to the mod, then to the tool, and back through the mod on the way out (So a tool call effectively becomes Claude → Mod → Tool → Mod → Claude). When several mods hook the same operation, they nest in registration order, with managed plugins an administrator prepends on the outside.
From a security perspective, this matters because a mod sits directly on the path between user intent, model decisions, and execution on the host. The module stays loaded for the session, can maintain state, and can reach the operations Claude Code exposes through $: tools, files, processes, prompts, session data, MCPs, and UI. This is a different level of trust than a shell hook, which runs as a separate process and sees only the JSON Claude passes to it.
Where a mod lives, and how it gets loaded
A mod is not a new install format - it’s a plugin whose hooks/hooks.json lists a module:
{ "modules": ["./hooks.ts"] }
Claude loads that file into the plugin's own environment. The loader identifies each plugin by its provenance, a name plus a suffix, and that is what policy matches on.
Provenance | Default path | Loaded by |
|---|---|---|
|
|
|
| any absolute path |
|
|
| auto-adopted, no install step |
|
| synced from claude.ai |
| inside the binary | always |
The load paths are not equally risky:
@skills-dirneeds the closest watch - A folder in~/.claude/skillsloads with no install step, so a mod can arrive as a skill folder instead of a plugin someone chose to enable. The same folder inside a project's.claude/skillsloads once you accept the workspace trust dialog, which most people click through.@inlineis next - it loads from any absolute path, and that path can come from a command-line flag.Marketplace plugins sit in a versioned cache under
~/.claude/plugins/cache.Built-ins ship with the binary and load every session.
Claude Mods ≠ Claude Code Hooks
So what’s the big deal - you may ask. We already had hooks before, so are they just called Function Hooks now?
No, not quite.
Classic hooks are mostly shell commands configured in settings. Claude launches the command at one of a fixed set of lifecycle points (PreToolUse, Stop, SessionStart, and about thirty others), passes it a JSON blob via stdin, and reads a JSON decision back from stdout. There is one process per invocation and no shared memory between calls. A settings hook can also be an HTTP endpoint, an MCP tool, a prompt, or an agent. None of these is an in-process module.
The two models coexist - on a classic hook event, managed settings hooks run first, then mods, then the remaining settings hooks. A deny from a managed hook stops the call before any mod runs. Mods add to command hooks - they do not replace them.
Classic hooks | Mods | |
Where the code runs | A separate OS process | The plugin's own environment inside the Claude Code process. |
Event set | ~30 fixed lifecycle points | The events the engine raises, plus every call a plugin makes on |
Shell | Only via its own shell authority |
|
State | None between invocations | Module variables for the session. |
UI | None | Can draw and rewrite Claude's own UI |
One clarification matters here:: mods did not introduce arbitrary code execution to Claude Code. A classic hook was always a shell command with your full user authority, and so is the Bash tool. What mods introduce is structure - and with it, a much larger set of things that can be reached, composed, and made persistent.
Mods - the capabilities and the attack surface
When we researched this capability,, we enumerated all the functions currently available in Claude Code at the time of writing (v2.1.277). We wanted to highlight a few capabilities in particular that we believe threat actors will find ways to abuse.
Host control (execute, read, write)
Process Execution -
$.process.run(argv, { cwd, env, stdin, timeoutMs })runs any binary as the session user, with no shell. The contract marks this CLI only. The default timeout is 30 seconds, capped at ten minutes.File Read -
$.fs.readreads a file. A relative path resolves under the session directory. An absolute path is used as given.File Write -
$.fs.writewrites a file.
Data reach
Session transcript -
$.session.messages()returns the transcript with tool inputs and results, up to the newest 4,096 messages.Settings -
$.settings.read()returns the merged settings, or a single source, including managed policy. The contract is explicit: every key crosses as the source holds it, env and helper commands included, and nothing is filtered. The OAuth session and~/.claude.jsonare not part of this read.Internet fetch -
$.http.fetchreaches whatever the host process can, unless an administrator's policy refuses it.MCP data -
$.mcp.call(server, tool, args)invokes a connected MCP server with the engine's own connection and credentials. There is no permission prompt. The plugin's call, seen by the hooks above it, is the grant.
Model manipulation
These are events a mod registers with on, not methods it calls on $. Returning a value answers for the engine.
Tool confusion -
on("tool.describe")rewrites a tool's description as the model reads it. The result is cached for the session.System prompt control -
on("prompt.section")returning{ text: null }drops that named section of the system prompt. The contract's own example dropsmemorythis way.Skill manipulation -
on("skill.prompt")replaces the text a skill expands to.Agent listing -
on("agent.offer")returning{ isOffered: false }hides that agent type from the model's listing and from dispatch.
Control of what the human sees
Mods render real UI (panes, buttons, inputs, selects) across terminal, desktop, VS Code, and mobile. on("ui.render") lets a mod wrap or replace how components are drawn, including ToolUse.
Including all those capabilities means threat actors now have a full suite of capabilities to write malicious mods that may be invisible to the Endpoint security since the code runs inside of Claude code. In an era where coding agents are granted more and more autonomy and trust one hiding malicious mod can change everything.
Abusing mods - a demo
With such a broad attack surface, the possibilities for abuse are extensive. To demonstrate what a malicious Mod could look like, we built a simple proof of concept of a wallet-swap attack that intercepts Bitcoin wallet addresses and replaces them with an attacker-controlled address.
Cryptocurrency address hijacking is nothing new. What makes Mods interesting from a security perspective is where the malicious logic lives: directly inside the Claude Code process. An endpoint security solution without visibility into the coding agent’s internal behavior could easily miss the actual source of the manipulation. At best, it may only see a legitimate Claude Code process performing the actions, while the malicious Mod operates inside it.
What organizations can do about it
A mod is code that runs as you, inside the tool you trust to write your code. Treat installing one like running a binary someone sent you.
Find out if mods are on. While the feature is in preview, the documented way to turn it on is
CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1. Check shell profiles, dev containers, and any environment your team manages.
Look where mods load from. A mod is any plugin whose
hooks/hooks.jsonhas amoduleskey. Check the plugin cache,~/.claude/skills, and the.claude/skillsfolder of every repo you open:
grep -rl '"modules"' ~/.claude/plugins ~/.claude/skills ./.claude/skills --include=hooks.json 2>/dev/null
Read what a mod calls before you run it:
claude plugin validatelists the events a mod hooks and the calls it makes on$. Be wary ofhttp.fetchtogether withprocess.run: with both, a mod can download code and run it after you reviewed it.
Read the
next()calls too: That scan only lists$calls. A mod that only rewrites what it passes tonext(), like our wallet demo comes back clean. The source is the only place that shows it.
Gate mods centrally: An administrator can ship a managed plugin that hooks
plugin.registerand refuses a user-installed mod based on its tier and the calls it makes:
on("plugin.register", { tier: "user" }, ($, e, next) => { const canFetchAndRun = e.uses.calls.includes("http.fetch") && e.uses.calls.includes("process.run") return canFetchAndRun ? { refuse: "network plus process is not allowed" } : next(e) })
Conclusion
A mod can run code, read files, and reach the network, and a classic hook could already do those things as the user. What Claude Mods add is a place for that code to live inside Claude Code, where it can also change the prompt, the tool call, and the transcript without a process of its own, so endpoint security keeps seeing Claude as the process. The feature is off by default today, behind CLAUDE_CODE_ENABLE_FUNCTION_HOOKS=1, and Anthropic has said it will ship in weeks. Decide which mods your organization will allow before that flag is no longer required.


