Skip to content

feat: discover MCP servers in VS code extensions - #504

Merged
hemang-snyk merged 3 commits into
mainfrom
feat/vscode-extension-mcp-providers
Oct 5, 2026
Merged

hemang-snyk merged 3 commits into
mainfrom
feat/vscode-extension-mcp-providers

Conversation

@hemang-snyk

@hemang-snyk hemang-snyk commented Oct 2, 2026 •

Copy link
Copy Markdown
Contributor

Note

Medium Risk
New filesystem scanning of extension bundles and JS parsing on MCP discovery; bounded for safety but expands attack surface under --scan-all-users, while fork gating limits false positives on non-VS Code hosts.

Overview
Adds discovery for MCP servers that VS Code extensions register in code via registerMcpServerDefinitionProvider (no mcp.json), such as Pylance’s HTTP provider.

A new extension_mcp module statically scans extension JS for McpStdioServerDefinition / McpHttpServerDefinition constructor calls, gated on contributes.mcpServerDefinitionProviders in package.json. Extraction is best-effort (literal args only; dynamic values are skipped or left as template holes) and includes caps on file size, walk depth, and scan windows to avoid hostile extension trees under multi-user scans.

VSCodeFamilyDiscoverer adds _discover_extension_provider_mcp_servers() (with duplicate-name suffixing) behind _extension_mcp_providers_enabled, default off so forks that ship the same extension bytes but stub the API don’t get false positives. VS Code opts in; Cursor documents and keeps the flag off.

Unit and discovery tests cover Pylance-like HTTP URLs, stdio configs, manifest gating, install manifest filtering, and fork opt-in behavior.

Reviewed by Cursor Bugbot for commit b94f9fb. Bugbot is set up for automated code reviews on this repo. Configure here.

hemang-snyk and others added 2 commits October 2, 2026 15:35
An extension can register MCP servers by calling
`vscode.lm.registerMcpServerDefinitionProvider` instead of shipping an
`mcp.json`. There is no config file at all in that case, so the server is
invisible to the existing extension walk while being just as live in the
editor. Pylance ships one this way.

Discovery is gated on `contributes.mcpServerDefinitionProviders` in the
extension's package.json: VS Code refuses to register an undeclared
provider, so an extension without the key cannot have one, and the
expensive step is skipped for all but the few that do. For those, the
`main` entry point and then the rest of the extension tree are scanned
for `McpStdioServerDefinition` / `McpHttpServerDefinition` constructor
calls, whose arguments are evaluated statically — strings, template
literals, arrays, object literals, `Uri.parse(...)`. Results are keyed by
the JS file and routed through `_validate_servers`, so they get the same
validation and binary-signature checks as file-based configs.

Extraction deliberately under-reports. A computed command or URI drops
the definition rather than guessing; a partially-computed args array
drops the whole argv rather than reporting a half-real one. Template
holes are kept verbatim (`http://localhost:${n}/stream`) because
Pylance picks the port at activation and any concrete value would be
fiction.

Scan cost is bounded at three levels, since extension trees are
attacker-influenceable under `--scan-all-users`: a byte prefilter before
decoding (36 MiB of Pylance bundles, 0.62 MiB actually scanned), an
explicit limit on every source-scanning helper so no single call can
outrun its window, and a per-file cap on definitions processed. Without
the latter two a bundle repeating the class name with a construct that
never closes cost one full-file scan per occurrence — 46s at 5 MiB,
growing quadratically.

