VYPR
Medium severity4.3GHSA Advisory· Published May 8, 2026· Updated May 14, 2026

CVE-2026-42282

CVE-2026-42282

Description

n8n-MCP is an MCP server that provides AI assistants access to n8n node documentation, properties, and operations. Prior to version 2.47.13, when n8n-mcp runs in HTTP transport mode, authenticated MCP tools/call requests had their full arguments and JSON-RPC params written to server logs by the request dispatcher and several sibling code paths before any redaction. When a tool call carries credential material — most notably n8n_manage_credentials.data — the raw values can be persisted in logs. In deployments where logs are collected, forwarded to external systems, or viewable outside the request trust boundary (shared log storage, SIEM pipelines, support/ops access), this can result in disclosure of: bearer tokens and OAuth credentials sent through n8n_manage_credentials, per-tenant API keys and webhook auth headers embedded in tool arguments, arbitrary secret-bearing payloads passed to any MCP tool. The issue requires authentication (AUTH_TOKEN accepted by the server), so unauthenticated callers cannot trigger it; the runtime exposure is also reduced by an existing console-silencing layer in HTTP mode, but that layer is fragile and the values are still constructed and passed into the logger. This issue has been patched in version 2.47.13.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
n8n-mcpnpm
< 2.47.132.47.13

Affected products

1

Patches

1
59b665bda367

Merge commit from fork

