Sandbox Write to --plugin-dir Escapes to Host Execution: Claude Code 2.1.299 Analysis

Claude Code 2.1.299 fixed a vulnerability where sandboxed shell commands could write to the --plugin-dir directory, which the host then reloads and executes outside the sandbox, revealing that write protection must be dynamically derived from actual session-loaded code paths rather than static directory lists, and requiring cross-boundary testing that verifies write-to-execution chains.

Architecture Development Notes
Architecture Development Notes
Architecture Development Notes
Sandbox Write to --plugin-dir Escapes to Host Execution: Claude Code 2.1.299 Analysis

Sandbox Default Write Permissions

Claude Code's Bash sandbox confines shell commands and their child processes within an OS-enforced boundary. The documented default write scope includes the current working directory and its subdirectories, directories added via --add-dir or permissions.additionalDirectories, and each user's temporary directory. Read scope is much broader — essentially the entire machine, including credential files, unless otherwise configured.

This default has a practical reason: the agent must modify repository files, so the workspace must be writable. The problem arises when "workspace writable" overlaps with "certain files in the workspace are executed by the host."

The documentation already recognizes this overlap. Inside writable directories, the sandbox still refuses writes to a set of protected paths, including .claude settings files, .claude/skills, .claude/agents, .claude/commands, .claude/hooks, .mcp.json, .claude/workflows, .claude/scheduled_tasks.json, workspace shell startup files, .gitconfig, .vscode, .idea, .git hooks and config, most contents under ~/.claude or CLAUDE_CONFIG_DIR, ~/.claude.json, and credential storage. The documented rationale is not merely that "these files are important," but that a command able to modify them could grant itself authorization or add a hook or MCP server that Claude Code runs outside the sandbox.

This statement provides the real criterion: what is protected is what the host will later load and trust — both configuration that can self-authorize and code that will be executed. By this criterion, the list should be derived from the host's load paths, not enumerated by directory convention.

--plugin-dir Brings Load Paths Outside the List

--plugin-dir

is a session-level parameter: load plugins from a directory or zip, valid only for this session, repeatable for multiple paths. Common usage points to a plugin directory inside the repository, e.g., ./my-plugin.

This directory typically lives inside the workspace, which is writable by default. Yet the documented protected paths do not include --plugin-dir. The four enumerated groups are fixed locations: (1) .claude settings and directories, .mcp.json in the workspace and its parents; (2) workspace startup files, .gitconfig, editor config, .git hooks; (3) bare repo files at workspace root; (4) ~/.claude or CLAUDE_CONFIG_DIR. A directory passed via a command-line argument usually falls outside all four groups; its location depends on what the user passes this time.

Version 2.1.299 fixed exactly this intersection. The changelog states: "a sandboxed shell command being able to write into a --plugin-dir folder, whose code Claude Code reloads and runs outside the sandbox." It confirms two facts: the write can occur; the directory's code is reloaded and runs outside the sandbox. The changelog does not specify the directory's location at trigger time. By default write scope, the most likely scenario is that it resides in the current working directory, an added directory, or a temp directory — all places the sandbox intentionally allows writes. The changelog does not confirm this.

The changelog also does not describe the fix method — whether the plugin directory was added to the write-protection deny-list, or whether reloads now refuse to execute modified files. From this single line we can only confirm the behavior: after this version in the latest channel, sandboxed commands can no longer write into that directory.

There is an easy misreading here. As of the documentation read on October 18, 2026, the four enumerated groups of protected paths still do not list --plugin-dir; the documentation states at the principle level that "files Claude Code loads for configuration and code" are protected, yet the enumeration omits this session parameter. The list is the documented default set, not necessarily the complete judgment in the implementation; true coverage must be observed from the current session's actual behavior.

After Write, Who Executes

Plugins are not mere metadata packages. The official documentation states directly: plugins can execute arbitrary code on your machine with your user privileges. Command-type hooks and monitors are shell commands; local stdio MCP servers and LSP servers are processes launched by Claude Code; mods are JavaScript running inside the Claude Code process. Hooks, monitors, MCP servers, and LSP servers all run outside the sandbox with full user privileges.

This explains why "able to write plugin directory" is more severe than "able to write a data file." Writing a data file changes the next read; writing a plugin directory changes the next loaded and executed code. The execution trigger threshold is low: default monitors run on session start and plugin reload; hooks run before/after the next tool call; local stdio MCP servers are launched by Claude Code on session start or plugin enable. These local servers do not auto-reconnect; only remote HTTP/SSE servers auto-reconnect, and those are not processes from the local plugin directory. The documentation also notes that plugin marketplace auto-updates rewrite files on disk in the background, so this directory is inherently a code supply chain, not just a cache.

