Back to skill

Security audit

Cherry Mcp

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real MCP-to-HTTP bridge, but it exposes powerful tool calls and restarts without authentication and handles secrets too loosely.

Review before installing. Use this only on a tightly controlled machine, keep it bound to localhost or protected behind real authentication, restrict CORS, avoid exposing it to a network, pin and review all MCP server packages, and do not store API keys in config.json. Run it under a low-privilege account with minimal environment variables.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (5)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
bridge.js:143
Finding
Unauthenticated MCP Tool Invocation and Server Restart<![CDATA[ ## Vulnerability Details **File Location**: `bridge.js:143-146, 165-190` **Vulnerability Type**: Missing authentication and authorization on privileged HTTP endpoints **Risk Level**: High ### Vulnerable Code ```js async function handler(req, res) { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type'); if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } if (!checkSecurity(req, res)) return; const parts = req.url.split('/').filter(Boolean); try { if (!parts.length) { const list = {}; for (const [n, s] of servers) list[n] = s.status(); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ servers: list })); return; } const srv = servers.get(parts[0]); if (!srv) { res.writeHead(404); res.end(JSON.stringify({ error: 'Server not found' })); return; } const action = parts[1] || 'status'; if (action === 'status') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(srv.status())); } else if (action === 'tools') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ tools: srv.tools || [] })); } else if (action === 'call' && req.method === 'POST') { let body = ''; for await (const chunk of req) body += chunk; const { tool, arguments: args } = JSON.parse(body); audit(req, 'call', { server: parts[0], tool }); const result = await srv.callTool(tool, args || {}); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ result })); } else if (action === 'restart' && req.method === 'POST') { srv.stop(); srv.restarts = 0; setTimeout(() => srv.start(), 500); res.writeHead(200, { 'Content-Type': 'application/json' }); ...[truncated 2245 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Require authentication for every endpoint, using a cryptographically strong bearer token, mutual TLS, or a protected local IPC mechanism. - Apply per-server and per-tool authorization instead of granting every authenticated caller access to all MCP capabilities. - Restrict CORS to an explicit list of trusted origins. Disable CORS entirely if browser access is unnecessary. - Reject non-loopback binding unless authentication has been explicitly enabled. - Protect restart and other administrative operations with a separate administrative permission. - Consider using a Unix domain socket with restrictive filesystem permissions for local-only deployments. - Return minimal server and tool metadata to unauthenticated callers. - Add security tests confirming that unauthenticated tool calls, enumeration, and restart operations are rejected. ]]>

T09 · Insecure Skill Coding Practices

Error
Location
bridge.js:177
Finding
Unbounded HTTP Request Body Permits Memory Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `bridge.js:177-180` **Vulnerability Type**: Unbounded request buffering and denial of service **Risk Level**: High ### Vulnerable Code ```js } else if (action === 'call' && req.method === 'POST') { let body = ''; for await (const chunk of req) body += chunk; const { tool, arguments: args } = JSON.parse(body); audit(req, 'call', { server: parts[0], tool }); const result = await srv.callTool(tool, args || {}); ``` The documentation claims a limit that the implementation does not enforce: ```md - 1MB max payload ``` ### Technical Analysis The handler appends every incoming chunk to an in-memory string until the client finishes sending the request. It does not validate `Content-Length`, count received bytes, enforce the documented 1 MB limit, or establish a request-body timeout. String concatenation and JSON parsing can require multiple copies of the supplied data in memory. A single large request can therefore consume substantial memory, while concurrent requests can amplify the effect and exhaust the Node.js heap. Rate limiting does not adequately mitigate this issue because a permitted request can itself be arbitrarily large and slow. ### Attack Path 1. The attacker identifies a configured server name through `GET /` or prior knowledge. 2. The attacker opens one or more connections to `POST /<server>/call`. 3. The attacker sends a very large body or continuously streams body data without completing the request. 4. The bridge accumulates every chunk in the `body` string. 5. Memory consumption grows until the process becomes unresponsive, is terminated by the runtime, or affects other services on the host. ### Impact Assessment A reachable unauthenticated attacker can degrade or terminate the bridge and all MCP services accessed through it. Concurrent oversized or slow requests may also consume host memory and file descriptors, potentially affecting unrelated processes running under the s ...[truncated 44 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Count bytes while reading the request and immediately return HTTP 413 when the body exceeds 1 MB. - Destroy the request stream after rejecting an oversized payload so that additional data is not buffered. - Validate `Content-Length` when present, while still enforcing a streaming limit because the header cannot be trusted. - Configure request, header, and idle timeouts to prevent slow-body attacks. - Limit concurrent requests globally and per client. - Require `Content-Type: application/json` and validate the decoded object against a strict schema. - Handle malformed JSON with HTTP 400 rather than a generic HTTP 500 response. - Add automated tests for oversized, chunked, concurrent, and slow request bodies. ]]>

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
bridge.js:52
Finding
MCP Child Processes Inherit the Entire Parent Environment<![CDATA[ ## Vulnerability Details **File Location**: `bridge.js:52-55` **Vulnerability Type**: Excessive secret and environment-variable exposure to child processes **Risk Level**: Medium ### Vulnerable Code ```js this.process = spawn(command, args, { env: { ...process.env, ...env }, stdio: ['pipe', 'pipe', 'pipe'] }); ``` ### Technical Analysis Every configured MCP executable inherits all environment variables belonging to the bridge process. The per-server `env` object is merged into the complete parent environment rather than being used to construct a minimal environment. MCP servers may include third-party code obtained through package managers such as `npx`. If such a package is malicious or compromised, it can read credentials unrelated to its legitimate function, including cloud credentials, repository tokens, deployment secrets, and service configuration. This violates least privilege because each child receives access to every secret available to the bridge process, regardless of whether the child requires that secret. ### Attack Path 1. The bridge is started in an environment containing sensitive tokens or credentials. 2. A malicious or compromised MCP package is configured as a server. 3. The bridge starts the package using `spawn`. 4. The child receives a copy of the complete `process.env`. 5. The child reads unrelated environment variables and can use or disclose those credentials through any network or filesystem access available to it. ### Impact Assessment The child process can obtain all environment-based secrets held by the bridge account. Compromised credentials may enable access to external APIs, cloud infrastructure, source repositories, databases, or other services. The operating-system privilege remains that of the bridge process, but the exposed credentials can substantially expand the effective scope beyond the MCP server's intended function. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions - Build a minimal environment for each MCP server rather than copying `process.env`. - Allowlist only essential runtime values such as a controlled `PATH`, locale settings, and explicitly configured server credentials. - Remove cloud-provider, CI/CD, repository, and deployment credentials unless a particular server explicitly requires them. - Run each MCP server under a separate restricted operating-system account or isolated container where practical. - Apply filesystem and network restrictions appropriate to each MCP server. - Avoid executing unreviewed packages in the same security context as sensitive credentials. - Document the precise environment variables granted to each server and warn when broad inheritance is requested. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
cli.js:19
Finding
Secrets Are Persisted and Displayed in Plaintext<![CDATA[ ## Vulnerability Details **File Location**: `cli.js:19-22, 66-76, 114-116` **Vulnerability Type**: Insecure storage and disclosure of sensitive configuration **Risk Level**: Medium ### Vulnerable Code ```js function save(config) { fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n'); console.log('✓ Saved'); } ``` ```js 'set-env': (args) => { const [server, key, value] = args; if (!server || !key || !value) { console.log('Usage: set-env <server> <KEY> <value>'); return; } const config = load(); if (!config.servers[server]) return console.log(`✗ Server '${server}' not found`); config.servers[server].env = config.servers[server].env || {}; config.servers[server].env[key] = value; save(config); console.log(`✓ Set ${key} for '${server}'`); }, ``` ```js 'show-config': () => { console.log(JSON.stringify(load(), null, 2)); }, ``` ### Technical Analysis The `set-env` command stores supplied values directly in `config.json`. The save operation does not explicitly create or enforce restrictive permissions such as mode `0600`. Existing permissive permissions are also not corrected. The `show-config` command serializes the complete configuration without redacting environment values. Secrets can consequently appear in terminal history capture, CI logs, support transcripts, process output collection, or screen recordings. Although the documentation warns users that values are stored in plaintext, disclosure does not remove the underlying risk. ### Attack Path 1. An operator executes `set-env` with an API token, password, or other secret. 2. The CLI writes the value into `config.json` in plaintext. 3. A local user, backup process, source-control operation, or other process reads the configuration file. 4. Alternatively, an operator runs `show-config`, causing all stored values to be printed. 5. The exposed credential is reused against the corresponding external service. ### Impact Assessment An attacker wh ...[truncated 407 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Store secrets in an operating-system keychain, dedicated secret manager, or protected service environment rather than `config.json`. - Support secret references so configuration contains identifiers instead of secret values. - Create secret-bearing files with mode `0600` and verify or correct permissions on every write. - Write configuration atomically through a restrictively created temporary file followed by a rename. - Redact environment values from `show-config`; display only variable names or masked values. - Warn users before any command prints sensitive configuration and provide a separate explicit privileged command if raw output is essential. - Add `config.json` and secret files to `.gitignore`, while noting that ignore rules do not protect files already committed. - Provide credential-rotation guidance for configurations that may already have been exposed. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:31
Finding
Documentation Recommends Unpinned Runtime Package Execution Through npx<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:31-34` **Vulnerability Type**: Unpinned third-party dependency execution **Risk Level**: Medium ### Vulnerable Code ```bash # Add a server node cli.js add-server github npx @anthropic/mcp-github ``` A second example repeats the same unpinned configuration: ```json { "servers": { "github": { "command": "npx", "args": ["@anthropic/mcp-github"], "env": {} } } } ``` ### Technical Analysis The recommended command invokes an npm package through `npx` without specifying an exact audited version or integrity value. If the package is not already available locally, `npx` may retrieve executable code from the configured package registry at runtime. Because no lockfile or integrity-pinned installation artifact governs this example, the code eventually executed can differ from the code reviewed when the Skill was audited. A compromised upstream release, registry account takeover, mutable package selection, or package-name error can introduce attacker-controlled code. The risk is amplified because the resulting child process inherits the bridge process environment and executes with the bridge account's operating-system privileges. ### Attack Path 1. An operator follows the documented Quick Start and configures `npx @anthropic/mcp-github`. 2. The bridge starts the configured command. 3. `npx` resolves and, when necessary, downloads the package available from its configured registry. 4. A compromised or unexpectedly changed package executes locally. 5. The package accesses the bridge account's files, environment variables, network access, and any configured MCP credentials. ### Impact Assessment A malicious package can execute arbitrary code with the privileges of the bridge process. It may read or modify accessible files, steal inherited credentials, communicate over the network, or interfere with other MCP servers. The scope depends on the operating-system account, run ...[truncated 115 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Verify the intended package name and publisher before recommending it. - Pin the package to an exact reviewed version rather than relying on the registry's current resolution. - Install dependencies ahead of runtime using a committed lockfile with integrity hashes. - Use reproducible installation mechanisms such as `npm ci` and disable unnecessary lifecycle scripts where compatible. - Avoid downloading executable dependencies when the bridge starts. - Review package provenance, signatures, publication history, and transitive dependencies. - Run third-party MCP servers in restricted containers or operating-system sandboxes with minimal credentials, filesystem access, and network access. - Establish an update process that reviews and tests each new package version before deployment. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (14)

Credential Access

High
Category
Privilege Escalation
Content
console.log(`[${this.name}] Starting: ${command} ${args.join(' ')}`);

    this.process = spawn(command, args, {
      env: { ...process.env, ...env },
      stdio: ['pipe', 'pipe', 'pipe']
    });
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
}
    const config = load();
    if (!config.servers[server]) return console.log(`✗ Server '${server}' not found`);
    config.servers[server].env = config.servers[server].env || {};
    config.servers[server].env[key] = value;
    save(config);
    console.log(`✓ Set ${key} for '${server}'`);
Confidence
91% confidence
Finding
This finding reflects credential handling behavior: the code accepts env values—likely secrets—and persists them to disk in plaintext under the server configuration. In the context of an HTTP bridge for MCP tools, those credentials may grant access to external APIs, repositories, or internal systems, so disclosure can have significant downstream impact.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill advertises network and environment-related capabilities but does not declare any explicit tool scope or permissions boundaries. In an agent ecosystem, this weakens reviewability and least-privilege guarantees, making it easier for the skill to access sensitive env data or perform network actions without clear operator consent.

Rp1

Medium
Category
MCP Rug Pull
Confidence
93% confidence
Finding
Using `npx @anthropic/mcp-github` without pinning an exact version makes the deployed MCP server supply-chain mutable at runtime. A future malicious or compromised package release could be fetched and executed automatically, which is especially risky here because the bridge persists child processes and can pass through environment secrets such as API tokens.

External Transmission

Medium
Category
Data Exfiltration
Content
```bash
# List servers
curl http://localhost:3456/

# List tools
curl http://localhost:3456/<server>/tools
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Context-Inappropriate Capability

Medium
Confidence
77% confidence
Finding
The bridge launches configured commands as child processes, inheriting the host environment and allowing custom environment overrides. While starting MCP servers is part of the bridge's implementation, arbitrary command execution is a powerful capability that is broader than a simple REST exposure layer unless explicitly declared in the skill scope.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The POST /:server/call endpoint allows remote invocation of any MCP tool without authentication, authorization, or meaningful user confirmation. Because MCP tools may perform sensitive local or network actions, this bridge effectively turns them into remotely callable capabilities, which is especially dangerous combined with permissive CORS and optional IP filtering.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The bridge exposes an unauthenticated POST /:server/restart endpoint that allows any reachable client to stop and restart managed subprocesses. In this skill’s context, the bridge is intended to keep MCP servers alive and expose tools via HTTP, so exposing lifecycle control over REST materially expands the attack surface and enables denial of service or operational disruption.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding
npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The CLI stores arbitrary environment-variable values directly into config.json on disk, which will commonly include API keys, tokens, or other secrets for MCP servers. If file permissions are weak, backups are exposed, or the config is accidentally committed or shared, attackers can recover credentials and pivot into connected services.

Missing User Warnings

Medium
Confidence
98% confidence
Finding
show-config prints the entire configuration object, including per-server env values, directly to stdout. This can leak secrets into terminal scrollback, logs, shell history captures, CI output, or remote support sessions, making credential disclosure easy even without filesystem access.

Context-Inappropriate Capability

Medium
Confidence
84% confidence
Finding
The manifest describes a bridge that keeps MCP servers alive and exposes them over REST, but this CLI also invokes a host process manager command to restart a named service. Managing local system processes through pm2 is a privileged operational capability that goes beyond simple bridge configuration and is not explicitly stated in the manifest description.

Missing User Warnings

Low
Confidence
92% confidence
Finding
The restart endpoint performs process control over managed MCP servers without any user-facing disclosure or access control. While the absence of disclosure alone is not the core issue, in practice this exposes administrative functionality to any reachable client and can be used to interrupt service or force repeated reinitialization.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The remove-server command deletes a configured server entry from config.json immediately after invocation. Although successful deletion is logged afterward, there is no prior confirmation prompt or warning for this destructive action, and the help text does not call out that the change is immediate.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
bridge.js:56

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
cli.js:119