Skip to content
Foo Guard

VS Code MCP Configuration Security Checks: What to Verify

Learn which VS Code MCP configuration security checks matter, how to write .fooguard.yml policies, and how to gate pull requests with static scans.

VS Code MCP Configuration Security Checks: What to Verify

You're reviewing a pull request that adds a shared .vscode/mcp.json. The local notes server works on its author's laptop, but its env block contains a literal API token. Every teammate who checks out the file receives that token, whether or not they ever approve the server.

Start by reviewing the complete entry before launching it. This synthetic configuration illustrates the problem; the token is fictional, and @example/mcp-demo is a synthetic package name, not something to install.

{
  "servers": {
    "synthetic-notes": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@example/mcp-demo"],
      "env": {
        "NOTES_TOKEN": "SYNTHETIC_NOT_A_REAL_TOKEN",
        "CLOUD_TOKEN": "${env:CLOUD_TOKEN}"
      }
    }
  }
}

The review task isn't just to validate JSON. It's to decide which executable, credentials, filesystem access, and network permissions your team is approving at this commit.

The Checks a VS Code MCP Configuration Should Pass

Run VS Code MCP configuration security checks against each server entry, then inspect the shared controls around those entries. A file can contain a narrowly scoped lookup server beside a shell-capable server with much broader access, and both deserve the same scrutiny at the commit that introduces them.

Nine checks for VS Code MCP server entries

Four of these checks correspond to named Foo Guard rules. The other five are general configuration review practices, and they still belong in your policy even without a named rule behind them.

CheckWhat the entry showsFoo Guard ruleUsual fix
Embedded credentialLiteral token, key, or Authorization value in env, args, or headersAG-004 Embedded CredentialReplace with a variable reference and rotate the value
Shell command executionCommand is a shell or interpreter evaluating inline codeAG-013 Shell or Command ExecutionCall a fixed binary with fixed arguments
Wildcard permissionTool or path grant uses a wildcardAG-008 Wildcard PermissionEnumerate exact tools and paths
Administrative permissionServer runs with admin or owner scopeAG-009 Administrative PermissionReduce to the scope the task needs
Unpinned package runnernpx, uvx, or similar resolves a floating versionGeneral review practicePin an exact version; pinning doesn't prove package safety
Broad filesystem pathHome directory, drive root, or whole workspace is reachableGeneral review practiceScope to a project subdirectory
Unrestricted networkNo domain list, or open outbound accessGeneral review practiceList exact service domains
Missing sandboxShell, file, or database server without sandboxEnabledGeneral review practiceEnable the sandbox and set narrow grants
Unreviewed remote endpointURL outside an approved list or registryGeneral review practiceRoute the server through an approved entry

A missing field needs interpretation, not an invented default. An absent network restriction is evidence that the configuration doesn't declare one; it isn't proof that the operating system permits every outbound connection. The same caution applies to sandbox flags and approval prompts: record what the file says, not what you hope the runtime defaults to.

Read findings as evidence

Each finding shows the rule, severity, configuration field, and evidence, followed by a suggested change. Read those fields together: an inline command in args means something different when command is bash than when the receiving program treats the argument as plain text.

Treat the suggested change as a review proposal. Replacing a shell invocation with a fixed binary reduces interpretation risk, but that binary still needs an owner, a reviewed source, and constrained access.

Review rule: Approve the effective execution path, not the server's friendly name.

These checks describe configuration text at a specific commit. They don't establish what the process did after startup; that is a question for runtime monitoring, not static review.

Why a Server Entry Is Executable Code

A server entry is a work order and a set of keys handed to a contractor who works under your badge. The declared task may be small; the authority you hand over may not be.

A construction worker and a software developer exchanging keys and a clipboard with VS Code imagery.

The VS Code MCP server documentation states that local servers can run code on the developer's machine and that their tools inherit the user's permissions. A local stdio entry therefore deserves executable-code review, including its working directory and interpolated values.

Read the synthetic entry as a chain

In the opening example, command: "npx" selects a package runner. Its arguments name an unpinned package, and -y skips the runner's installation confirmation; it doesn't replace or describe VS Code's trust decisions.

