Every time you click “Always Allow” in GitHub Copilot or Claude Code, a permission is written somewhere in your VS Code configuration. Terminal commands, URLs, MCP server tool calls - they accumulate across sessions, across projects, and across the sqlite3 database VS Code uses internally for agent state. There’s no built-in way to see all of them at once, and no built-in way to audit what risk level they represent.
Copilot Approval Guard is a VS Code extension I built to fix that. It’s audit-only by design: it never adds approvals, only shows what exists and lets you remove them.
The Problem: Invisible Permission Accumulation
AI agent tools make it very convenient to grant permanent permissions. When Copilot asks “allow npm run build always?”, clicking yes is faster than reading the command. When you do it dozens of times across weeks of development, you end up with a list of auto-approved commands you’ve never reviewed as a set.
Some of those are harmless (git status, npm test). Some are not. git push --force and rm -rf ./dist are qualitatively different from read-only commands - but they end up in the same list, approved with the same one-click gesture.
The problem compounds with MCP servers. VS Code stores MCP tool approvals in its internal state.vscdb SQLite database, not in any settings file you’d normally look at. There’s no settings UI for them. Without tooling, you’d need to know the database schema, find the right path, and run SQL queries manually.
What the Extension Shows
The extension adds a panel to the VS Code activity bar with three sections:
Terminal Commands - everything in chat.tools.terminal.autoApprove, read from both User and Workspace config scopes. Each entry is classified by risk:
- HIGH (red):
rm -rf,curl … | sh,git push --force,git reset --hard,eval(, SQLDROP/TRUNCATE, world-writablechmod - MEDIUM (yellow):
sudo,git commit --amend,npm publish, deleting remote refs - LOW (blue):
git pushand similar write operations - SAFE (green): everything else
URL Approvals - entries from chat.tools.urls.autoApprove. Displayed without risk classification since URLs are context-dependent, but surfaced so you can spot any that look unfamiliar.
MCP Server Tools - the technically interesting part. The extension queries state.vscdb directly using the sqlite3 CLI to read VS Code’s internal agent state: which MCP servers are enabled, which individual tools within each server have auto-approval, and whether that approval was granted pre-call or post-call. It resolves VS Code’s internal truncated tool key format back to human-readable server names.
On startup, if any HIGH risk terminal commands are detected, a warning notification fires immediately with a “Review in Panel” button.
How It Works Under the Hood
The entire extension is a single TypeScript file (~750 lines). The architecture is straightforward:
Risk classification is a priority-ordered array of regex rules, each with a level and a human-readable reason. assessRisk(value) iterates and returns the first match:
const RISK_PATTERNS: RiskPattern[] = [
{ pattern: /rm\s+-rf/, level: 'high', reason: 'Recursive force delete' },
{ pattern: /curl[^|]+\|\s*sh/, level: 'high', reason: 'Pipe curl to shell' },
{ pattern: /git\s+push\s+--force/, level: 'high', reason: 'Force push' },
{ pattern: /sudo\s/, level: 'medium', reason: 'Elevated privileges' },
{ pattern: /git\s+push\b/, level: 'low', reason: 'Remote write' },
// ...
];
MCP database access derives the state.vscdb path from the extension’s own globalStorageUri - VS Code places each extension’s storage under User/globalStorage/{extensionId}/, and the shared state database lives one level up. That parent directory contains state.vscdb:
const dbPath = path.join(
path.dirname(context.globalStorageUri.fsPath),
'state.vscdb'
);
Reads and writes use execFileSync('sqlite3', [dbPath, sqlQuery]). VS Code bundles sqlite3 on macOS; Linux users need apt install sqlite3. The queries read JSON blobs from the ItemTable under three keys: chat/selectedTools, chat/autoconfirm, and chat/autoconfirm-post.
Tree rendering is a lazy TreeDataProvider. Each call to getChildren reads the current VS Code config and database state, so the panel always reflects live state without explicit polling.
Removal for settings-backed entries uses the VS Code workspace API. For database-backed MCP entries, it deserialises the JSON blob, removes the target key, re-serialises, and writes back via a SQL UPDATE. After a database write, the extension prompts for a window reload - VS Code caches the database state in memory.
The Design Decision: Read-Only API
The extension deliberately cannot add approvals. This is an explicit design constraint, not a missing feature. The use case is auditing and remediation, not configuration. Adding an “approve command” button would make it a tool for expanding your attack surface rather than reducing it.
The same constraint applies to bulk operations: you can remove all entries in a scope with one click, but you can’t import or restore a previous set. If you remove something you wanted to keep, re-approve it by running the command in the agent and clicking “Always Allow” again.
Installation and Usage
Install directly from the VS Code Marketplace - search for Copilot Approval Guard in the Extensions panel or run:
code --install-extension pettll.copilot-approval-guard
Or install from source:
git clone https://github.com/pettll/copilot-approval-guard
cd copilot-approval-guard
npm install
npm run compile
Then in VS Code: Extensions: Install from VSIX... after packaging with vsce package, or press F5 in the cloned repo to run it in a VS Code Extension Development Host.
Once active, the shield icon appears in the activity bar. The startup scan runs immediately - if you have HIGH risk approvals, you’ll see a notification within seconds of VS Code loading.
Why This Matters Now
AI coding agents are fast becoming standard tooling. The “always allow” convenience is real - interrupting your flow to approve every git status would be unbearable. But the accumulation problem is also real. Most developers who’ve been using Copilot or Claude Code for more than a few months have a list of auto-approved commands they’ve never reviewed holistically.
The risk isn’t necessarily an external attacker. It’s a confused agent, a malformed prompt injection in a file you opened, or a future model update that interprets a previously benign approval more aggressively. A git push --force approval granted months ago during a specific context might be exercised in a very different context later.
Copilot Approval Guard is a small tool, but it addresses a gap that will matter more as agents do more. The source is on GitHub - feedback and PRs welcome.