mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 13:33:11 +00:00
fix: address critical security issues in template metadata
- Fix SQL injection vulnerability in template-repository.ts - Use proper parameterization with SQLite concatenation operator - Escape JSON strings correctly for LIKE queries - Prevent malicious SQL through filter parameters - Add input sanitization for OpenAI API calls - Sanitize template names and descriptions before sending to API - Remove control characters and prompt injection patterns - Limit input length to prevent token abuse - Lower temperature to 0.3 for consistent structured outputs - Add comprehensive test coverage - 100+ new tests for metadata functionality - Security-focused tests for SQL injection prevention - Integration tests with real database operations Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,9 @@ describe('TemplateService', () => {
|
||||
created_at: overrides.created_at || '2024-01-01T00:00:00Z',
|
||||
updated_at: overrides.updated_at || '2024-01-01T00:00:00Z',
|
||||
url: overrides.url || `https://n8n.io/workflows/${id}`,
|
||||
scraped_at: '2024-01-01T00:00:00Z'
|
||||
scraped_at: '2024-01-01T00:00:00Z',
|
||||
metadata_json: overrides.metadata_json || null,
|
||||
metadata_generated_at: overrides.metadata_generated_at || null
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -79,7 +81,9 @@ describe('TemplateService', () => {
|
||||
getExistingTemplateIds: vi.fn(),
|
||||
clearTemplates: vi.fn(),
|
||||
saveTemplate: vi.fn(),
|
||||
rebuildTemplateFTS: vi.fn()
|
||||
rebuildTemplateFTS: vi.fn(),
|
||||
searchTemplatesByMetadata: vi.fn(),
|
||||
getSearchTemplatesByMetadataCount: vi.fn()
|
||||
} as any;
|
||||
|
||||
// Mock the constructor
|
||||
@@ -520,6 +524,114 @@ describe('TemplateService', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchTemplatesByMetadata', () => {
|
||||
it('should return paginated metadata search results', async () => {
|
||||
const mockTemplates = [
|
||||
createMockTemplate(1, {
|
||||
name: 'AI Workflow',
|
||||
metadata_json: JSON.stringify({
|
||||
categories: ['ai', 'automation'],
|
||||
complexity: 'complex',
|
||||
estimated_setup_minutes: 60
|
||||
})
|
||||
}),
|
||||
createMockTemplate(2, {
|
||||
name: 'Simple Webhook',
|
||||
metadata_json: JSON.stringify({
|
||||
categories: ['automation'],
|
||||
complexity: 'simple',
|
||||
estimated_setup_minutes: 15
|
||||
})
|
||||
})
|
||||
];
|
||||
|
||||
mockRepository.searchTemplatesByMetadata = vi.fn().mockReturnValue(mockTemplates);
|
||||
mockRepository.getSearchTemplatesByMetadataCount = vi.fn().mockReturnValue(12);
|
||||
|
||||
const result = await service.searchTemplatesByMetadata({
|
||||
complexity: 'simple',
|
||||
maxSetupMinutes: 30
|
||||
}, 10, 5);
|
||||
|
||||
expect(result).toEqual({
|
||||
items: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
id: 1,
|
||||
name: 'AI Workflow',
|
||||
metadata: {
|
||||
categories: ['ai', 'automation'],
|
||||
complexity: 'complex',
|
||||
estimated_setup_minutes: 60
|
||||
}
|
||||
}),
|
||||
expect.objectContaining({
|
||||
id: 2,
|
||||
name: 'Simple Webhook',
|
||||
metadata: {
|
||||
categories: ['automation'],
|
||||
complexity: 'simple',
|
||||
estimated_setup_minutes: 15
|
||||
}
|
||||
})
|
||||
]),
|
||||
total: 12,
|
||||
limit: 10,
|
||||
offset: 5,
|
||||
hasMore: false // 5 + 10 >= 12
|
||||
});
|
||||
|
||||
expect(mockRepository.searchTemplatesByMetadata).toHaveBeenCalledWith({
|
||||
complexity: 'simple',
|
||||
maxSetupMinutes: 30
|
||||
}, 10, 5);
|
||||
expect(mockRepository.getSearchTemplatesByMetadataCount).toHaveBeenCalledWith({
|
||||
complexity: 'simple',
|
||||
maxSetupMinutes: 30
|
||||
});
|
||||
});
|
||||
|
||||
it('should use default pagination parameters', async () => {
|
||||
mockRepository.searchTemplatesByMetadata = vi.fn().mockReturnValue([]);
|
||||
mockRepository.getSearchTemplatesByMetadataCount = vi.fn().mockReturnValue(0);
|
||||
|
||||
await service.searchTemplatesByMetadata({ category: 'test' });
|
||||
|
||||
expect(mockRepository.searchTemplatesByMetadata).toHaveBeenCalledWith({ category: 'test' }, 20, 0);
|
||||
});
|
||||
|
||||
it('should handle templates without metadata gracefully', async () => {
|
||||
const templatesWithoutMetadata = [
|
||||
createMockTemplate(1, { metadata_json: null }),
|
||||
createMockTemplate(2, { metadata_json: undefined }),
|
||||
createMockTemplate(3, { metadata_json: 'invalid json' })
|
||||
];
|
||||
|
||||
mockRepository.searchTemplatesByMetadata = vi.fn().mockReturnValue(templatesWithoutMetadata);
|
||||
mockRepository.getSearchTemplatesByMetadataCount = vi.fn().mockReturnValue(3);
|
||||
|
||||
const result = await service.searchTemplatesByMetadata({ category: 'test' });
|
||||
|
||||
expect(result.items).toHaveLength(3);
|
||||
result.items.forEach(item => {
|
||||
expect(item.metadata).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle malformed metadata JSON', async () => {
|
||||
const templateWithBadMetadata = createMockTemplate(1, {
|
||||
metadata_json: '{"invalid": json syntax}'
|
||||
});
|
||||
|
||||
mockRepository.searchTemplatesByMetadata = vi.fn().mockReturnValue([templateWithBadMetadata]);
|
||||
mockRepository.getSearchTemplatesByMetadataCount = vi.fn().mockReturnValue(1);
|
||||
|
||||
const result = await service.searchTemplatesByMetadata({ category: 'test' });
|
||||
|
||||
expect(result.items).toHaveLength(1);
|
||||
expect(result.items[0].metadata).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
describe('formatTemplateInfo (private method behavior)', () => {
|
||||
it('should format template data correctly through public methods', async () => {
|
||||
const mockTemplate = createMockTemplate(1, {
|
||||
|
||||
Reference in New Issue
Block a user