The env block then supplies both a literal notes credential and the host's cloud token. Read together, those fields authorize package-selected local code to receive credentials unrelated to a simple notes lookup. Checking each key independently would miss that combination.

Our scanner inspects MCP-style tool definitions before those servers run. A reviewer should likewise resolve the chain from launcher to package or script, then to credentials and reachable resources.

Keep trust and execution separate

Workspace MCP configurations inherit Workspace Trust. Servers from other sources can require a separate trust decision, and configuration changes can prompt again.

Keep a review inventory for each entry:

  • Executable: The resolved binary, package, or repository script.
  • Evaluation flags: Interpreter arguments that evaluate inline code.
  • Environment: Every explicitly passed variable and any inherited environment assumptions.
  • Reach: Working directories, interpolated paths, and resources available to the process.

A trusted workspace isn't a reason to stop inspecting that inventory. It's a boundary within which the execution still needs constraints.

Keep Credentials Out of Shared Configuration

A literal value and a variable reference solve different problems. The literal puts credential material in the file; the reference tells the client where to obtain a value when it runs.

For the same synthetic notes entry, compare these forms:

FormExample fieldMeaning
Literal credential"NOTES_TOKEN": "SYNTHETIC_NOT_A_REAL_TOKEN"The value travels with the file
Environment reference"NOTES_TOKEN": "${env:NOTES_TOKEN}"The client resolves a developer-specific environment value
Deployment input"NOTES_ENDPOINT": "${input:ENDPOINT}"The client resolves a separately declared input

The input form also needs a matching input declaration. It isn't a complete configuration merely because the placeholder looks plausible.

Remove secrets without hiding permissions

VS Code's version 1.99 release notes, for the release in March 2025, introduced built-in MCP support and variable substitution for credentials and deployment-specific values. Personal configuration files aren't secret stores either: don't commit tokens, passwords, or populated authentication headers.

A 2025 report summarizing the Queen's University study stated that 3.6% of the 1,899 analyzed MCP servers exposed credentials. That's a finding about that sample, not an exposure rate for your repository.

Check literal values in env, populated Authorization headers, and credentials embedded in URL query strings. Also investigate credential names that are declared but never referenced; they may indicate stale wiring, not necessarily a leaked value.

  1. Replace the literal with an appropriate reference.
  2. Rotate any credential that was committed.
  3. Remove it from Git history through your team's coordinated history-cleanup process.

AG-004 Embedded Credential is the matching rule name, with native-field applicability pending confirmation. Our severity levels include critical, high, medium, and low; AG-004 is among the critical rules.

A reference removes the secret from the shared file. It doesn't reduce the authority of the secret supplied at runtime.

Review where the environment value originates and who can read it. Don't pass a general cloud credential when a task-specific identity will do.

Least Privilege Through Sandbox Grants

Which filesystem and network reach does this server need for its declared task? Answer that before deciding whether its grants are acceptable.

The VS Code MCP configuration reference documents "sandboxEnabled": true and a sandbox object for filesystem restrictions, narrow write grants, and specified network domains on supported macOS and Linux setups. Check current platform documentation before assuming equivalent enforcement elsewhere.

A 2025 study summarized by DeepLearning.AI tested more than 280 MCP servers and found that 72% exposed at least one sensitive capability to attackers. That sample finding supports reviewing capability scope, not declaring any particular server unsafe.

Sandbox grants and review postures

GrantReview postureReason
Read-only access to project filesAllow by default for approved project scopeMatches most review and lookup tasks
Write access to a named build or temp directoryAllow with a stated reasonOutput paths stay predictable
Write access to the whole workspaceFlagThe process can change tracked files
Home directory accessFlagReaches SSH keys, browser profiles, and other tools' settings
Named service domainsAllow when listed and justifiedEach domain is visible to the reviewer
Unrestricted outbound networkFlagData can leave the machine without a declared destination boundary
Wildcard or pattern domain ruleFlagThe reachable scope is harder to establish
Shell-capable server without sandboxFlagThe command runs with the user's permissions

Read-only doesn't mean nonsensitive. Source files can contain private business logic, test credentials, or customer fixtures, so the approved project scope still matters.

Review the shared boundary

All servers defined in the same mcp.json file share that file's sandbox configuration. Adding a high-risk server can therefore widen the effective grants available to otherwise modest entries.