Applies family-wide (Cursor, Windsurf, Kiro, Antigravity); the manifest
gate makes it a no-op on forks whose extensions don't use the API.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The provider scan landed enabled family-wide on the reasoning that the
`contributes.mcpServerDefinitionProviders` gate makes it self-limiting.
That reasoning covered cost, not correctness, and it breaks the
convention in `vscode.py` ("VSCode/Copilot-specific features, not
assumed for forks") for the four flags beside it.

Forks install the same extension artifacts as VS Code, so the manifest
key and the constructor call sit on disk identically whether or not the
fork's extension host implements the API. Reading them is therefore
always possible; concluding a server is live is not.

Cursor is a confirmed negative, not merely unverified. It tracks VS Code
1.128.0 — well past the 1.101 that added the API — and still stubs the
registration out in its extension host:

  registerMcpServerDefinitionProvider: () => (
    warn("registerMcpServerDefinitionProvider is not supported in Cursor"), noop
  )

An extension declaring a provider with a literal command, installed in
Cursor, would have been reported as a live MCP server that Cursor never
registers. This machine escaped it only by luck: the one declaring
extension in its Cursor tree is Snyk's own, whose command is computed
and so dropped for lack of a static identifying argument.

`_extension_mcp_providers_enabled` now defaults off, is on only in
`vscode.py` (Pylance verified live), and is explicitly off in
`cursor.py` with the stub quoted — a verified negative is different
information from an unchecked default. Windsurf, Kiro and Antigravity
inherit off pending a check of each extension host. A drift guard
asserts the split so opting a fork in stays a deliberate edit.

Noted for follow-up: Cursor exposes its own
`registerMcpConfigurationProvider`, a separate contribution point this
scan does not cover.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@hemang-snyk
hemang-snyk requested review from a team and marcelosousa as code owners October 2, 2026 14:07
@qodo-merge-etso

Copy link
Copy Markdown

PR Summary by Qodo

Discover MCP servers registered by VS Code extensions

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Discover MCP servers registered in extension code when no mcp.json exists.
• Limit scanning to installed, declared providers and verified VS Code hosts to avoid false
 positives.
• Reuse server validation and test static extraction, fork gating, and bounded scans.
Diagram

graph TD
  A["VS Code scan"] --> B["Installed extensions"] --> C{"Provider declared?"} -->|yes| D["JS bundles"] --> E["Static extraction"] --> F["Shared validation"] --> G["MCP inventory"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Parse bundles with a JavaScript AST library
  • ➕ More reliable handling of JavaScript syntax and constructor arguments.
  • ➖ Adds a parser dependency and parsing cost for large, minified extension bundles.
  • ➖ Still cannot resolve values computed at extension activation.
2. Inspect providers through a running VS Code extension host
  • ➕ Could observe registered servers and runtime-computed values directly.
  • ➖ Requires an active editor and extension activation, rather than offline discovery.
  • ➖ Introduces editor coupling and execution of extension code.

Recommendation: Keep the manifest-gated, bounded static scan for offline inventory: it covers code-only providers without activating extensions and reuses existing validation. Its deliberate under-reporting is preferable to guessing runtime values; an AST parser is worth reconsidering only if missed syntax becomes common.

Files changed (6) +1244 / -0

Enhancement (2) +617 / -0
base.pyIntegrate code-registered extension servers into MCP discovery +83/-0

Integrate code-registered extension servers into MCP discovery

• Adds an opt-in provider-discovery pass for installed extensions with provider declarations. Scans candidate JavaScript files, deduplicates server names, and sends extracted definitions through shared validation.

src/agent_scan/agents/vscode/base.py

extension_mcp.pyAdd bounded static extraction of extension MCP definitions +534/-0

Add bounded static extraction of extension MCP definitions

• Reads provider declarations, enumerates extension JavaScript bundles, and extracts resolvable stdio and HTTP constructor arguments. Applies file, walk, and call-scan limits while leaving runtime-computed values unresolved.

src/agent_scan/agents/vscode/extension_mcp.py

Tests (2) +605 / -0
test_agent_discovery.pyTest end-to-end extension provider discovery and host gating +237/-0

Test end-to-end extension provider discovery and host gating

• Covers HTTP and stdio discovery, chunked bundles, installation and declaration gates, dynamic definitions, duplicate names, and disabled scanning in Cursor and other forks.

tests/unit/test_agent_discovery.py

test_vscode_extension_mcp.pyTest JavaScript extraction and scan limits +368/-0

Test JavaScript extraction and scan limits

• Exercises manifest and bundle selection, literal evaluation, constructor parsing, unresolved values, malformed input, and bounded behavior on oversized or hostile bundles.

tests/unit/test_vscode_extension_mcp.py

Other (2) +22 / -0
cursor.pyExplicitly disable provider scanning for Cursor +17/-0

Explicitly disable provider scanning for Cursor

• Documents that Cursor's extension host does not implement the VS Code registration API and keeps code-provider discovery disabled to prevent false positives.

src/agent_scan/agents/vscode/cursor.py

vscode.pyEnable extension provider discovery for VS Code +5/-0

Enable extension provider discovery for VS Code

• Opts VS Code into the provider scan based on its supported extension-host API and a verified Pylance provider example.

src/agent_scan/agents/vscode/vscode.py

@qodo-merge-etso

qodo-merge-etso Bot commented Oct 2, 2026 •

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (6) 📘 Rule violations (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Comments can invent extension servers 🐞 Bug ≡ Correctness
Description
servers_in_js_file runs its constructor-name regex over the entire source without excluding
comments or string literals. In a declared extension, text such as `//
McpStdioServerDefinition("example", "node")` is parsed and validated as a server despite
constructing nothing.
Code

src/agent_scan/agents/vscode/extension_mcp.py[R173-177]

+    for seen, match in enumerate(_DEFINITION_RE.finditer(text)):
+        if seen >= _MAX_DEFINITIONS_PER_FILE:
+            logger.warning("Stopping after %d MCP definition calls in %s", _MAX_DEFINITIONS_PER_FILE, path.as_posix())
+            break
+        args = _split_call_args(text, match.end() - 1)
Relevance

●●● Strong

Regex-only scanning can parse commented constructor text as live definitions, causing deterministic
false positives.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The regex searches decoded source directly, and each match is passed to the argument parser and
server builder. Comment handling only occurs inside argument parsing, after a match has already been
accepted; the discovery loop validates the resulting entries.

src/agent_scan/agents/vscode/extension_mcp.py[55-57]
src/agent_scan/agents/vscode/extension_mcp.py[169-183]
src/agent_scan/agents/vscode/base.py[900-910]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Constructor-shaped text inside comments and strings becomes a reported MCP server.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[55-57]
- src/agent_scan/agents/vscode/extension_mcp.py[169-183]
## Recommended Fix
Tokenize enough JavaScript to exclude comments and string literals before accepting constructor matches, and test both cases through provider discovery.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


2. Extension links scan unrelated files 🐞 Bug ⛨ Security
Description
extension_js_files checks paths lexically, but its file checks and the subsequent read follow
symlinks. A declared extension can point its main bundle or another JavaScript file outside its
directory, causing unrelated contents to be scanned and potentially reported as its servers.
Code

src/agent_scan/agents/vscode/extension_mcp.py[R131-135]

+        candidate = Path(os.path.normpath(extension_dir / main))
+        if candidate.is_relative_to(extension_dir):
+            try:
+                if candidate.is_file():
+                    found.append(candidate)
Relevance

●●● Strong

Symlink traversal can expose unrelated files; prior security feedback specifically targeted symlink
escape.

PR-#381

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The main-file check does not resolve its target, and walked JavaScript paths are appended without a
symlink check. The reader then uses stat() and read_bytes(), which follow a file symlink; the
new discovery loop scans every returned path.

src/agent_scan/agents/vscode/extension_mcp.py[126-154]
src/agent_scan/agents/vscode/extension_mcp.py[187-204]
src/agent_scan/agents/vscode/base.py[900-910]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Symlinked extension files can redirect provider scanning outside the installed extension directory.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[126-154]
- src/agent_scan/agents/vscode/extension_mcp.py[187-204]
- src/agent_scan/agents/vscode/base.py[900-905]
## Recommended Fix
Reject symlink escapes for both manifest-selected and walked files, including a symlinked extension root. Verify containment against resolved paths at read time, and add tests for each route.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗



Remediation recommended

3. File-based addresses lose their scheme 🐞 Bug ≡ Correctness
Description
_unwrap_uri extracts the argument of Uri.file() exactly as it does for Uri.parse(), without
converting the path to the URI the factory produces. When an HTTP definition uses
Uri.file("relative.sock"), the reported URL is relative.sock rather than a file URI, and the
remote-server model accepts that string.
Code

src/agent_scan/agents/vscode/extension_mcp.py[R253-257]

+    match = _URI_FACTORY_RE.match(stripped)
+    if match is None:
+        return stripped
+    inner = _split_call_args(stripped, match.end() - 1)
+    return inner[0] if inner else stripped
Relevance

●●● Strong

Uri.file and Uri.parse have different semantics; preserving the path loses the required file URI
scheme.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The factory regex admits both methods, but _unwrap_uri returns the unmodified inner argument for
either one. _server_from_args places that string in url, which RemoteServer validates as a
string without URI conversion.

src/agent_scan/agents/vscode/extension_mcp.py[78-80]
src/agent_scan/agents/vscode/extension_mcp.py[232-257]
src/agent_scan/models/mcp.py[83-99]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`Uri.file()` paths are emitted as URLs without the factory's URI conversion.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[78-80]
- src/agent_scan/agents/vscode/extension_mcp.py[244-257]
## Recommended Fix
Distinguish `Uri.file()` from `Uri.parse()`; resolve its URI correctly where possible, or leave that definition unresolved rather than emitting the path as a URL.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


4. Undeclared providers appear as live 🐞 Bug ≡ Correctness
Description
provider_declarations accepts a top-level mcpServerDefinitionProviders key even though the
registration gate is the key under contributes. An extension with only that top-level key passes
discovery, so matching JavaScript definitions reach the results although VS Code does not recognize
its declaration.
Code

src/agent_scan/agents/vscode/extension_mcp.py[R106-111]

+    for container in (manifest.get("contributes"), manifest):
+        if not isinstance(container, dict):
+            continue
+        declared = container.get(_PROVIDERS_KEY)
+        if isinstance(declared, list):
+            return [entry for entry in declared if isinstance(entry, dict)]
Relevance

●●● Strong

Top-level fallback contradicts the documented VS Code gate and can produce false-positive servers.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new function explicitly falls back to the manifest root. The discovery caller treats any
returned entry as permission to scan, while its stated gate is
contributes.mcpServerDefinitionProviders.

src/agent_scan/agents/vscode/extension_mcp.py[98-112]
src/agent_scan/agents/vscode/base.py[880-883]
src/agent_scan/agents/vscode/base.py[897-910]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
A top-level manifest key bypasses the declaration gate and yields servers VS Code cannot register.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[98-112]
- tests/unit/test_vscode_extension_mcp.py[38-40]
## Recommended Fix
Read provider declarations only from `manifest["contributes"]`, and change the top-level-key test to assert that it does not enable discovery.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗



Informational

5. Numeric environment values change 🐞 Bug ≡ Correctness
Description
_js_string_object copies a numeric literal's source spelling instead of converting its JavaScript
value to a string. A stdio definition containing {PORT: 1.0} is inventoried with PORT set to
"1.0", although that numeric value stringifies to "1"; numeric object keys are likewise copied
unchanged.
Code

src/agent_scan/agents/vscode/extension_mcp.py[R330-334]

+        if value is None:
+            if not _NUMBER_RE.fullmatch(value_stripped):
+                return None
+            value = value_stripped
+        result[key] = value
Relevance

●●● Strong

JavaScript numeric stringification differs from source spelling, producing incorrect environment
values and keys.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The object evaluator assigns value_stripped directly after matching a number, then the stdio
builder includes the resulting map as env. The numeric-key path also returns the literal text
unchanged.

src/agent_scan/agents/vscode/extension_mcp.py[224-230]
src/agent_scan/agents/vscode/extension_mcp.py[326-351]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Numeric literal spelling is mistaken for its stringified JavaScript value in extracted environment maps.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[303-351]
## Recommended Fix
Convert supported numeric literals according to their evaluated value before emitting values or property keys; leave unsupported numeric forms unresolved, and test `1.0`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


6. Continued strings gain a newline 🐞 Bug ≡ Correctness
Description
_unescape recognizes a backslash followed by LF as a continuation but does not handle a backslash
followed by CRLF. In a JavaScript string such as "a\ followed by CRLF and b", extraction retains
the newline in a command, argument, or URL where the evaluated string contains only ab.
Code

src/agent_scan/agents/vscode/extension_mcp.py[R380-384]

+                i += consumed
+                continue
+        out.append(nxt)
+        i += 2
+    return "".join(out)
Relevance

●●● Strong

CRLF line continuations are a deterministic JavaScript escape case currently decoded incorrectly.

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
_js_string delegates accepted literals to _unescape. Its escape table handles a bare LF but not
CRLF, so the fallback retains CR and the next iteration retains LF.

src/agent_scan/agents/vscode/extension_mcp.py[82-95]
src/agent_scan/agents/vscode/extension_mcp.py[263-278]
src/agent_scan/agents/vscode/extension_mcp.py[354-384]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
CRLF line continuations are retained in extracted JavaScript string values.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[82-95]
- src/agent_scan/agents/vscode/extension_mcp.py[354-384]
## Recommended Fix
Consume backslash-CRLF and backslash-CR as complete line continuations during unescaping, and add tests for their use in server arguments.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗


Grey Divider

Context sources
✅ Compliance rules (platform): 8 rules
✅ Cross-repo context — repo relationships
  Explored: repo: snyk/invariant-mcp-scan-backend (sha: 01c12df1) — View relationship
  Explored: repo: snyk/invariant-platform (sha: ec9eba56) — View relationship
Review mode: ⚖️ Balanced: Downgraded extended -> standard: change is below the extended eligibility bar (hunks 9/18, lines 1244/200; both must reach the floor). Router rationale: This adds substantial, security- and correctness-sensitive static parsing logic across multiple independent paths, with many edge cases and a broad extension-discovery blast radius that benefits from redundant review passes.

Grey Divider

Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history

More tips ↗ | Customize Qodo ↗ | Qodo docs ↗

Grey Divider

Qodo Logo

Comment on lines +131 to +135
candidate = Path(os.path.normpath(extension_dir / main))
if candidate.is_relative_to(extension_dir):
try:
if candidate.is_file():
found.append(candidate)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Extension links scan unrelated files 🐞 Bug ⛨ Security

extension_js_files checks paths lexically, but its file checks and the subsequent read follow
symlinks. A declared extension can point its main bundle or another JavaScript file outside its
directory, causing unrelated contents to be scanned and potentially reported as its servers.
Agent Prompt
## Issue description
Symlinked extension files can redirect provider scanning outside the installed extension directory.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[126-154]
- src/agent_scan/agents/vscode/extension_mcp.py[187-204]
- src/agent_scan/agents/vscode/base.py[900-905]
## Recommended Fix
Reject symlink escapes for both manifest-selected and walked files, including a symlinked extension root. Verify containment against resolved paths at read time, and add tests for each route.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment on lines +173 to +177
for seen, match in enumerate(_DEFINITION_RE.finditer(text)):
if seen >= _MAX_DEFINITIONS_PER_FILE:
logger.warning("Stopping after %d MCP definition calls in %s", _MAX_DEFINITIONS_PER_FILE, path.as_posix())
break
args = _split_call_args(text, match.end() - 1)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Comments can invent extension servers 🐞 Bug ≡ Correctness

servers_in_js_file runs its constructor-name regex over the entire source without excluding
comments or string literals. In a declared extension, text such as `//
McpStdioServerDefinition("example", "node")` is parsed and validated as a server despite
constructing nothing.
Agent Prompt
## Issue description
Constructor-shaped text inside comments and strings becomes a reported MCP server.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[55-57]
- src/agent_scan/agents/vscode/extension_mcp.py[169-183]
## Recommended Fix
Tokenize enough JavaScript to exclude comments and string literals before accepting constructor matches, and test both cases through provider discovery.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment on lines +106 to +111
for container in (manifest.get("contributes"), manifest):
if not isinstance(container, dict):
continue
declared = container.get(_PROVIDERS_KEY)
if isinstance(declared, list):
return [entry for entry in declared if isinstance(entry, dict)]

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Undeclared providers appear as live 🐞 Bug ≡ Correctness

provider_declarations accepts a top-level mcpServerDefinitionProviders key even though the
registration gate is the key under contributes. An extension with only that top-level key passes
discovery, so matching JavaScript definitions reach the results although VS Code does not recognize
its declaration.
Agent Prompt
## Issue description
A top-level manifest key bypasses the declaration gate and yields servers VS Code cannot register.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[98-112]
- tests/unit/test_vscode_extension_mcp.py[38-40]
## Recommended Fix
Read provider declarations only from `manifest["contributes"]`, and change the top-level-key test to assert that it does not enable discovery.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment on lines +253 to +257
match = _URI_FACTORY_RE.match(stripped)
if match is None:
return stripped
inner = _split_call_args(stripped, match.end() - 1)
return inner[0] if inner else stripped

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. File-based addresses lose their scheme 🐞 Bug ≡ Correctness

_unwrap_uri extracts the argument of Uri.file() exactly as it does for Uri.parse(), without
converting the path to the URI the factory produces. When an HTTP definition uses
Uri.file("relative.sock"), the reported URL is relative.sock rather than a file URI, and the
remote-server model accepts that string.
Agent Prompt
## Issue description
`Uri.file()` paths are emitted as URLs without the factory's URI conversion.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[78-80]
- src/agent_scan/agents/vscode/extension_mcp.py[244-257]
## Recommended Fix
Distinguish `Uri.file()` from `Uri.parse()`; resolve its URI correctly where possible, or leave that definition unresolved rather than emitting the path as a URL.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment on lines +330 to +334
if value is None:
if not _NUMBER_RE.fullmatch(value_stripped):
return None
value = value_stripped
result[key] = value

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

5. Numeric environment values change 🐞 Bug ≡ Correctness

_js_string_object copies a numeric literal's source spelling instead of converting its JavaScript
value to a string. A stdio definition containing {PORT: 1.0} is inventoried with PORT set to
"1.0", although that numeric value stringifies to "1"; numeric object keys are likewise copied
unchanged.
Agent Prompt
## Issue description
Numeric literal spelling is mistaken for its stringified JavaScript value in extracted environment maps.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[303-351]
## Recommended Fix
Convert supported numeric literals according to their evaluated value before emitting values or property keys; leave unsupported numeric forms unresolved, and test `1.0`.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

Comment on lines +380 to +384
i += consumed
continue
out.append(nxt)
i += 2
return "".join(out)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

6. Continued strings gain a newline 🐞 Bug ≡ Correctness

_unescape recognizes a backslash followed by LF as a continuation but does not handle a backslash
followed by CRLF. In a JavaScript string such as "a\ followed by CRLF and b", extraction retains
the newline in a command, argument, or URL where the evaluated string contains only ab.
Agent Prompt
## Issue description
CRLF line continuations are retained in extracted JavaScript string values.
## Fix Focus Areas
- src/agent_scan/agents/vscode/extension_mcp.py[82-95]
- src/agent_scan/agents/vscode/extension_mcp.py[354-384]
## Recommended Fix
Consume backslash-CRLF and backslash-CR as complete line continuations during unescaping, and add tests for their use in server arguments.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Dismiss ↗ | View ↗

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b94f9fb. Configure here.

return found
except (PermissionError, OSError, ValueError):
logger.warning("Skipping unreadable extension tree %s", extension_dir.as_posix())
return found

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Special files can hang discovery

High Severity

The JS walk adds every name ending in .js/.cjs/.mjs and _read_text reads it after a size stat only. Unlike the existing mcp.json walk, nothing checks is_file(). A FIFO or a symlink to a device (for example /dev/zero) in an attacker-influenceable extension tree under --scan-all-users blocks forever or grows without bound, stalling the whole discoverer.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b94f9fb. Configure here.

@hemang-snyk
hemang-snyk merged commit 69ce32c into main Oct 5, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants