DEV Community

Rulestack
Rulestack

Posted on

VS Code 1.141.0 let Copilot edit hook files with no prompt. Claude Code's default mode prompts for .claude/ writes

On 2026-10-13 the VS Code team published an advisory for CVE-2026-102232: in VS Code 1.141.0 and earlier, the Copilot agent could edit hook files in custom hook locations without the approval prompt, so content the agent read could get it to add a shell command that later ran with the user's privileges. Hook files are code, and the same .claude/settings.json can be read by both VS Code's Copilot harness and Claude Code. Here is how each tool treats a write to one, and a Claude Code hook that refuses settings changes mid-session.

What the advisory says

The advisory is GHSA-fq62-m6f5-c7h9, Remote Code Execution via Custom Hook Edits in the Copilot Harness. It was one of 11 VS Code advisories published the same day.

The summary: "The harness could automatically approve edits to enabled custom hook and agent-definition locations configured through chat.hookFilesLocations or chat.agentFilesLocations, treating them as ordinary workspace files." The details spell out the consequence. "An attacker who can influence an agent through malicious content or prompt injection could cause the agent to add a shell command to one of these files without showing the expected approval prompt. The command could then execute with the user's privileges when the affected hook is subsequently invoked."

The conditions are specific: "Exploitation requires the user to have an affected custom location and hooks enabled and to run a Copilot-harness session against attacker-controlled content." The fix shipped in VS Code 1.141.1, which "treats enabled workspace-relative hook and agent-definition locations as sensitive edit locations". After the fix, "Direct writes and shell redirections targeting these locations" require confirmation, and the protection "accounts for multi-root workspaces, symbolic links, case-insensitive paths, and configuration changes made while a session is running".

Quote from the VS Code advisory: the harness could automatically approve edits to enabled custom hook and agent-definition locations, treating them as ordinary workspace files

The workaround for anyone who cannot update yet is to "disable chat hooks by setting chat.useHooks and chat.useClaudeHooks to false", or to remove the custom entries in the two location settings. The last line is advice that applies to every agent with hooks: "Do not approve unexpected changes to hook configuration files or agent definitions."

Why a hook file is as sensitive as a script

A hook is a command the agent's harness runs for you at a point in the session: before a tool call, after an edit, when the session starts. The model does not need to ask for it. That is the point of hooks, and it is also why editing one is equivalent to editing a script that runs on every tool call. VS Code's own hooks documentation says it plainly in its security section: "Hook configuration and scripts are executable code."

The bug was a mismatch between that fact and the approval logic. Edits to source files are approved or not by the usual rules; edits to the files that define what runs automatically were, in this case, approved by the same rules. A prompt injection that only needed to get one line written got a command that would keep running.

One repository file, two harnesses

The part we did not expect is in the same VS Code page. Describing where the Copilot harness looks for hooks, it says it "reads inline hook settings in .github/copilot/settings.json, .github/copilot/settings.local.json, and ~/.copilot/settings.json. It also reads repository .claude/settings.json and .claude/settings.local.json."

So a hook committed to a repository's .claude/settings.json is not only a Claude Code concern. A teammate who opens the same repository in VS Code with the Copilot harness may run it too. When reviewing a pull request that touches .claude/settings.json, it is worth reading the hooks block as if it were a shell script added to the build, because for two tools it is.

How Claude Code treats a write to its hook files

In Claude Code, hooks live in settings files, in a plugin's hooks/hooks.json, and in the frontmatter of skills and subagents. For the settings files and the project's skills and agents, the relevant rule is on the permission modes page: .claude is a protected path, one of a small set of paths that hold repository state and Claude's own configuration, "so each mode has its own rule for writes to them".

Permission mode A write to .claude/
default, acceptEdits Prompted
auto Routed to the classifier
dontAsk Denied
bypassPermissions Allowed