Start with read-only project access, allow writes only to named build or temporary directories, and enumerate required service domains. Don't invent per-server isolation where the client applies a file-level policy.

For workloads needing a separate execution environment, Beam's guide to isolated MCP servers for agent workloads provides useful context beyond local configuration review. Isolation adds operational work, but it can separate sensitive host resources from server execution.

The scanner should report shared grants as evidence. Reviewers should see which entries coexist inside that boundary, not receive an apparently isolated verdict for each server.

Write a .fooguard.yml Policy Beside the Config

Keep scan policy beside the native configuration and review both in the same pull request. A synthetic repository for a local notes server could have this layout:

repository/
  .vscode/
    mcp.json
  .fooguard.yml
  mcp/
    notes-server.js
  .cache/

The native server entry is:

{
  "servers": {
    "synthetic-notes": {
      "type": "stdio",
      "command": "node",
      "args": ["./mcp/notes-server.js"],
      "env": {
        "NOTES_TOKEN": "${env:NOTES_TOKEN}"
      }
    }
  }
}

This is a complete server-entry example, not a complete sandbox policy. Its intended sandbox is project-read access with writes limited to ./.cache; the actual sandbox keys must come from the current client reference rather than a guessed JSON structure.

Keep executable syntax separate from policy intent

Our policy-as-code support lets teams declare scan paths, fail conditions, and a minimum grade in .fooguard.yml. Team scans read that file at the commit SHA, so the reviewed policy produces the named security check.

Confirm the exact YAML schema in the current Foo Guard policy documentation before wiring the file into CI. Treat the following as a policy worksheet, not YAML to paste into a pipeline:

Policy itemSynthetic repository choice
Scan path.vscode/mcp.json
Fail conditionReject findings at the team's selected severity threshold
Minimum gradeRequire the organization's approved minimum
Sandbox reviewProject reads, writes limited to ./.cache
Policy revisionThe same commit as the configuration

The scan-path setting selects the file under review. The fail condition decides which findings block acceptance. The minimum grade adds an aggregate threshold, without replacing field-level evidence.

CLI flags can tighten a single run, but they can't undercut organization policy. Use the documented key names for each setting rather than inferring them from their labels. A plausible-looking key the parser ignores fails without any warning and gives the team a false sense of enforcement.

Preserve native client files

Use the native-format documentation to establish accepted paths, schemas, and parser coverage before enabling a required check. Don't assume Cursor, Claude Desktop, VS Code, and Continue use interchangeable fields, and don't rewrite their files into a different schema merely to make them look alike.

For each client, record its actual native configuration path, consult its current product documentation, and confirm that the scanner read that file. Verify coverage per client in the supported-formats reference rather than assuming every product's file is parsed.

Use our policy-as-code examples rather than maintaining a second, improvised syntax guide. This synthetic repository doesn't launch the notes server, fetch a package, or test whether a sandbox blocks access. A static pass proves the committed fields met policy at that revision; whether the server stays inside its declared boundary once running is a separate question for runtime monitoring.

Gate Pull Requests on the Reviewed Commit

Require a named check that reads the exact revision being considered for merge. A log message without a required-check rule doesn't enforce review.

The flow below shows the intended merge gate.

A five-step flowchart illustrating how to gate pull requests using specific security checks before merging code.

Wire the check into merge protection

  1. Add pull_request, default-branch push, and, for queued merges, merge_group triggers. GitHub's required-status-check troubleshooting guide requires merge-queue event coverage and evaluates required checks against the latest pull-request commit SHA.
  2. Check out the pull-request head for a head-revision scan. For a merge-group event, scan the merge-group revision instead; don't reuse pull-request-only checkout logic.
  3. Run our CLI against the policy's declared scan paths. It supports local scans and CI enforcement, but use documented installation and invocation syntax rather than an invented command.
  4. Preserve the exit code. Code 0 means policy passed, 1 means policy failed, and 2 means an operational or configuration error; both failing codes must fail the pipeline step.
  5. Publish a stable, named result. GitHub's status-check documentation distinguishes detailed Checks generated by Actions or Apps from simpler commit statuses.
  6. Require that exact check name through branch protection or a repository ruleset. Our integration supports required checks; repository administrators still need to select the intended check.
  7. Push a new commit and verify that the scanner reruns against it. Inspect the reported revision rather than trusting a familiar green badge.
  8. Investigate skipped jobs. GitHub accepts success, skipped, and neutral as successful states, so conditional execution must not bypass the security scan without anyone noticing.

