mirror of
https://github.com/czlonkowski/n8n-mcp.git
synced 2026-02-06 13:33:11 +00:00
fix: resolve final template security test failures
- Fix getTemplatesByCategory to use parameterized SQL concatenation - Fix searchTemplatesByMetadata to handle empty string filters - Change truthy checks to explicit undefined checks for filter parameters - Update test expectations to match secure parameterization patterns All 21 tests in template-repository-security.test.ts now pass ✓ 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
BIN
data/nodes.db
BIN
data/nodes.db
Binary file not shown.
@@ -641,7 +641,7 @@ export class TemplateRepository {
|
|||||||
const params: any[] = [];
|
const params: any[] = [];
|
||||||
|
|
||||||
// Build WHERE conditions based on filters with proper parameterization
|
// Build WHERE conditions based on filters with proper parameterization
|
||||||
if (filters.category) {
|
if (filters.category !== undefined) {
|
||||||
// Use parameterized LIKE with JSON array search - safe from injection
|
// Use parameterized LIKE with JSON array search - safe from injection
|
||||||
conditions.push("json_extract(metadata_json, '$.categories') LIKE '%' || ? || '%'");
|
conditions.push("json_extract(metadata_json, '$.categories') LIKE '%' || ? || '%'");
|
||||||
// Escape special characters and quotes for JSON string matching
|
// Escape special characters and quotes for JSON string matching
|
||||||
@@ -664,7 +664,7 @@ export class TemplateRepository {
|
|||||||
params.push(filters.minSetupMinutes);
|
params.push(filters.minSetupMinutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.requiredService) {
|
if (filters.requiredService !== undefined) {
|
||||||
// Use parameterized LIKE with JSON array search - safe from injection
|
// Use parameterized LIKE with JSON array search - safe from injection
|
||||||
conditions.push("json_extract(metadata_json, '$.required_services') LIKE '%' || ? || '%'");
|
conditions.push("json_extract(metadata_json, '$.required_services') LIKE '%' || ? || '%'");
|
||||||
// Escape special characters and quotes for JSON string matching
|
// Escape special characters and quotes for JSON string matching
|
||||||
@@ -672,7 +672,7 @@ export class TemplateRepository {
|
|||||||
params.push(sanitizedService);
|
params.push(sanitizedService);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.targetAudience) {
|
if (filters.targetAudience !== undefined) {
|
||||||
// Use parameterized LIKE with JSON array search - safe from injection
|
// Use parameterized LIKE with JSON array search - safe from injection
|
||||||
conditions.push("json_extract(metadata_json, '$.target_audience') LIKE '%' || ? || '%'");
|
conditions.push("json_extract(metadata_json, '$.target_audience') LIKE '%' || ? || '%'");
|
||||||
// Escape special characters and quotes for JSON string matching
|
// Escape special characters and quotes for JSON string matching
|
||||||
@@ -708,7 +708,7 @@ export class TemplateRepository {
|
|||||||
const conditions: string[] = ['metadata_json IS NOT NULL'];
|
const conditions: string[] = ['metadata_json IS NOT NULL'];
|
||||||
const params: any[] = [];
|
const params: any[] = [];
|
||||||
|
|
||||||
if (filters.category) {
|
if (filters.category !== undefined) {
|
||||||
// Use parameterized LIKE with JSON array search - safe from injection
|
// Use parameterized LIKE with JSON array search - safe from injection
|
||||||
conditions.push("json_extract(metadata_json, '$.categories') LIKE '%' || ? || '%'");
|
conditions.push("json_extract(metadata_json, '$.categories') LIKE '%' || ? || '%'");
|
||||||
const sanitizedCategory = JSON.stringify(filters.category).slice(1, -1);
|
const sanitizedCategory = JSON.stringify(filters.category).slice(1, -1);
|
||||||
@@ -730,14 +730,14 @@ export class TemplateRepository {
|
|||||||
params.push(filters.minSetupMinutes);
|
params.push(filters.minSetupMinutes);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.requiredService) {
|
if (filters.requiredService !== undefined) {
|
||||||
// Use parameterized LIKE with JSON array search - safe from injection
|
// Use parameterized LIKE with JSON array search - safe from injection
|
||||||
conditions.push("json_extract(metadata_json, '$.required_services') LIKE '%' || ? || '%'");
|
conditions.push("json_extract(metadata_json, '$.required_services') LIKE '%' || ? || '%'");
|
||||||
const sanitizedService = JSON.stringify(filters.requiredService).slice(1, -1);
|
const sanitizedService = JSON.stringify(filters.requiredService).slice(1, -1);
|
||||||
params.push(sanitizedService);
|
params.push(sanitizedService);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters.targetAudience) {
|
if (filters.targetAudience !== undefined) {
|
||||||
// Use parameterized LIKE with JSON array search - safe from injection
|
// Use parameterized LIKE with JSON array search - safe from injection
|
||||||
conditions.push("json_extract(metadata_json, '$.target_audience') LIKE '%' || ? || '%'");
|
conditions.push("json_extract(metadata_json, '$.target_audience') LIKE '%' || ? || '%'");
|
||||||
const sanitizedAudience = JSON.stringify(filters.targetAudience).slice(1, -1);
|
const sanitizedAudience = JSON.stringify(filters.targetAudience).slice(1, -1);
|
||||||
@@ -785,12 +785,14 @@ export class TemplateRepository {
|
|||||||
const query = `
|
const query = `
|
||||||
SELECT * FROM templates
|
SELECT * FROM templates
|
||||||
WHERE metadata_json IS NOT NULL
|
WHERE metadata_json IS NOT NULL
|
||||||
AND json_extract(metadata_json, '$.categories') LIKE ?
|
AND json_extract(metadata_json, '$.categories') LIKE '%' || ? || '%'
|
||||||
ORDER BY views DESC, created_at DESC
|
ORDER BY views DESC, created_at DESC
|
||||||
LIMIT ? OFFSET ?
|
LIMIT ? OFFSET ?
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const results = this.db.prepare(query).all(`%"${category}"%`, limit, offset) as StoredTemplate[];
|
// Use same sanitization as searchTemplatesByMetadata for consistency
|
||||||
|
const sanitizedCategory = JSON.stringify(category).slice(1, -1);
|
||||||
|
const results = this.db.prepare(query).all(sanitizedCategory, limit, offset) as StoredTemplate[];
|
||||||
return results.map(t => this.decompressWorkflow(t));
|
return results.map(t => this.decompressWorkflow(t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -353,7 +353,7 @@ describe('TemplateRepository - Security Tests', () => {
|
|||||||
expect(capturedParams.length).toBeGreaterThan(0);
|
expect(capturedParams.length).toBeGreaterThan(0);
|
||||||
// Find the parameter that contains 'test'
|
// Find the parameter that contains 'test'
|
||||||
const testParam = capturedParams[0].find((p: any) => typeof p === 'string' && p.includes('test'));
|
const testParam = capturedParams[0].find((p: any) => typeof p === 'string' && p.includes('test'));
|
||||||
expect(testParam).toBe('%"test"%');
|
expect(testParam).toBe('test');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user