Two more lines on that page close the obvious gaps. "permissions.allow rules in settings files do not pre-approve protected-path writes", so an entry such as Edit(.claude/**) does not turn the prompt off. And when a prompt does appear, it can offer a broader answer: "Yes, and allow Claude to edit files in this project's .claude folder for this session". That option is the closest Claude Code equivalent of the VS Code behavior before the fix, chosen by you, for one session.

Put next to the advisory, the comparison is narrow but useful:

VS Code Copilot harness, 1.141.0 and earlier Claude Code
Files affected Custom locations in chat.hookFilesLocations and chat.agentFilesLocations Settings, skills and agents under .claude/
Agent edit to those files Could be auto-approved as an ordinary file Prompted in default and acceptEdits, classifier in auto
Can an allow rule skip the prompt Not described in the advisory No, allow rules do not pre-approve protected paths
When the edit takes effect When the hook is next invoked Normally in the same session, through the file watcher

That last row deserves a sentence. The hooks reference says "Direct edits to hooks in settings files are normally picked up automatically by the file watcher." An approved edit is not waiting for the next launch. It can fire on the next tool call.

Hooks a repository brings with it

The other path is a hook that was in the repository before you opened it. Claude Code's permissions documentation has a table titled "What runs before you trust a folder". For hooks in settings files, its answer is "Used" in both columns it covers: when you trusted only a parent folder, and when you ran claude -p or the SDK, "which never shows the trust dialog". In an interactive session in a folder you have not trusted, the trust dialog comes first.

For non-interactive runs in a repository you did not write, the same page lists flags to decide what it may run, including --settings '{"disableAllHooks": true}', with the note that setting it in user settings alone "isn't enough, because the repository's project settings take precedence over yours and can set it back to false".

A hook that refuses settings changes during a session

Claude Code has an event for exactly this file: ConfigChange. The hooks reference describes it as running "when a configuration file changes during a session", and lists matchers for user_settings, project_settings, local_settings, policy_settings and skills. A ConfigChange hook can block: "Use exit code 2 or a JSON decision to prevent the change. When blocked, the new settings are not applied to the running session."

A minimal version in ~/.claude/settings.json, so the repository cannot remove it:

{
  "hooks": {
    "ConfigChange": [
      {
        "matcher": "project_settings|local_settings|skills",
        "hooks": [
          {
            "type": "command",
            "command": "echo 'project settings changed during the session; restart to load them' >&2; exit 2"
          }
        ]
      }
    ]
  }
}
Enter fullscreen mode Exit fullscreen mode

A matcher made of letters, underscores and | is read as a list of exact values, per the reference's matcher table. Three caveats before using it. First, "A blocked change surfaces no message to you or to Claude", only a line in the debug log, so you will not see it happen. Second, the documentation says the change is not applied to the running session; it does not say the file is reverted, so the edit is still on disk for the next launch and git diff is where to look. Third, it also blocks changes you wanted: choosing "Yes, and don't ask again" on a permission prompt writes to .claude/settings.local.json, and with this hook that change may not apply in the running session. If that is too strict, the reference's other example only logs changes for auditing.

What to do this week

  • Update VS Code to 1.141.1 or later. If you cannot, use the advisory's workaround and turn chat.useHooks and chat.useClaudeHooks off.
  • Check whether you have entries in chat.hookFilesLocations or chat.agentFilesLocations. The advisory only applies when those custom locations are set and hooks are enabled.
  • Treat changes to .claude/settings.json, .github/hooks/*.json and agent definition files in a pull request as script changes, and read the hooks blocks.
  • In Claude Code, read every prompt for a write under .claude/, decline the session-wide option in repositories you did not set up, and keep bypassPermissions for isolated environments.
  • For claude -p runs on code you did not write, pass --settings '{"disableAllHooks": true}'.

None of this makes hooks unsafe to use. It makes them something to review with the same care as the code they run next to, which is what the VS Code page says in six words.

Do you review hook changes differently from code changes in your repositories, or do they go through the same pull request without anyone opening the JSON? If you have run a ConfigChange hook like the one above for more than a few days, tell us in the comments below what it blocked that you did not expect.

Top comments (0)