Test the enforcement path

Use a synthetic policy violation to exercise the merge gate, then remove it. Confirm that a scanner error blocks merging too; unavailable analysis isn't a passing security decision.

For broader workflow design, security automation insights from PushOps provide useful context. Our guide to MCP security checks in GitHub pull requests covers the repository integration in more detail.

The accompanying walkthrough provides another view of the process.

Govern Server Approvals Across Teams

A teammate approves a source-control MCP server, then the shared configuration changes to a package version with broader permissions. The original consent still exists, but it does not identify which command, endpoint, and permission set was accepted.

The VS Code full-MCP-support article describes the authorization foundation. Use that consent boundary as an input to team governance: approval belongs to the reviewed execution path in a specific commit, and a material change needs a fresh decision.

Record what was approved

Keep an approval record that connects the configuration to its operational identity:

  • Approver: The person or accountable team accepting the entry.
  • Execution path: The exact command, arguments, package version, or remote endpoint.
  • Reviewed revision: The commit SHA containing the approved configuration and policy.
  • Credentials: The service identity, intended scope, and credential source, without storing secret values.
  • Runtime result: Whether supported requests routed through Gateway were permitted or blocked, under which policy, and what request history supports the outcome.

Workspace trust, server trust, extensions, and network domains remain distinct boundaries. A server approval should expire when the command, endpoint, package version, credential scope, or tool permissions change.

Connect intent to observed behavior

Static analysis identifies permissions, credentials, and approval settings that need attention without running the agent. Runtime verification needs evidence tied to the same identity and configuration revision.

Store the reviewed configuration in the Library so teams can reuse the approved version. Connect the server to Gateway for the separate runtime boundary: Gateway checks supported MCP requests routed through it, beginning with the connection and continuing through policy behavior and request history.

Approval should expire when the approved execution path changes.

The VS Code trust-and-safety guidance supports treating approval as a lifecycle concern: bind local services appropriately, isolate networks, use role-based access, and prefer short-lived, narrowly scoped credentials.

Do not claim visibility into requests that bypass the monitored route. An absence of blocked requests could mean permitted behavior, missing coverage, or no requests at all.

Decide What a Passing Scan Covers Before You Merge

A static finding comes from configuration text. Observed runtime behavior comes from running the server and watching its actions and requests.

Define the acceptance boundary

A passing scan covers only the files, parser coverage, and policy that were really applied:

  • Configuration fields: Commands, arguments, endpoints, and environment references successfully read.
  • Rule evaluation: Matching findings and thresholds for the selected policy.
  • Pins as written: Whether a package reference declares a fixed version.

It doesn't establish facts outside that evidence:

  • Package contents: A pinned version can still contain malicious or vulnerable code.
  • Runtime calls: Declared destinations aren't proof of actual outbound behavior.
  • Approval history: A passing result doesn't establish who authorized the server.
  • Platform enforcement: Sandbox settings aren't proof of enforcement on an untested platform.
  • Unread files: An omitted Cursor, Claude Desktop, VS Code, or Continue file remains outside the result.

Our rule packs group related checks around security goals; they don't certify compliance. A clean scan isn't a safety guarantee or framework certification.

Make the merge decision explicit

Open the synthetic native MCP scan example, then read each finding in order: rule, severity, field, evidence, and suggested change. Compare the output with the entry-level checklist, without assuming every checklist item maps to a confirmed native rule.

Use our MCP security best practices guide for the broader operating baseline. If findings match expectations, make the check required; if a finding is unexpected, fix the entry and rescan; if an expected finding is missing, inspect which file and fields the scan read.


Foo Guard offers pre-deployment configuration scanning and repository policy enforcement for reviewing MCP execution paths before merge. Visit Foo Guard to inspect a configuration, review evidence-backed findings, and connect supported routed requests to runtime controls where your workflow needs them.