https://github.com/czlonkowski/n8n-mcpRomuald CzłonkowskiApr 20, 2026via nvd-ref
13 files changed · +257 51
  • CHANGELOG.md+8 0 modified
    @@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
     
     ## [Unreleased]
     
    +## [2.47.13] - 2026-04-20
    +
    +### Security
    +
    +- Redact MCP tool-call arguments in server logs (GHSA-wg4g-395p-mqv3, CVSS 4.3 Medium). Reported by @Mirr2.
    +
    +Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
    +
     ## [2.47.12] - 2026-04-17
     
     Batch of ten fixes from the 2026-04-16 staging QA regression (release-blockers and polish items shipped together).
    
  • dist/mcp/server.d.ts.map+1 1 modified
    @@ -1 +1 @@
    -{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
    \ No newline at end of file
    +{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"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"}
    \ No newline at end of file
    
  • dist/mcp/server.js+14 21 modified
    @@ -48,6 +48,7 @@ const tools_n8n_manager_1 = require("./tools-n8n-manager");
     const tools_n8n_friendly_1 = require("./tools-n8n-friendly");
     const workflow_examples_1 = require("./workflow-examples");
     const logger_1 = require("../utils/logger");
    +const redaction_1 = require("../utils/redaction");
     const node_repository_1 = require("../database/node-repository");
     const database_adapter_1 = require("../database/database-adapter");
     const shared_database_1 = require("../database/shared-database");
    @@ -438,15 +439,11 @@ class N8NDocumentationMCPServer {
             });
             this.server.setRequestHandler(types_js_1.CallToolRequestSchema, async (request) => {
                 const { name, arguments: args } = request.params;
    -            logger_1.logger.info('Tool call received - DETAILED DEBUG', {
    +            logger_1.logger.info('Tool call received', {
                     toolName: name,
    -                arguments: JSON.stringify(args, null, 2),
    -                argumentsType: typeof args,
    -                argumentsKeys: args ? Object.keys(args) : [],
    -                hasNodeType: args && 'nodeType' in args,
    -                hasConfig: args && 'config' in args,
    -                configType: args && args.config ? typeof args.config : 'N/A',
    -                rawRequest: JSON.stringify(request.params)
    +                ...(0, redaction_1.summarizeToolCallArgs)(args),
    +                hasNodeType: !!(args && typeof args === 'object' && 'nodeType' in args),
    +                hasConfig: !!(args && typeof args === 'object' && 'config' in args),
                 });
                 const disabledTools = this.getDisabledTools();
                 if (disabledTools.has(name)) {
    @@ -482,16 +479,17 @@ class N8NDocumentationMCPServer {
                             const parsed = JSON.parse(possibleNestedData);
                             if (parsed && typeof parsed === 'object') {
                                 logger_1.logger.warn('Detected n8n nested output bug, attempting to extract actual arguments', {
    -                                originalArgs: args,
    -                                extractedArgs: parsed
    +                                toolName: name,
    +                                originalArgsKeys: Object.keys(args),
    +                                extractedArgsKeys: Object.keys(parsed),
                                 });
                                 if (this.validateExtractedArgs(name, parsed)) {
                                     processedArgs = parsed;
                                 }
                                 else {
                                     logger_1.logger.warn('Extracted arguments failed validation, using original args', {
                                         toolName: name,
    -                                    extractedArgs: parsed
    +                                    extractedArgsKeys: Object.keys(parsed),
                                     });
                                 }
                             }
    @@ -508,7 +506,7 @@ class N8NDocumentationMCPServer {
                     processedArgs = JSON.parse(JSON.stringify(processedArgs));
                 }
                 try {
    -                logger_1.logger.debug(`Executing tool: ${name}`, { args: processedArgs });
    +                logger_1.logger.debug(`Executing tool: ${name}`, (0, redaction_1.summarizeToolCallArgs)(processedArgs));
                     const startTime = Date.now();
                     const result = await this.executeTool(name, processedArgs);
                     const duration = Date.now() - startTime;
    @@ -800,8 +798,8 @@ class N8NDocumentationMCPServer {
                 if (!(requiredField in args)) {
                     logger_1.logger.debug(`Extracted args missing required field: ${requiredField}`, {
                         toolName,
    -                    extractedArgs: args,
    -                    required
    +                    extractedArgsKeys: Object.keys(args),
    +                    required,
                     });
                     return false;
                 }
    @@ -818,7 +816,6 @@ class N8NDocumentationMCPServer {
                             toolName,
                             expectedType,
                             actualType,
    -                        fieldValue
                         });
                         return false;
                     }
    @@ -931,7 +928,7 @@ class N8NDocumentationMCPServer {
             }
             if (coercedAny) {
                 logger_1.logger.warn(`Coerced mistyped params for tool "${toolName}"`, {
    -                original: Object.fromEntries(Object.entries(args).map(([k, v]) => [k, `${typeof v}: ${typeof v === 'string' ? v.substring(0, 80) : v}`])),
    +                original: Object.fromEntries(Object.entries(args).map(([k, v]) => [k, typeof v])),
                 });
             }
             return coerced;
    @@ -942,11 +939,7 @@ class N8NDocumentationMCPServer {
             if (disabledTools.has(name)) {
                 throw new Error(`Tool '${name}' is disabled via DISABLED_TOOLS environment variable`);
             }
    -        logger_1.logger.info(`Tool execution: ${name}`, {
    -            args: typeof args === 'object' ? JSON.stringify(args) : args,
    -            argsType: typeof args,
    -            argsKeys: typeof args === 'object' ? Object.keys(args) : 'not-object'
    -        });
    +        logger_1.logger.info(`Tool execution: ${name}`, (0, redaction_1.summarizeToolCallArgs)(args));
             if (typeof args !== 'object' || args === null) {
                 throw new Error(`Invalid arguments for tool ${name}: expected object, got ${typeof args}`);
             }
    
  • dist/mcp/server.js.map+1 1 modified
  • dist/utils/redaction.d.ts+1 0 modified
    @@ -1,4 +1,5 @@
     export declare const REDACTED = "[REDACTED]";
     export declare function redactHeaders(headers: Record<string, unknown> | undefined | null): Record<string, unknown>;
     export declare function summarizeMcpBody(body: unknown): Record<string, unknown>;
    +export declare function summarizeToolCallArgs(args: unknown): Record<string, unknown>;
     //# sourceMappingURL=redaction.d.ts.map
    \ No newline at end of file
    
  • dist/utils/redaction.d.ts.map+1 1 modified
    @@ -1 +1 @@
    -{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../src/utils/redaction.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,QAAQ,eAAe,CAAC;AAMrC,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,GAClD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAMD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAcvE"}
    \ No newline at end of file
    +{"version":3,"file":"redaction.d.ts","sourceRoot":"","sources":["../../src/utils/redaction.ts"],"names":[],"mappings":"AAcA,eAAO,MAAM,QAAQ,eAAe,CAAC;AAMrC,wBAAgB,aAAa,CAC3B,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,GAAG,IAAI,GAClD,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CASzB;AAMD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAcvE;AAOD,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAyB5E"}
    \ No newline at end of file
    
  • dist/utils/redaction.js+29 0 modified
    @@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
     exports.REDACTED = void 0;
     exports.redactHeaders = redactHeaders;
     exports.summarizeMcpBody = summarizeMcpBody;
    +exports.summarizeToolCallArgs = summarizeToolCallArgs;
     const SENSITIVE_HEADER_NAMES = new Set([
         'authorization',
         'proxy-authorization',
    @@ -37,4 +38,32 @@ function summarizeMcpBody(body) {
             hasParams: b.params !== undefined && b.params !== null,
         };
     }
    +function summarizeToolCallArgs(args) {
    +    if (args === undefined || args === null) {
    +        return { argsType: args === null ? 'null' : 'undefined' };
    +    }
    +    if (typeof args !== 'object' || Array.isArray(args)) {
    +        let size;
    +        if (typeof args === 'string')
    +            size = args.length;
    +        return {
    +            argsType: Array.isArray(args) ? 'array' : typeof args,
    +            ...(size !== undefined ? { size } : {}),
    +        };
    +    }
    +    const keys = Object.keys(args);
    +    let size;
    +    try {
    +        size = JSON.stringify(args).length;
    +    }
    +    catch {
    +        size = undefined;
    +    }
    +    return {
    +        argsType: 'object',
    +        argsKeys: keys,
    +        hasNestedOutput: keys.includes('output'),
    +        ...(size !== undefined ? { size } : {}),
    +    };
    +}
     //# sourceMappingURL=redaction.js.map
    \ No newline at end of file
    
  • dist/utils/redaction.js.map+1 1 modified
    @@ -1 +1 @@
    -{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../../src/utils/redaction.ts"],"names":[],"mappings":";;;AAoBA,sCAWC;AAMD,4CAcC;AA9CD,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,eAAe;IACf,qBAAqB;IACrB,QAAQ;IACR,YAAY;IACZ,WAAW;IACX,WAAW;CACZ,CAAC,CAAC;AAEU,QAAA,QAAQ,GAAG,YAAY,CAAC;AAMrC,SAAgB,aAAa,CAC3B,OAAmD;IAEnD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,GAAG,CAAC,GAAG,CAAC,GAAG,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,gBAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAMD,SAAgB,gBAAgB,CAAC,IAAa;IAC5C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5D,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;IACnE,CAAC;IACD,MAAM,CAAC,GAAG,IAA+B,CAAC;IAC1C,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC9D,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QAC3D,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;QAC3E,SAAS,EAAE,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI;KACvD,CAAC;AACJ,CAAC"}
    \ No newline at end of file
    +{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../../src/utils/redaction.ts"],"names":[],"mappings":";;;AAoBA,sCAWC;AAMD,4CAcC;AAOD,sDAyBC;AA9ED,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,eAAe;IACf,qBAAqB;IACrB,QAAQ;IACR,YAAY;IACZ,WAAW;IACX,WAAW;CACZ,CAAC,CAAC;AAEU,QAAA,QAAQ,GAAG,YAAY,CAAC;AAMrC,SAAgB,aAAa,CAC3B,OAAmD;IAEnD,IAAI,CAAC,OAAO,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAC5C,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAA4B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,GAAG,CAAC,GAAG,CAAC,GAAG,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,CAAC,gBAAQ,CAAC,CAAC,CAAC,KAAK,CAAC;IAC9E,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAMD,SAAgB,gBAAgB,CAAC,IAAa;IAC5C,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5D,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,OAAO,EAAE,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC;IACnE,CAAC;IACD,MAAM,CAAC,GAAG,IAA+B,CAAC;IAC1C,OAAO;QACL,OAAO,EAAE,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;QAC9D,MAAM,EAAE,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS;QAC3D,EAAE,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,IAAI,OAAO,CAAC,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;QAC3E,SAAS,EAAE,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,MAAM,KAAK,IAAI;KACvD,CAAC;AACJ,CAAC;AAOD,SAAgB,qBAAqB,CAAC,IAAa;IACjD,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QACxC,OAAO,EAAE,QAAQ,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC5D,CAAC;IACD,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,IAAI,IAAwB,CAAC;QAC7B,IAAI,OAAO,IAAI,KAAK,QAAQ;YAAE,IAAI,GAAG,IAAI,CAAC,MAAM,CAAC;QACjD,OAAO;YACL,QAAQ,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,IAAI;YACrD,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACxC,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,IAA+B,CAAC,CAAC;IAC1D,IAAI,IAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;IACrC,CAAC;IAAC,MAAM,CAAC;QACP,IAAI,GAAG,SAAS,CAAC;IACnB,CAAC;IACD,OAAO;QACL,QAAQ,EAAE,QAAQ;QAClB,QAAQ,EAAE,IAAI;QACd,eAAe,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QACxC,GAAG,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACxC,CAAC;AACJ,CAAC"}
    \ No newline at end of file
    
  • package.json+1 1 modified
    @@ -1,6 +1,6 @@
     {
       "name": "n8n-mcp",
    -  "version": "2.47.12",
    +  "version": "2.47.13",
       "description": "Integration between n8n workflow automation and Model Context Protocol (MCP)",
       "main": "dist/index.js",
       "types": "dist/index.d.ts",
    
  • src/mcp/server.ts+21 24 modified
    @@ -15,6 +15,7 @@ import { n8nManagementTools } from './tools-n8n-manager';
     import { makeToolsN8nFriendly } from './tools-n8n-friendly';
     import { getWorkflowExampleString } from './workflow-examples';
     import { logger } from '../utils/logger';
    +import { summarizeToolCallArgs } from '../utils/redaction';
     import { NodeRepository } from '../database/node-repository';
     import { DatabaseAdapter, createDatabaseAdapter } from '../database/database-adapter';
     import { getSharedDatabase, releaseSharedDatabase, SharedDatabaseState } from '../database/shared-database';
    @@ -686,16 +687,12 @@ export class N8NDocumentationMCPServer {
         this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
           const { name, arguments: args } = request.params;
           
    -      // Enhanced logging for debugging tool calls
    -      logger.info('Tool call received - DETAILED DEBUG', {
    +      // SECURITY (GHSA-wg4g-395p-mqv3): log metadata only, not raw arg values.
    +      logger.info('Tool call received', {
             toolName: name,
    -        arguments: JSON.stringify(args, null, 2),
    -        argumentsType: typeof args,
    -        argumentsKeys: args ? Object.keys(args) : [],
    -        hasNodeType: args && 'nodeType' in args,
    -        hasConfig: args && 'config' in args,
    -        configType: args && args.config ? typeof args.config : 'N/A',
    -        rawRequest: JSON.stringify(request.params)
    +        ...summarizeToolCallArgs(args),
    +        hasNodeType: !!(args && typeof args === 'object' && 'nodeType' in args),
    +        hasConfig: !!(args && typeof args === 'object' && 'config' in args),
           });
     
           // Check if tool is disabled via DISABLED_TOOLS environment variable
    @@ -738,19 +735,21 @@ export class N8NDocumentationMCPServer {
               if (typeof possibleNestedData === 'string' && possibleNestedData.trim().startsWith('{')) {
                 const parsed = JSON.parse(possibleNestedData);
                 if (parsed && typeof parsed === 'object') {
    +              // SECURITY (GHSA-wg4g-395p-mqv3): log key shape only, not values.
                   logger.warn('Detected n8n nested output bug, attempting to extract actual arguments', {
    -                originalArgs: args,
    -                extractedArgs: parsed
    +                toolName: name,
    +                originalArgsKeys: Object.keys(args),
    +                extractedArgsKeys: Object.keys(parsed),
                   });
    -              
    +
                   // Validate the extracted arguments match expected tool schema
                   if (this.validateExtractedArgs(name, parsed)) {
                     // Use the extracted data as args
                     processedArgs = parsed;
                   } else {
                     logger.warn('Extracted arguments failed validation, using original args', {
                       toolName: name,
    -                  extractedArgs: parsed
    +                  extractedArgsKeys: Object.keys(parsed),
                     });
                   }
                 }
    @@ -776,7 +775,8 @@ export class N8NDocumentationMCPServer {
           }
     
           try {
    -        logger.debug(`Executing tool: ${name}`, { args: processedArgs });
    +        // SECURITY (GHSA-wg4g-395p-mqv3): log metadata only, not raw arg values.
    +        logger.debug(`Executing tool: ${name}`, summarizeToolCallArgs(processedArgs));
             const startTime = Date.now();
             const result = await this.executeTool(name, processedArgs);
             const duration = Date.now() - startTime;
    @@ -1152,8 +1152,8 @@ export class N8NDocumentationMCPServer {
           if (!(requiredField in args)) {
             logger.debug(`Extracted args missing required field: ${requiredField}`, {
               toolName,
    -          extractedArgs: args,
    -          required
    +          extractedArgsKeys: Object.keys(args),
    +          required,
             });
             return false;
           }
    @@ -1172,11 +1172,11 @@ export class N8NDocumentationMCPServer {
                 continue;
               }
               
    +          // SECURITY (GHSA-wg4g-395p-mqv3): log type mismatch shape only, not the value.
               logger.debug(`Extracted args field type mismatch: ${fieldName}`, {
                 toolName,
                 expectedType,
                 actualType,
    -            fieldValue
               });
               return false;
             }
    @@ -1306,9 +1306,10 @@ export class N8NDocumentationMCPServer {
         }
     
         if (coercedAny) {
    +      // SECURITY (GHSA-wg4g-395p-mqv3): log key-level types only, never values.
           logger.warn(`Coerced mistyped params for tool "${toolName}"`, {
             original: Object.fromEntries(
    -          Object.entries(args).map(([k, v]) => [k, `${typeof v}: ${typeof v === 'string' ? v.substring(0, 80) : v}`])
    +          Object.entries(args).map(([k, v]) => [k, typeof v])
             ),
           });
         }
    @@ -1328,12 +1329,8 @@ export class N8NDocumentationMCPServer {
           throw new Error(`Tool '${name}' is disabled via DISABLED_TOOLS environment variable`);
         }
     
    -    // Log the tool call for debugging n8n issues
    -    logger.info(`Tool execution: ${name}`, {
    -      args: typeof args === 'object' ? JSON.stringify(args) : args,
    -      argsType: typeof args,
    -      argsKeys: typeof args === 'object' ? Object.keys(args) : 'not-object'
    -    });
    +    // SECURITY (GHSA-wg4g-395p-mqv3): log metadata only, not raw arg values.
    +    logger.info(`Tool execution: ${name}`, summarizeToolCallArgs(args));
     
         // Validate that args is actually an object
         if (typeof args !== 'object' || args === null) {
    
  • src/utils/redaction.ts+32 0 modified
    @@ -50,3 +50,35 @@ export function summarizeMcpBody(body: unknown): Record<string, unknown> {
         hasParams: b.params !== undefined && b.params !== null,
       };
     }
    +
    +/**
    + * Reduce MCP tool-call arguments to a safe metadata summary. Returns the type,
    + * the top-level key list, a hasNestedOutput flag for the n8n nested-output
    + * workaround, and an approximate serialized size. Never returns values.
    + */
    +export function summarizeToolCallArgs(args: unknown): Record<string, unknown> {
    +  if (args === undefined || args === null) {
    +    return { argsType: args === null ? 'null' : 'undefined' };
    +  }
    +  if (typeof args !== 'object' || Array.isArray(args)) {
    +    let size: number | undefined;
    +    if (typeof args === 'string') size = args.length;
    +    return {
    +      argsType: Array.isArray(args) ? 'array' : typeof args,
    +      ...(size !== undefined ? { size } : {}),
    +    };
    +  }
    +  const keys = Object.keys(args as Record<string, unknown>);
    +  let size: number | undefined;
    +  try {
    +    size = JSON.stringify(args).length;
    +  } catch {
    +    size = undefined;
    +  }
    +  return {
    +    argsType: 'object',
    +    argsKeys: keys,
    +    hasNestedOutput: keys.includes('output'),
    +    ...(size !== undefined ? { size } : {}),
    +  };
    +}
    
  • tests/unit/mcp/server-tool-call-redaction.test.ts+105 0 added
    @@ -0,0 +1,105 @@
    +import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest';
    +
    +vi.mock('../../../src/database/database-adapter');
    +vi.mock('../../../src/database/node-repository');
    +vi.mock('../../../src/templates/template-service');
    +vi.mock('../../../src/utils/logger', () => ({
    +  logger: {
    +    info: vi.fn(),
    +    warn: vi.fn(),
    +    debug: vi.fn(),
    +    error: vi.fn(),
    +  },
    +  Logger: class {},
    +  LogLevel: { ERROR: 0, WARN: 1, INFO: 2, DEBUG: 3 },
    +}));
    +
    +import { N8NDocumentationMCPServer } from '../../../src/mcp/server';
    +import { logger } from '../../../src/utils/logger';
    +
    +const SECRET = 'Bearer DEMO_SECRET_DO_NOT_LEAK';
    +
    +class TestableServer extends N8NDocumentationMCPServer {
    +  public callCoerce(toolName: string, args: Record<string, any>) {
    +    return (this as any).coerceStringifiedJsonParams(toolName, args);
    +  }
    +  public callValidateExtracted(toolName: string, args: Record<string, any>) {
    +    return (this as any).validateExtractedArgs(toolName, args);
    +  }
    +}
    +
    +function allLogPayloads(): string {
    +  const calls = [
    +    ...(logger.info as any).mock.calls,
    +    ...(logger.warn as any).mock.calls,
    +    ...(logger.debug as any).mock.calls,
    +    ...(logger.error as any).mock.calls,
    +  ];
    +  return JSON.stringify(calls);
    +}
    +
    +describe('server tool-call log redaction (GHSA-wg4g-395p-mqv3)', () => {
    +  let server: TestableServer;
    +
    +  beforeEach(() => {
    +    process.env.NODE_DB_PATH = ':memory:';
    +    vi.clearAllMocks();
    +    server = new TestableServer();
    +  });
    +
    +  afterEach(() => {
    +    delete process.env.NODE_DB_PATH;
    +  });
    +
    +  it('does not leak secret values when executeTool is called with credential payload', async () => {
    +    await server.executeTool('n8n_manage_credentials', {
    +      action: 'create',
    +      name: 'demo',
    +      type: 'httpHeaderAuth',
    +      data: { name: 'Authorization', value: SECRET },
    +    }).catch(() => {
    +      // executeTool may throw because n8n API is not configured in test env;
    +      // we only care about what was logged BEFORE the throw.
    +    });
    +
    +    // 'DEMO_SECRET' is a substring of SECRET, so this also rules out full-value leaks.
    +    expect(allLogPayloads()).not.toContain('DEMO_SECRET');
    +  });
    +
    +  it('does not leak secret values when coerceStringifiedJsonParams runs', () => {
    +    server.callCoerce('validate_node', {
    +      nodeType: 'nodes-base.slack',
    +      config: SECRET,
    +    });
    +
    +    expect(allLogPayloads()).not.toContain('DEMO_SECRET');
    +  });
    +
    +  it('does not leak secret values from validateExtractedArgs type-mismatch path', () => {
    +    server.callValidateExtracted('search_nodes', {
    +      query: 123 as any,
    +      extra: SECRET,
    +    });
    +
    +    expect(allLogPayloads()).not.toContain('DEMO_SECRET');
    +  });
    +
    +  it('logs metadata (tool name, key list, types) so debugging stays useful', async () => {
    +    await server.executeTool('n8n_manage_credentials', {
    +      action: 'create',
    +      name: 'demo',
    +      data: { value: SECRET },
    +    }).catch(() => {});
    +
    +    const infoCalls = (logger.info as any).mock.calls;
    +    const executionLog = infoCalls.find((c: any[]) =>
    +      typeof c[0] === 'string' && c[0].includes('Tool execution')
    +    );
    +    expect(executionLog).toBeDefined();
    +    expect(executionLog[1]).toMatchObject({
    +      argsType: 'object',
    +      argsKeys: ['action', 'name', 'data'],
    +    });
    +    expect(JSON.stringify(executionLog[1])).not.toContain('DEMO_SECRET');
    +  });
    +});
    
  • tests/unit/utils/redaction.test.ts+42 1 modified
    @@ -1,5 +1,5 @@
     import { describe, it, expect } from 'vitest';
    -import { redactHeaders, summarizeMcpBody, REDACTED } from '../../../src/utils/redaction';
    +import { redactHeaders, summarizeMcpBody, summarizeToolCallArgs, REDACTED } from '../../../src/utils/redaction';
     
     describe('redactHeaders', () => {
       it('redacts authorization header', () => {
    @@ -121,3 +121,44 @@ describe('summarizeMcpBody', () => {
         expect(JSON.stringify(result)).not.toContain('canary');
       });
     });
    +
    +describe('summarizeToolCallArgs', () => {
    +  it('omits values from objects but keeps the key list', () => {
    +    const result = summarizeToolCallArgs({
    +      action: 'create',
    +      name: 'demo',
    +      type: 'httpHeaderAuth',
    +      data: { name: 'Authorization', value: 'Bearer DEMO_SECRET' },
    +    });
    +    expect(result.argsType).toBe('object');
    +    expect(result.argsKeys).toEqual(['action', 'name', 'type', 'data']);
    +    expect(result.hasNestedOutput).toBe(false);
    +    expect(typeof result.size).toBe('number');
    +    expect(JSON.stringify(result)).not.toContain('DEMO_SECRET');
    +    expect(JSON.stringify(result)).not.toContain('Bearer');
    +  });
    +
    +  it('flags hasNestedOutput when args includes output', () => {
    +    const result = summarizeToolCallArgs({ output: '{"nested":true}' });
    +    expect(result.hasNestedOutput).toBe(true);
    +    expect(JSON.stringify(result)).not.toContain('nested');
    +  });
    +
    +  it('returns string type and size for string args without leaking content', () => {
    +    const result = summarizeToolCallArgs('Bearer DEMO_SECRET');
    +    expect(result.argsType).toBe('string');
    +    expect(result.size).toBe('Bearer DEMO_SECRET'.length);
    +    expect(JSON.stringify(result)).not.toContain('DEMO_SECRET');
    +  });
    +
    +  it('returns argsType placeholder for undefined and null', () => {
    +    expect(summarizeToolCallArgs(undefined)).toEqual({ argsType: 'undefined' });
    +    expect(summarizeToolCallArgs(null)).toEqual({ argsType: 'null' });
    +  });
    +
    +  it('returns argsType array for array args', () => {
    +    const result = summarizeToolCallArgs(['secret-value']);
    +    expect(result.argsType).toBe('array');
    +    expect(JSON.stringify(result)).not.toContain('secret-value');
    +  });
    +});
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.