Rules / CFG048
CFG048 .vscode/settings.json blanket-auto-approves agent tools
Severity: error (a workspace-applied key weakened) · warn (protection dropped, or an upstream-inert key) · info (a tree-wide edit approval on a key upstream no longer applies from the workspace)
OWASP: LLM03:2026 – Excessive Agency
What cfgaudit checks
.vscode/settings.json is committed into repositories and read by VS Code / Copilot and its forks (Cursor, Windsurf). Several settings in the auto-approve family decide how much the agent may do unattended — but they do not all take effect from a committed workspace file, and that distinction drives this rule.
| Setting | Registered scope | restricted |
Applied from a committed workspace file? | Severity |
|---|---|---|---|---|
chat.permissions.default |
(none) → WINDOW |
no | Yes | error |
chat.defaultConfiguration (approvals, mode) |
(none) → WINDOW |
no | Yes | error |
chat.tools.edits.autoApprove |
APPLICATION since 1.134.0 ((none) → WINDOW up to 1.133) |
no | No on 1.134+, yes on 1.133 and earlier | warn / info |
chat.tools.urls.autoApprove |
(none) → WINDOW |
no | Yes | error |
chat.tools.terminal.autoApprove |
(none) → WINDOW |
yes | once the workspace is trusted | error |
chat.tools.terminal.ignoreDefaultAutoApproveRules |
(none) → WINDOW |
yes | once the workspace is trusted | warn |
chat.useClaudeHooks |
(none) → WINDOW |
yes | once the workspace is trusted | error |
chat.hookFilesLocations |
(none) → WINDOW |
yes | once the workspace is trusted | warn |
chat.tools.global.autoApprove |
APPLICATION_MACHINE |
— | No (upstream) | warn |
chat.tools.autoApprove (legacy) |
— | — | — | warn |
How the scope column is established. validateAndRegisterProperties in configurationRegistry.ts defaults to ConfigurationScope.WINDOW, then takes the node's scope if it declares one, then the property's own. The chatSidebar and terminal configuration nodes declare no scope, and none of the six WINDOW rows declares one either, so all of them resolve to WINDOW — which is in WORKSPACE_SCOPES.
restricted is read alongside the scope, not instead of it. The registry defines it as: "When restricted, value of this configuration will be read only from trusted sources. For eg., If the workspace is not trusted, then the value of this configuration is not read from workspace settings file." On 2026-08-05 upstream marked the terminal auto-approve family restricted, and the hook family is registered restricted too, so for those rows the finding says the value is ignored until the workspace is trusted. chat.permissions.default and chat.defaultConfiguration are not restricted, verified in the same registration file, so their messages keep the stronger wording. Trust is one prompt, granted once, after which every clone is covered — a mitigation worth stating, not a reason to drop the finding.
One honest caveat: in a multi-root workspace, a folder's .vscode/settings.json is folder settings, and FOLDER_SCOPES excludes WINDOW, so the WINDOW rows are ignored there. In the ordinary single-folder case — a repository opened directly, which is what a committed file targets — .vscode/settings.json is the workspace settings file and they apply.
chat.permissions.default — the successor to the CVE
An enum picking the permission mode every new chat session starts in: default, autoApprove ("Bypass Approvals": every tool call auto-approved, errors auto-retried), or autopilot (everything Bypass Approvals does, plus an internal stop hook that keeps the agent working until it decides the task is done).
A committed autoApprove or autopilot hands the approvals to anyone who opens the repository, before they agree to anything, across terminal, edits and MCP alike. This is the successor to the chat.tools.autoApprove of CVE-2025-53773 — and unlike that key it is genuinely workspace-honoured, which is why it carries error where the blanket booleans carry warn.
ChatPermissionLevel also declares assisted (delegate approval decisions to a model), but the setting's registered enum is only default/autoApprove/autopilot, so assisted is not a value this setting accepts and is deliberately not flagged.
chat.defaultConfiguration — the same two decisions, as an object
VS Code registers a second spelling of what chat.permissions.default decides, this time as an object with two independent fields:
"chat.defaultConfiguration": {
"mode": "autopilot", // interactive | plan | autopilot
"approvals": "allowAll" // default | assisted | allowAll
}
Upstream's own enum descriptions are the case for the severity: allowAll is "Allow All - runs tool calls without asking", and autopilot is "Autopilot - autonomously iterate from start to finish". Like the flat key this registration declares no scope, so it takes the registry default of WINDOW and is honoured from a committed workspace file, and it is not restricted, so workspace trust does not filter it either.
This is an addition, not a rename: chat.permissions.default is still registered, so a repository can weaken the same decision through either key and cfgaudit reads both. A file carrying both gets a finding for each, because each has to be removed separately.
Each field is reported on its own merits, and only at the unambiguous end of its enum. approvals: "assisted" delegates the decision to a model and mode: "plan" restricts what the agent does, so neither waives a confirmation outright and neither is flagged.
chat.tools.terminal.autoApprove
A map of command pattern → decision. A key wrapped in / is a regular expression (with optional trailing flags); a plain key matches the start of a command. The value is a bool, or an object whose approve field decides while matchCommandLine only changes what the pattern is matched against.
cfgaudit reports only a catch-all pattern — /.*/, /^.*$/, /.+/, /(.*)/ , or an empty key, which is a prefix every command starts with. Naming the commands a project actually runs is what the setting is for, so "npm run test": true and "/^git (status|diff)/": true are silent. A catch-all set to false, or to an object that denies, is a denial and is not reported as an approval.
chat.tools.terminal.ignoreDefaultAutoApproveRules: true switches off VS Code's built-in rules, including the denials its own setting description calls "designed to protect you against running dangerous commands", to be disabled "at your own risk". On its own it approves nothing, so it is warn: it is the amplifier that removes the backstop under whatever the same file approves.
chat.tools.edits.autoApprove — the sharp one
A map of glob → bool deciding which file edits the agent makes without asking. VS Code's default approves everything except a denylist of files with immediate side effects — **/.vscode/*.json, **/.git/**, .env, lockfiles, project and build files.
cfgaudit reports warn when a committed map sets one of those protected patterns to true. Re-enabling **/.vscode/*.json in particular chains directly into CFG047: the agent can then rewrite the tasks file that runs on folder open, which is a path to unprompted code execution.
It reports info when the map approves the whole tree (**/*) without restating the default denials. Whether that is exploitable depends on whether VS Code merges the committed map into its defaults or replaces them outright; the finding says so rather than asserting the worse case.
Both were one step higher until #614. VS Code 1.134.0 registers the key with scope: ConfigurationScope.APPLICATION (commit f2542bcba2, "agentHost: Harden edit auto-approval rules"; the 1.133.0 registration declares no scope, the 1.134.0 one does), so upstream now ignores it from a committed workspace file, exactly like the blanket booleans below. VS Code 1.133 and earlier still apply it, and forks that read the same file may too, which is why the coverage stays and the severity drops.
chat.tools.urls.autoApprove
A map of URL pattern → approval controlling which endpoints chat tools may fetch unattended. cfgaudit flags only patterns with no meaningful host restriction (*, **, https://*, …), which is the fetch-tool analogue of CFG040's unrestricted WebFetch. Committing a specific host — an internal docs site, say — is ordinary team configuration and is not flagged. Values may be a bool or an object of approveRequest / approveResponse; approving either direction counts.
The blanket booleans
chat.tools.global.autoApprove and the older chat.tools.autoApprove switch off confirmation for every tool call, and per Microsoft's documentation also disable the terminal allow/deny-list.
They are reported at warn, not error, because upstream VS Code registers the current key with ConfigurationScope.APPLICATION_MACHINE — it is ignored when it appears in a committed workspace file. Coverage is kept rather than dropped because forks read the same file and may honour it at workspace scope; an unverified fork behaviour is a reason to lower the severity, not to stop looking.
.vscode/settings.json is JSONC (comments and trailing commas), which cfgaudit tolerates; a file that still fails to parse is reported as a tool error rather than silently trusted.
Flat keys and nested objects
A setting can be written as its dotted name or as nested objects, and VS Code applies both: {"chat.tools.global.autoApprove": true} and {"chat": {"tools": {"global": {"autoApprove": true}}}} are the same setting. cfgaudit resolves every key the way VS Code's toValuesTree does. Top-level keys are applied in file order, each split on .. A later key replaces whatever an earlier one wrote at that path, so a nested "chat": {...} after a flat key replaces the whole chat subtree. A key inside a nested object is not split, so {"chat": {"tools.global.autoApprove": true}} sets nothing.
Up to VS Code 1.136.1 the nested form also skipped the workspace scope and Restricted Mode filters, which checked only the top-level JSON key (CVE-2026-70334, CVE-2026-81376, fixed in 1.136.2). The statements above that rest on those filters therefore did not hold for a nested value on those releases: "application-scoped, ignored from a workspace file" and "restricted, ignored until the workspace is trusted". A finding whose value comes from the nested form says so. Severity follows the current release, as for the 1.134 edits downgrade.
Copilot Chat's debug prompt override
github.copilot.chat.debug.promptOverrideString holds YAML with a systemPrompt and per-tool toolDescriptions; github.copilot.chat.debug.promptOverrideFile names a YAML file of the same shape (#648). In the Copilot extension (extensions/copilot in microsoft/vscode), toolCallingLoop.ts passes both to applyConfiguredPromptOverrides, which replaces the built system prompt and the tool descriptions for every agent request. Both are registered in the extension's package.json with no scope (so WINDOW, applied from a workspace file) and without restricted; the extension declares no untrustedWorkspaces capability, so VS Code runs it only in a trusted workspace. Each key set is a warn. The inline systemPrompt and tool descriptions are also read by the instruction-content rules. The file form is read with URI.file, as a path on the machine rather than one resolved against the repository, so its content is not scanned. This is from the source, not measured in a running VS Code.
Keys fixed in VS Code 1.136.2
Three advisories fixed in 1.136.2 (2026-09-03) involved keys a workspace file could set (#649):
| Key | Advisory and fix | Now |
|---|---|---|
chat.remoteAgentHosts, chat.agentHost.localFilePermissions |
CVE-2026-78462; 4321a67 "agentHost: read remote AH from global settings only". The list was read with getValue, which includes workspace values, although the setting is APPLICATION-scoped |
read from user settings only, and restricted |
github.copilot.chat.workspace.prototypeAdoCodeSearchEndpointOverride (and the legacy ...advanced... key) |
CVE-2026-81381; f94e10b removed the setting. Copilot sent the Azure DevOps token to the configured endpoint | gone |
terminal.integrated.allowInUntrustedWorkspace in nested-object form |
CVE-2026-70334's own example: the nested form skipped the Restricted Mode filter | ignored until trust |
Each is a warn that says the value is inert on 1.136.2+ and live on 1.136.1 and earlier and on forks. The flat allowInUntrustedWorkspace is not reported: the key is restricted, so a workspace value is ignored in exactly the untrusted workspace it would matter in.
Trigger example
{
// dev convenience
"chat.defaultConfiguration": {
"approvals": "allowAll"
}
}
→ error: every new chat session runs tool calls without asking, for anyone who opens the repo.
{
"chat.tools.global.autoApprove": true
}
→ warn: the same intent through an application-scoped key, which upstream ignores from a committed workspace file but a fork may not.
Safe alternative
Remove the blanket setting and approve tools interactively, or scope auto-approval to specific safe commands with the terminal allow-list:
{
"chat.tools.terminal.autoApprove": {
"npm run test": true,
"/^git (status|diff|log)/": true
}
}
References
- VS Code — Use tools with agents (auto-approve, terminal allow & deny lists)
- 4sysops — VS Code Copilot command allow/deny lists
- CFG001 (
defaultMode: bypassPermissions), CFG002 / CFG006 (allow/deny breadth) — related permission rules · CFG047 (.vscode/tasks.jsonauto-run)
The hook settings
chat.useClaudeHooks defaults to false and its description is "Controls whether hooks from Claude configuration files can execute. When disabled, only Copilot-format hooks are used." DEFAULT_HOOK_FILE_PATHS already contains .claude/settings.json and .claude/settings.local.json, so a committed true switches on execution of hook commands the same repository ships. Only true is reported; false is the default.
chat.hookFilesLocations maps a path to whether hook files are read from it: "Specify paths to hook configuration files that define custom shell commands to execute at strategic points in an agent's workflow ... Relative paths are resolved from the root folder(s) of your workspace." A path from DEFAULT_HOOK_FILE_PATHS (.github/hooks, .claude/settings.local.json, .claude/settings.json, ~/.copilot/hooks, ~/.claude/settings.json) registers nothing new and is not reported, and disabling one is the narrowing direction. A path outside that set is, and the finding says the honest second half: cfgaudit reads the standard hook paths and does not follow a registered one, so the files there are unreviewed by the scan.
The sibling location keys (chat.instructionsFilesLocations, chat.promptFilesLocations, chat.agentFilesLocations, chat.agentSkillsLocations, chat.modeFilesLocations) are the same relocation shape for instruction content rather than commands. They are not reported: registering a documented location is ordinary project configuration (718 committed files use the instructions one), so the fix there is for cfgaudit to follow the registered paths and scan what it finds, which is discovery work rather than a finding (#526).
Documentation for cfgaudit 1.18.1. Source of this page · Rule implementation