Permission rules cannot close this gap. Permission rules govern tool calls initiated by Claude; the sandbox governs shell commands and their children. Code that the plugin itself runs belongs to neither. This is the same problem as "putting untrusted extensions in a cage": the cage must be a process boundary, not a permission prompt.

Path Identity Changes During a Session

Version 2.1.299 also fixed two symlink-related issues, which together illustrate the limits of a static list.

One fix addresses macOS: a sandboxed command could read/write a normally denied path via the real location of a symlink. The other fix addresses allowRead and allowWrite wildcard rules: when the rule's starting directory is replaced with a symlink, the rule is not re-evaluated.

These two fixes address different rules. One is a mismatch between the observed path and the actual read/write target; the other is a rule whose starting directory was swapped during the session without re-checking. If write protection is computed once at rule creation and never re-examines path identity changes, it misses two kinds of changes: the target a path points to changes, or the path itself is replaced. The documentation's handling of protected settings files moves in this direction: if a symlink appears at a protected settings file path during the session, the sandbox refuses writes to its target from the next command onward. The key is not only resolving the real target, but re-checking after path identity changes, rather than reusing the earlier conclusion.

The same questions apply to the plugin directory, though the changelog does not cover this. Is this directory a symlink? Does it fall inside the workspace or an added directory — i.e., a writable scope? After session start, has it been replaced? If write protection only recognizes fixed locations like " .claude in the workspace," none of these questions have answers.

Verification Must Cover "Write Then Execute"

Typical sandbox tests are: /etc cannot be written, workspace can be written, network allowed by domain. These are necessary but insufficient to cover the path above. A cross-boundary test suite is needed, run with actual production parameters, not default config.

At minimum, enumerate this session's code and config load paths: --plugin-dir directories (including plugin directories inside --add-dir ), marketplace install directories, and .claude/settings.json , .claude/skills , .claude/hooks , .mcp.json — all paths that will be loaded. This set varies with parameters; a static checklist is not enough.

Inside the sandbox, attempt a write to each path: for a file path, try to modify it; for a directory, create a sentinel file inside. Expect rejection. Any success means that path lacks write protection.

For paths where write succeeds, perform an execution test: write the sentinel into a declared hook or monitor script, or replace a known loaded executable; trigger a plugin reload or start a new session; observe whether it writes a marker file. Expect no execution; if execution occurs, the write-to-execute chain is open.

Add a symlink variant: in a writable directory, create a link pointing to a protected path; write through the link; then replace the link's target during the session. Expect both cases to be rejected.

Keep a reverse control: write to a regular data file in the workspace, expect success. If this step also fails, first investigate denyWrite , read-only mounts, and file permissions; only if the entire workspace is unwritable should filesystem.disabled be suspected.

The point of this test suite is not "how long is the write-protection list," but "from a single sandbox write to an outside execution, is there a break in the chain?" As long as that chain exists, missing one path from the list is enough.

Co-locate Load Entry and Write Protection

If you are building an agent host, the soundest approach is not maintaining a longer directory deny-list, but ensuring that "adding a code load entry" and "adding it to the write-protection set" happen in the same code location. --plugin-dir, --settings, --mcp-config are session-level load entries; --add-dir changes the writable scope, and if it contains a plugin directory, that must be accounted for as well. Each addition should trigger a recomputation of the write-protection set. Paths must be resolved to real targets; directory changes must trigger recomputation, not a one-time calculation at startup.

Two broader deployment mitigations exist. Plugin directories can be placed outside the workspace or mounted read-only; if they must reside in the workspace, at least deny sandbox commands write access to them. If your team follows the stable channel, verify the version first: at writing time stable still lacks this fix, so plugin directory isolation cannot wait. With auto-updates enabled, this directory is rewritten in the background; audit records must capture version and commit, not just "reviewed a plugin."

Finally, remember the sandbox boundary itself. The documentation says it only wraps shell commands; file tools, MCP servers, and hooks run outside. When hard isolation is required, run the entire Claude Code process inside a container, VM, or sandbox runtime — do not rely on the Bash sandbox to cover all processes.

The one-line fix in 2.1.299 is short, but the question it raises is large: when is a write-protection list complete? The answer is not "list enough entries," but "it covers every path the host will actually load in this session." Verifying that can only be done by walking those paths, not by reading the list.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

supply chainsecurity testingClaude Codesandbox securitypath resolutionwrite protectionplugin directoryversion 2.1.299
Architecture Development Notes
Written by

Architecture Development Notes

Focused on architecture design, technology trend analysis, and practical development experience sharing.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.