Use with caution. Address findings before production.
Scanned 5/3/2026, 7:38:38 PMยทCached resultยทFast Scanยท88 rulesยทHow we decide โ
AIVSS Score
Medium
Severity Breakdown
0
critical
2
high
18
medium
0
low
MCP Server Information
Findings
This package has a C-grade security rating with 2 high-severity findings including SQL injection vulnerabilities and ANSI escape injection risks, plus 18 medium-severity issues primarily related to server configuration weaknesses and a vulnerable dependency. While no critical vulnerabilities were detected, the combination of injection flaws and configuration problems poses meaningful security risks that should be addressed before deployment. You should carefully review the specific SQL injection and injection attack vectors before deciding to use this package.
Dependencies
@modelcontextprotocol/sdk (3)
Scan Details
Want deeper analysis?
Fast scan found 20 findings using rule-based analysis. Upgrade for LLM consensus across 5 judges, AI-generated remediation, and cross-file taint analysis.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnโt shipped yet.
20 of 20 findings
20 findings
SQL injection risk. SQL call receives a query built with string interpolation (%, +, f-string, or template literal) instead of placeholder parameters. Use parameterised queries.
Evidence
| 55 | try { |
| 56 | // Create database if it doesn't exist |
| 57 | await connection.query(`CREATE DATABASE IF NOT EXISTS ${dbName}`); |
| 58 | |
| 59 | // Switch to the test database |
| 60 | await connection.query(`USE ${dbName}`); |
Remediation
Use placeholder bindings (cursor.execute(sql, params), connection. query(sql, params), sequelize.query(sql, { replacements })) and never interpolate untrusted values into SQL text. For dynamic identifiers (table / column names), map user input to a fixed allowlist before substitution.
SQL injection risk. SQL call receives a query built with string interpolation (%, +, f-string, or template literal) instead of placeholder parameters. Use parameterised queries.
Evidence
| 58 | await connection.query(`CREATE DATABASE IF NOT EXISTS ${dbName}`); |
| 59 | |
| 60 | // Switch to the test database |
| 61 | await connection.query(`USE ${dbName}`); |
| 62 | |
| 63 | // Temporarily disable foreign key checks to allow dropping tables |
| 64 | await connection.query('SET FOREIGN_KEY_CHECKS = 0'); |
Remediation
Use placeholder bindings (cursor.execute(sql, params), connection. query(sql, params), sequelize.query(sql, { replacements })) and never interpolate untrusted values into SQL text. For dynamic identifiers (table / column names), map user input to a fixed allowlist before substitution.
@modelcontextprotocol/sdk==1.15.1 has 3 known CVEs [HIGH]: GHSA-345p-7cg4-v4c7, GHSA-8r9q-7v3j-jr4g, GHSA-w48q-cv73-mx4w. Upgrade to a patched version.
Remediation
Upgrade the pinned dependency to a patched version. Check the CVE's advisory URL for the recommended safe release, or use `npm audit fix` / `pip-audit --fix`. If no patched release is available yet, pin to a known-good prior version, vendor the fix, or remove the dependency.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 496 | // SSE notifications not supported in stateless mode |
| 497 | app.get("/mcp", async (req: Request, res: Response) => { |
| 498 | console.log("Received GET MCP request"); |
| 499 | res.writeHead(405).end( |
| 500 | JSON.stringify({ |
| 501 | jsonrpc: "2.0", |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
User-controlled value printed to terminal without ANSI escape sanitization. Malicious input can inject cursor-control sequences, rewrite earlier output, or hide shell commands from the operator.
Evidence
| 511 | // Session termination not needed in stateless mode |
| 512 | app.delete("/mcp", async (req: Request, res: Response) => { |
| 513 | console.log("Received DELETE MCP request"); |
| 514 | res.writeHead(405).end( |
| 515 | JSON.stringify({ |
| 516 | jsonrpc: "2.0", |
Remediation
Strip C0/C1 control codes before printing user-controlled values. Python: re.sub(r"[\x00-\x08\x0b-\x1f\x7f]", "", s). Prefer a structured logger (json/logfmt) over raw print to stdout.
Package declares an install-time hook (npm postinstall/preinstall/prepare, setup.py cmdclass override, custom setuptools install class, or non-default pyproject build-backend). Anyone installing this package runs the hook. Confirm the hook is necessary and review its contents; prefer shipping a plain library without install-time execution.
Evidence
| 20 | "start": "node dist/index.js", |
| 21 | "dev": "ts-node index.ts", |
| 22 | "build": "tsc && shx chmod +x dist/*.js", |
| 23 | "prepare": "npm run build", |
| 24 | "watch": "tsc --watch", |
| 25 | "setup:test:db": "tsx scripts/setup-test-db.ts", |
| 26 | "pretest": "pnpm run setup:test:db", |
Remediation
Prefer libraries that do not require install-time code execution: - Drop `postinstall`/`preinstall`/`prepare` scripts if the work can happen at runtime or build-time instead. - Ship pre-built native binaries rather than compiling via a custom `cmdclass` or `build_ext` override. - For Dockerfiles: replace `RUN curl โฆ | sh` with a pinned download + checksum verification + explicit `RUN` of a named script. - If the hook is unavoidable, document exactly what it does so downstream reviewers
Dockerfile never sets a non-root `USER` directive, so the CMD runs as root by default. Any RCE or library-level vulnerability exploited inside this container gets full privileges (MCP Top-10 R3). Add `USER <non-root>` before CMD / ENTRYPOINT in the final stage โ e.g. `USER 1000`, `USER nobody`, or `USER nonroot` on distroless.
Evidence
| 1 | # Generated by https://smithery.ai. See: https://smithery.ai/docs/config#dockerfile |
| 2 | # Use a Node.js Alpine image as the base |
| 3 | FROM node:22-alpine AS builder |
| 4 | |
| 5 | # Install pnpm |
| 6 | RUN npm install -g pnpm |
| 7 | |
| 8 | # Set the working directory |
| 9 | WORKDIR /app |
| 10 | |
| 11 | # Copy the package.json and pnpm-lock.yaml if present |
| 12 | COPY package.json pnpm-lock.yaml* /app/ |
| 13 | |
| 14 | # Install the dependencies |
| 15 | RUN pnpm install --frozen-lockfile --ignore-scripts |
| 16 | |
| 17 | # Copy the rest of the application code |
| 18 | COPY . /app |
| 19 | |
| 20 | # Build the application |
| 21 | RUN pnpm |
Remediation
Create and switch to a non-root user before the CMD / ENTRYPOINT: RUN adduser --system --uid 1000 app USER 1000 Or reuse the base image's shipped non-root account (e.g. `USER nobody`, `USER nonroot` on distroless). Multi-stage builds only need the USER directive in the final stage.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal โ confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # Multi-DB Mode and Schema-Specific Permissions |
| 2 | |
| 3 | This document describes the new multi-database mode and schema-specific permissions features added to the MCP-Server-MySQL. |
| 4 | |
| 5 | ## Multi-DB Mode |
| 6 | |
| 7 | MCP-Server-MySQL now supports working with multiple databases simultaneously when no specific database is set in the configuration. |
| 8 | |
| 9 | ### How to Enable Multi-DB Mode |
| 10 | |
| 11 | To enable multi-DB mode, simply leave the `MYSQL_DB` environment variable empty: |
| 12 | |
| 13 | ```json |
| 14 | { |
| 15 | "mcpServers": { |
| 16 | "mcp_server_mysql": { |
| 17 | |
Remediation
Declare a real authentication mechanism in the manifest, matching what the running server actually enforces: - `"auth": "bearer"` with a token scheme documented for callers - `"auth": "oauth"` / `"oauth2": { ... }` for delegated flows - `"apiKey": { "header": "X-API-Key", "prefix": "..." }` - `"mtls": true` when client certificates are required If the server is intentionally unauthenticated (stdio-only, local developer tool, trusted-host network), document the assumption in the manifest via a `"
File registers a state-changing HTTP route (POST / PUT / PATCH / DELETE) but no CSRF protection middleware is applied anywhere in the file. If the server uses cookie-based session auth, a cross-site request from any origin can hit this route while the user's cookies ride along. Apply CSRF middleware: - Express: `csurf` / `csrf-csrf` / `lusca.csrf()` - FastAPI: `fastapi-csrf-protect` - Flask: `flask_wtf.csrf.CSRFProtect` Or, if the route is a JSON API authenticated by bearer tokens (no co
Evidence
| 1 | #!/usr/bin/env node |
| 2 | |
| 3 | import { Server } from "@modelcontextprotocol/sdk/server/index.js"; |
| 4 | import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; |
| 5 | import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js"; |
| 6 | import { |
| 7 | CallToolRequestSchema, |
| 8 | ListResourcesRequestSchema, |
| 9 | ListToolsRequestSchema, |
| 10 | ReadResourceRequestSchema, |
| 11 | } from "@modelcontextprotocol/sdk/types.js"; |
| 12 | import { z } from "zod"; |
| 13 | import { log } from "./src/utils/index.j |
Remediation
Apply CSRF middleware at the route or router level: - Express: `app.use(csurf())` / `csrf-csrf` package - FastAPI: `fastapi-csrf-protect` with `Depends(...)` - Flask: `CSRFProtect(app)` from `flask_wtf.csrf` Or move to bearer-token auth and set `SameSite=Strict` / `SameSite=Lax` on any session cookies. Document the choice in the project README so reviewers can confirm intent.
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 29 | - uses: actions/checkout@v4 |
| 30 | |
| 31 | - name: Install pnpm |
| 32 | uses: pnpm/action-setup@v2 |
| 33 | with: |
| 34 | version: 8 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 38 | uses: actions/checkout@v4 |
| 39 | |
| 40 | - name: Set up Node.js |
| 41 | uses: actions/setup-node@v4 |
| 42 | with: |
| 43 | node-version: '22' |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 34 | version: 8 |
| 35 | |
| 36 | - name: Setup Node.js |
| 37 | uses: actions/setup-node@v4 |
| 38 | with: |
| 39 | node-version: "22" |
| 40 | cache: "pnpm" |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 26 | --health-retries=3 |
| 27 | |
| 28 | steps: |
| 29 | - uses: actions/checkout@v4 |
| 30 | |
| 31 | - name: Install pnpm |
| 32 | uses: pnpm/action-setup@v2 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 12 | - uses: actions/checkout@v4 |
| 13 | |
| 14 | - name: Install pnpm |
| 15 | uses: pnpm/action-setup@v2 |
| 16 | with: |
| 17 | version: 8 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 14 | steps: |
| 15 | - name: Checkout repository |
| 16 | uses: actions/checkout@v4 |
| 17 | |
| 18 | - name: Set up Node.js |
| 19 | uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 17 | uses: actions/checkout@v4 |
| 18 | |
| 19 | - name: Set up Node.js |
| 20 | uses: actions/setup-node@v4 |
| 21 | with: |
| 22 | node-version: '22' |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 35 | steps: |
| 36 | - name: Checkout repository |
| 37 | uses: actions/checkout@v4 |
| 38 | |
| 39 | - name: Set up Node.js |
| 40 | uses: actions/setup-node@v4 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 17 | version: 8 |
| 18 | |
| 19 | - name: Setup Node.js |
| 20 | uses: actions/setup-node@v4 |
| 21 | with: |
| 22 | node-version: "22" |
| 23 | registry-url: "https://registry.npmjs.org" |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
GitHub Actions `uses:` reference is not pinned to a 40-character commit SHA. Tags (`@v4`) and branches (`@main`) are mutable โ a compromised maintainer or a tag rewrite can substitute malicious code into your CI pipeline silently. Pin to a SHA: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab`. For readability, include the version as a trailing comment: `# v4.1.1`. Tools like `pinact` / `ratchet` automate this. Allowed unpinned forms (excluded by the rule): - Local actions `.
Evidence
| 9 | publish: |
| 10 | runs-on: ubuntu-latest |
| 11 | steps: |
| 12 | - uses: actions/checkout@v4 |
| 13 | |
| 14 | - name: Install pnpm |
| 15 | uses: pnpm/action-setup@v2 |
Remediation
Pin every `uses:` to a 40-character commit SHA. Trailing comment with the version helps reviewers: `uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v4.1.1` Automate the migration with `pinact` (https://github.com/suzuki-shunsuke/pinact) or `ratchet` (https://github.com/sethvargo/ratchet). Add a `pinact run --check` pre-commit hook so future PRs stay pinned. Re-pin when the action releases a new version โ Dependabot can do this automatically with `version-update-strategy: inc
Time-of-check-to-time-of-use race. Code calls `os.path.exists` / `fs.existsSync` to check a path, then `open` / `readFileSync` / `unlink` on the same name within a few lines โ without a lock or atomic-open. An attacker who can race the filesystem (symlink, file replacement) between the check and the use gets the action applied to a different target. Replace the check-then-use pattern with the action's own error handling: try the open and catch FileNotFoundError / ENOENT. For atomic creation use
Evidence
| 1 | import * as dotenv from "dotenv"; |
| 2 | import * as fs from "fs"; |
| 3 | import { SchemaPermissions } from "../types/index.js"; |
| 4 | import { parseSchemaPermissions, parseMySQLConnectionString } from "../utils/index.js"; |
| 5 | |
| 6 | /** |
| 7 | * Read and validate an SSL file (certificate, key, or CA) for SSL connections. |
| 8 | * @param filePath - Path to the SSL file (PEM format) |
| 9 | * @param label - Human-readable label for error messages (e.g. "CA certificate", "client certificate") |
| 10 | * @returns Buffer containing the file data |
| 11 | * @throw |
Remediation
Replace check-then-use with action-then-handle: Python: `try: with open(p) as f: ... except FileNotFoundError: ...` Node: `try { fs.readFileSync(p); } catch (e) { if (e.code === "ENOENT") ... }` For atomic file creation: Python: `os.open(p, os.O_RDWR | os.O_CREAT | os.O_EXCL)` Node: `fs.open(p, "wx")` โ fails if file exists, no race. When you genuinely must check first, use `flock` (Python `fcntl`) or a similar per-process advisory lock to make the window uninteresting to a
Tables