High risk. Don't ship without significant remediation.
Scanned 5/18/2026, 12:58:48 PM·Cached result·Deep Scan·91 rules·View source ↗·How we decide ↗
AIVSS Score
High
Severity Breakdown
0
critical
14
high
2
medium
0
low
MCP Server Information
Findings
This package carries significant security risks with a D-grade safety score of 66/100 and 14 high-severity findings, primarily centered on prompt injection vulnerabilities (9 instances) and tool poisoning attacks (4 instances) that could allow attackers to manipulate the server's behavior or inject malicious instructions. The presence of a vulnerable dependency and denial-of-wallet risk further compounds concerns about both supply chain integrity and potential resource abuse. Installation is not recommended without substantial remediation of these high-severity issues.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 — 16 of 16 findings. Click any finding to read.
Dependencies
@modelcontextprotocol/sdk (3)
Workflow telemetry sanitizer could retain partial values from URL-shaped node parameters
Multi-tenant MCP requests fall back to process-level n8n credentials when tenant headers are absent or incomplete
Authenticated SSRF in n8n-mcp webhook and API client paths
Path traversal, redirect-following SSRF, and telemetry payload exposure in n8n-mcp
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
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.
16 of 16 findings
16 findings
generateSummary() uses module-level OpenAI client with baseUrl and apiKey from constructor config without per-request caller identity
Evidence
| 57 | private getSystemPrompt; |
| 58 | private extractJson; |
| 59 | private truncateArrayFields; |
| 60 | private truncateReadme; |
| 61 | private getDefaultSummary; |
| 62 | testConnection(): Promise<{ |
| 63 | success: boolean; |
RemediationAI
The problem is that generateSummary() uses a module-level OpenAI client initialized with baseUrl and apiKey from constructor config, which means all callers share the same credentials regardless of their identity or authorization level. Add a per-request caller identity parameter (e.g., userId, requestContext) to the generateSummary() method signature and pass it to a new OpenAI client instance created per-request with caller-specific credentials or a credential resolver function. This ensures each caller's API calls are isolated and auditable to their identity. Verify by adding logging that captures the caller identity for each API request and confirming different callers receive different client instances or credential contexts.
getPackageTarballUrl() uses module-level npmRegistryUrl credential without per-request caller identity to access npm registry
Evidence
| 54 | username: string; |
| 55 | email: string; |
| 56 | }; |
| 57 | maintainers: Array<{ |
| 58 | username: string; |
| 59 | email: string; |
| 60 | }>; |
RemediationAI
The problem is that getPackageTarballUrl() uses a module-level npmRegistryUrl credential without per-request caller identity, allowing all callers to access the npm registry with the same authentication context. Refactor the method to accept a caller identity parameter and resolve npm registry credentials per-request using a credential provider that maps caller identity to appropriate registry tokens or authentication headers. This isolates registry access by caller and enables per-caller rate limiting and audit trails. Verify by confirming that requests from different callers use different authentication tokens or headers in the npm registry HTTP calls.
generateBatch() uses module-level OpenAI client with baseUrl and apiKey from constructor config without per-request caller identity
Evidence
| 58 | private extractJson; |
| 59 | private truncateArrayFields; |
| 60 | private truncateReadme; |
| 61 | private getDefaultSummary; |
| 62 | testConnection(): Promise<{ |
| 63 | success: boolean; |
| 64 | message: string; |
RemediationAI
The problem is that generateBatch() uses a module-level OpenAI client with baseUrl and apiKey from constructor config, creating a shared credential context for all batch operations regardless of caller identity. Modify generateBatch() to accept a caller identity parameter and instantiate a new OpenAI client per-batch using caller-specific credentials resolved from a credential provider, or wrap the batch call with caller context that is logged and audited. This ensures batch operations are isolated by caller and traceable to their identity. Verify by adding request context logging to each batch API call and confirming that different callers' batches are processed with distinct credential contexts.
fetchPackageJson() uses module-level npmRegistryUrl credential without per-request caller identity to access npm registry
Evidence
| 53 | publisher?: { |
| 54 | username: string; |
| 55 | email: string; |
| 56 | }; |
| 57 | maintainers: Array<{ |
| 58 | username: string; |
| 59 | email: string; |
RemediationAI
The problem is that fetchPackageJson() uses a module-level npmRegistryUrl credential without per-request caller identity, allowing all callers to fetch package metadata with the same authentication. Add a caller identity parameter to fetchPackageJson() and resolve npm registry credentials per-request using a credential provider that returns caller-specific tokens or headers. This isolates package metadata access by caller and enables per-caller audit trails. Verify by confirming that different callers' fetchPackageJson() calls use different authentication tokens in the HTTP headers sent to the npm registry.
fetchNpmPackages() uses module-level npmSearchUrl and npmRegistryUrl credentials without per-request caller identity to access npm registry
Evidence
| 52 | }; |
| 53 | publisher?: { |
| 54 | username: string; |
| 55 | email: string; |
| 56 | }; |
| 57 | maintainers: Array<{ |
| 58 | username: string; |
RemediationAI
The problem is that fetchNpmPackages() uses module-level npmSearchUrl and npmRegistryUrl credentials without per-request caller identity, creating a shared authentication context for all npm registry and search API calls. Refactor fetchNpmPackages() to accept a caller identity parameter and resolve both search and registry credentials per-request using a credential provider that maps caller identity to appropriate tokens. This ensures npm API access is isolated and auditable by caller. Verify by instrumenting the HTTP calls to npmSearchUrl and npmRegistryUrl and confirming that different callers use different authentication credentials.
fetchVerifiedNodes() uses module-level strapiBaseUrl credential (environment-dependent) without per-request caller identity to access Strapi API
Evidence
| 51 | username?: string; |
| 52 | }; |
| 53 | publisher?: { |
| 54 | username: string; |
| 55 | email: string; |
| 56 | }; |
| 57 | maintainers: Array<{ |
RemediationAI
The problem is that fetchVerifiedNodes() uses a module-level strapiBaseUrl credential (environment-dependent) without per-request caller identity, allowing all callers to access the Strapi API with the same authentication context. Add a caller identity parameter to fetchVerifiedNodes() and resolve Strapi credentials per-request using a credential provider that returns caller-specific API keys or authentication headers based on the caller identity. This isolates Strapi API access by caller and enables per-caller audit logging. Verify by confirming that different callers' Strapi API requests include different authentication tokens or headers.
DocumentationGenerator.generateSummary() calls OpenAI chat.completions.create without explicit max_tokens parameter in the chatCompletion call, despite maxTokens being set in constructor config.
Evidence
| 54 | const validated = exports.DocumentationSummarySchema.parse(truncated); |
| 55 | return { |
| 56 | nodeType: input.nodeType, |
| 57 | summary: validated, |
| 58 | }; |
| 59 | } |
| 60 | catch (error) { |
RemediationAI
The problem is that the OpenAI chat.completions.create() call in generateSummary() does not pass the max_tokens parameter, even though maxTokens is available in the constructor config, allowing the LLM to generate unbounded responses that may exceed expected limits. Modify the chat.completions.create() call to explicitly include max_tokens: this.maxTokens in the request parameters. This ensures responses are bounded by the configured token limit, preventing cost overruns and resource exhaustion. Verify by calling generateSummary() and confirming that the OpenAI API request includes the max_tokens field set to the configured maxTokens value.
DocumentationGenerator.extractJson() parses and executes arbitrary JSON responses from remote LLM API (openai_1.default client at baseURL), enabling silent redefinition via remote-exec of LLM-generated code embedded in documentation summaries.
Evidence
| 1 | "use strict"; |
| 2 | var __importDefault = (this && this.__importDefault) || function (mod) { |
| 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; |
| 4 | }; |
| 5 | Object.defineProperty(exports, "__esModule", { value: true }); |
| 6 | exports.DocumentationGenerator = exports.DocumentationSummarySchema = void 0; |
| 7 | exports.createDocumentationGenerator = createDocumentationGenerator; |
| 8 | const openai_1 = __importDefault(require("openai")); |
| 9 | const zod_1 = require("zod"); |
| 10 | const logger_1 = require("../utils/logger"); |
| 11 | exports.D |
RemediationAI
The problem is that extractJson() parses arbitrary JSON responses from the remote OpenAI API without validation, allowing an attacker who controls the LLM endpoint (via baseUrl injection) to embed malicious code or redefinitions in the JSON that are later executed. Replace the generic JSON.parse() call with strict schema validation using the existing DocumentationSummarySchema (via zod parse()), and reject any JSON that does not conform to the expected schema structure. This ensures only expected fields are accepted and prevents injection of arbitrary code. Verify by attempting to inject a malicious JSON structure into the LLM response and confirming that extractJson() throws a validation error.
CommunityNodeFetcher.fetchPackageJson() fetches and returns arbitrary package.json from npm registry, enabling remote code loading via eval/require of returned JSON structure; combined with getPackageTarballUrl() and potential dynamic require patterns, this enables silent redefinition via remote-exec of fetched package metadata.
Evidence
| 1 | "use strict"; |
| 2 | var __importDefault = (this && this.__importDefault) || function (mod) { |
| 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; |
| 4 | }; |
| 5 | Object.defineProperty(exports, "__esModule", { value: true }); |
| 6 | exports.CommunityNodeFetcher = void 0; |
| 7 | const axios_1 = __importDefault(require("axios")); |
| 8 | const logger_1 = require("../utils/logger"); |
| 9 | const FETCH_CONFIG = { |
| 10 | STRAPI_TIMEOUT: 30000, |
| 11 | NPM_REGISTRY_TIMEOUT: 15000, |
| 12 | NPM_DOWNLOADS_TIMEOUT: 10000, |
| 13 | RETRY_DELAY: 1000, |
| 14 | MAX_R |
RemediationAI
The problem is that fetchPackageJson() fetches and returns arbitrary package.json from npm without validation, and if combined with dynamic require or eval patterns, enables remote code execution via malicious package metadata. Implement strict schema validation on the returned package.json using zod or a similar validator that only allows expected fields (name, version, description, etc.) and rejects any fields that could trigger code execution (e.g., scripts, bin, require hooks). This prevents malicious package metadata from being used in code execution contexts. Verify by attempting to fetch a package with malicious fields and confirming that the validation rejects it.
DocumentationGenerator fetches remote LLM endpoint configuration (baseUrl, model, apiKey) at construction time and uses it to make dynamic LLM calls with user-provided prompts that are built from node documentation, allowing remote steering of summarization behavior via the baseUrl parameter.
Evidence
| 1 | "use strict"; |
| 2 | var __importDefault = (this && this.__importDefault) || function (mod) { |
| 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; |
| 4 | }; |
| 5 | Object.defineProperty(exports, "__esModule", { value: true }); |
| 6 | exports.DocumentationGenerator = exports.DocumentationSummarySchema = void 0; |
| 7 | exports.createDocumentationGenerator = createDocumentationGenerator; |
| 8 | const openai_1 = __importDefault(require("openai")); |
| 9 | const zod_1 = require("zod"); |
| 10 | const logger_1 = require("../utils/logger"); |
| 11 | exports.D |
RemediationAI
The problem is that DocumentationGenerator fetches remote LLM endpoint configuration (baseUrl, model, apiKey) at construction time and uses it to make dynamic LLM calls, allowing an attacker to steer summarization behavior by controlling the baseUrl parameter. Implement strict validation of the baseUrl parameter using a whitelist of allowed LLM endpoints, and move credential resolution to a secure credential provider that is not user-configurable. This prevents remote steering of the LLM endpoint. Verify by attempting to pass a malicious baseUrl and confirming that the constructor rejects it or that the credential provider overrides it with a whitelisted endpoint.
DocumentationGenerator.generateSummary() builds dynamic prompts from input documentation and sends them to a remote LLM endpoint (this.chatCompletion), where the system prompt and user prompt content are constructed per-call and determine the LLM's behavior for each node summary generation.
Evidence
| 47 | const content = completion.choices[0]?.message?.content; |
| 48 | if (!content) { |
| 49 | throw new Error('No content in LLM response'); |
| 50 | } |
| 51 | const jsonContent = this.extractJson(content); |
| 52 | const parsed = JSON.parse(jsonContent); |
| 53 | const truncated = this.truncateArrayFields(parsed); |
| 54 | const validated = exports.DocumentationSummarySchema.parse(truncated); |
| 55 | return { |
| 56 | nodeType: input.nodeType, |
| 57 | s |
RemediationAI
The problem is that generateSummary() builds dynamic prompts from input documentation and sends them to a remote LLM endpoint without delimiters or clear separation between system instructions and user-provided content, enabling prompt injection attacks. Refactor the prompt construction to use explicit delimiters (e.g., XML tags or structured prompt templates) that clearly separate the system prompt from the user-provided documentation, and validate that user input does not contain prompt injection patterns. This prevents malicious documentation from breaking out of the intended prompt context. Verify by attempting to inject LLM instructions into the readme field and confirming that the LLM treats them as data rather than instructions.
DocumentationGenerator.generateSummary() accepts untrusted readme field from DocumentationInput and includes it in LLM prompt without delimiters, enabling injection of malicious instructions via package documentation.
Evidence
| 1 | import { z } from 'zod'; |
| 2 | export declare const DocumentationSummarySchema: z.ZodObject<{ |
| 3 | purpose: z.ZodString; |
| 4 | capabilities: z.ZodArray<z.ZodString, "many">; |
RemediationAI
The problem is that generateSummary() accepts the untrusted readme field from DocumentationInput and includes it directly in the LLM prompt without delimiters or escaping, enabling prompt injection attacks via malicious package documentation. Wrap the readme content in explicit delimiters (e.g., <README>...content...</README>) and escape any special characters that could break the prompt structure, or use a structured prompt template that treats readme as data. This prevents malicious documentation from injecting instructions into the LLM prompt. Verify by creating a test package with prompt injection payloads in the README and confirming that the LLM treats them as literal text.
CommunityNodeFetcher.fetchPackageWithReadme() returns untrusted npm package README content (from registry.npmjs.org) directly into LLM context without provenance delimiters, enabling indirect prompt injection via malicious package documentation.
Evidence
| 1 | export interface StrapiCommunityNodeAttributes { |
| 2 | name: string; |
| 3 | displayName: string; |
| 4 | description: string; |
RemediationAI
The problem is that fetchPackageWithReadme() returns untrusted npm package README content directly into LLM context without provenance markers or delimiters, enabling indirect prompt injection via malicious package documentation. Wrap the README content in clear provenance delimiters (e.g., <EXTERNAL_SOURCE source="npm" package="name">...content...</EXTERNAL_SOURCE>) and add a validation step that sanitizes or escapes the README before returning it. This makes the external origin explicit and prevents injection attacks. Verify by confirming that the returned README includes provenance markers and that malicious content in the README does not affect LLM behavior.
DocumentationBatchProcessor.fetchReadmes() fetches untrusted README content from npm packages and stores it in repository without sanitization or provenance markers, which is later returned to LLM context by DocumentationGenerator.
Evidence
| 1 | import { NodeRepository } from '../database/node-repository'; |
| 2 | import { CommunityNodeFetcher } from './community-node-fetcher'; |
| 3 | import { DocumentationGenerator } from './documentation-generator'; |
| 4 | export interface BatchProcessorOptions { |
RemediationAI
The problem is that fetchReadmes() fetches untrusted README content from npm packages and stores it in the repository without sanitization or provenance markers, which is later returned to LLM context without clear attribution. Add a sanitization step that escapes or validates README content before storage, and add provenance metadata (source, package name, timestamp) to each stored README. When returning README content to LLM context, include the provenance markers. This ensures untrusted content is clearly marked and cannot inject malicious instructions. Verify by confirming that stored READMEs include provenance metadata and that malicious content is escaped or rejected.
@modelcontextprotocol/sdk==1.20.1 has 3 known CVEs [HIGH]: GHSA-345p-7cg4-v4c7, GHSA-8r9q-7v3j-jr4g, GHSA-w48q-cv73-mx4w. Upgrade to a patched version.
RemediationAI
The problem is that @modelcontextprotocol/sdk version 1.20.1 has 3 known HIGH-severity CVEs (GHSA-345p-7cg4-v4c7, GHSA-8r9q-7v3j-jr4g, GHSA-w48q-cv73-mx4w) that could enable remote code execution or other attacks. Upgrade the package to the latest patched version by running npm update @modelcontextprotocol/sdk or manually updating the version in package.json to a version >= the first patched release for each CVE. This eliminates the known vulnerabilities. Verify by running npm audit and confirming that no HIGH-severity vulnerabilities are reported for this package.
Network / IO / subprocess call without an explicit timeout. A malicious or hung upstream (HTTP host, socket peer, child process) can pin threads, exhaust connection/process pools, and make the MCP server unresponsive. Always pass a bounded timeout. v2 extends v1 with subprocess coverage (R03 from the legacy readiness audit).
Evidence
| 186 | # Templates are processed in batches using OpenAI's Batch API for 50% cost savings |
| 187 | # OPENAI_BATCH_SIZE=100 |
| 188 | |
| 189 | # Enable metadata generation during template fetch (default: false) |
| 190 | # Set to true to automatically generate metadata when running fetch:templates |
| 191 | # METADATA_GENERATION_ENABLED=false |
RemediationAI
The problem is that network, IO, and subprocess calls throughout the codebase lack explicit timeouts, allowing malicious or hung upstream services (HTTP hosts, npm registry, Strapi API, OpenAI API) to pin threads and exhaust connection pools, making the MCP server unresponsive. Add explicit timeout parameters to all HTTP client calls (axios, OpenAI client), npm registry calls, and subprocess operations; set reasonable defaults (e.g., 30 seconds for HTTP, 60 seconds for batch operations) in the configuration and environment variables. This prevents hung connections from blocking the server. Verify by simulating a hung upstream service and confirming that the MCP server times out and recovers within the configured timeout window.