2025-05-29 13:35:36 +00:00
|
|
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
2025-05-26 04:04:51 +00:00
|
|
|
import * as plugins from '../plugins.js';
|
2025-05-29 13:35:36 +00:00
|
|
|
import { EInvoice, FormatDetector } from '../../../ts/index.js';
|
2025-05-26 04:04:51 +00:00
|
|
|
import { PerformanceTracker } from '../performance.tracker.js';
|
|
|
|
|
|
|
|
const performanceTracker = new PerformanceTracker('SEC-06: Memory DoS Prevention');
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
tap.test('SEC-06: Memory DoS Prevention - should prevent memory exhaustion attacks', async () => {
|
|
|
|
// Test 1: Large attribute count attack (reduced for practical testing)
|
2025-05-26 04:04:51 +00:00
|
|
|
const largeAttributeAttack = await performanceTracker.measureAsync(
|
|
|
|
'large-attribute-count-attack',
|
|
|
|
async () => {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Create XML with many attributes (reduced from 1M to 10K for practical testing)
|
2025-05-26 04:04:51 +00:00
|
|
|
let attributes = '';
|
2025-05-29 13:35:36 +00:00
|
|
|
const attrCount = 10000;
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
for (let i = 0; i < attrCount; i++) {
|
|
|
|
attributes += ` attr${i}="value${i}"`;
|
|
|
|
}
|
|
|
|
|
|
|
|
const maliciousXML = `<?xml version="1.0" encoding="UTF-8"?>
|
2025-05-29 13:35:36 +00:00
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2" ${attributes}>
|
2025-05-26 04:04:51 +00:00
|
|
|
<ID>test</ID>
|
2025-05-29 13:35:36 +00:00
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
2025-05-26 04:04:51 +00:00
|
|
|
</Invoice>`;
|
|
|
|
|
|
|
|
const startMemory = process.memoryUsage();
|
|
|
|
const startTime = Date.now();
|
|
|
|
|
|
|
|
try {
|
2025-05-29 13:35:36 +00:00
|
|
|
await EInvoice.fromXml(maliciousXML);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
const endMemory = process.memoryUsage();
|
|
|
|
const endTime = Date.now();
|
|
|
|
|
|
|
|
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
|
|
|
|
const timeTaken = endTime - startTime;
|
|
|
|
|
|
|
|
return {
|
|
|
|
prevented: memoryIncrease < 100 * 1024 * 1024, // Less than 100MB
|
|
|
|
memoryIncrease,
|
|
|
|
timeTaken,
|
|
|
|
attributeCount: attrCount
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
|
|
|
prevented: true,
|
|
|
|
rejected: true,
|
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
console.log('Large attribute attack result:', largeAttributeAttack);
|
|
|
|
expect(largeAttributeAttack.prevented).toEqual(true);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
// Test 2: Deep nesting attack (reduced depth)
|
|
|
|
const deepNestingAttack = await performanceTracker.measureAsync(
|
|
|
|
'deep-nesting-attack',
|
2025-05-26 04:04:51 +00:00
|
|
|
async () => {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Create deeply nested XML (reduced from 50K to 500 for practical testing)
|
|
|
|
const depth = 500;
|
|
|
|
let xml = '<?xml version="1.0" encoding="UTF-8"?>\n<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">';
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
for (let i = 0; i < depth; i++) {
|
2025-05-29 13:35:36 +00:00
|
|
|
xml += `<Note>`;
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
xml += 'data';
|
2025-05-29 13:35:36 +00:00
|
|
|
for (let i = 0; i < depth; i++) {
|
|
|
|
xml += `</Note>`;
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
2025-05-29 13:35:36 +00:00
|
|
|
xml += '<ID>test</ID><IssueDate>2024-01-01</IssueDate></Invoice>';
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
const startMemory = process.memoryUsage();
|
|
|
|
|
|
|
|
try {
|
2025-05-29 13:35:36 +00:00
|
|
|
await EInvoice.fromXml(xml);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
const endMemory = process.memoryUsage();
|
|
|
|
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
|
|
|
|
|
|
|
|
return {
|
|
|
|
prevented: memoryIncrease < 50 * 1024 * 1024, // Less than 50MB
|
|
|
|
memoryIncrease,
|
|
|
|
depth
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
// Stack overflow or depth limit is also prevention
|
|
|
|
return {
|
|
|
|
prevented: true,
|
|
|
|
rejected: true,
|
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
console.log('Deep nesting attack result:', deepNestingAttack);
|
|
|
|
expect(deepNestingAttack.prevented).toEqual(true);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
// Test 3: Large element content
|
|
|
|
const largeContentAttack = await performanceTracker.measureAsync(
|
|
|
|
'large-content-attack',
|
2025-05-26 04:04:51 +00:00
|
|
|
async () => {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Create XML with very large content
|
|
|
|
const contentSize = 10 * 1024 * 1024; // 10MB
|
|
|
|
const largeContent = 'A'.repeat(contentSize);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
2025-05-26 04:04:51 +00:00
|
|
|
<ID>test</ID>
|
2025-05-29 13:35:36 +00:00
|
|
|
<Note>${largeContent}</Note>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
2025-05-26 04:04:51 +00:00
|
|
|
</Invoice>`;
|
|
|
|
|
|
|
|
const startMemory = process.memoryUsage();
|
|
|
|
|
|
|
|
try {
|
2025-05-29 13:35:36 +00:00
|
|
|
await EInvoice.fromXml(xml);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
const endMemory = process.memoryUsage();
|
|
|
|
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
|
|
|
|
|
|
|
|
return {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Should handle large content efficiently
|
|
|
|
efficient: memoryIncrease < contentSize * 3, // Allow up to 3x content size
|
2025-05-26 04:04:51 +00:00
|
|
|
memoryIncrease,
|
2025-05-29 13:35:36 +00:00
|
|
|
contentSize
|
2025-05-26 04:04:51 +00:00
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
2025-05-29 13:35:36 +00:00
|
|
|
efficient: true,
|
|
|
|
rejected: true,
|
|
|
|
error: error.message
|
2025-05-26 04:04:51 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
console.log('Large content attack result:', largeContentAttack);
|
|
|
|
expect(largeContentAttack.efficient).toEqual(true);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
// Test 4: Entity expansion attack
|
|
|
|
const entityExpansionAttack = await performanceTracker.measureAsync(
|
|
|
|
'entity-expansion-attack',
|
2025-05-26 04:04:51 +00:00
|
|
|
async () => {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Billion laughs attack variant
|
|
|
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<!DOCTYPE lolz [
|
|
|
|
<!ENTITY lol "lol">
|
|
|
|
<!ENTITY lol2 "&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;">
|
|
|
|
<!ENTITY lol3 "&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;">
|
|
|
|
<!ENTITY lol4 "&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;">
|
|
|
|
<!ENTITY lol5 "&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;">
|
2025-05-26 04:04:51 +00:00
|
|
|
]>
|
2025-05-29 13:35:36 +00:00
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
|
|
<ID>&lol5;</ID>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
2025-05-26 04:04:51 +00:00
|
|
|
</Invoice>`;
|
|
|
|
|
|
|
|
const startMemory = process.memoryUsage();
|
|
|
|
|
|
|
|
try {
|
2025-05-29 13:35:36 +00:00
|
|
|
await EInvoice.fromXml(xml);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
const endMemory = process.memoryUsage();
|
|
|
|
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
|
|
|
|
|
|
|
|
return {
|
2025-05-29 13:35:36 +00:00
|
|
|
prevented: memoryIncrease < 10 * 1024 * 1024, // Less than 10MB
|
|
|
|
memoryIncrease
|
2025-05-26 04:04:51 +00:00
|
|
|
};
|
|
|
|
} catch (error) {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Parser should reject or limit entity expansion
|
2025-05-26 04:04:51 +00:00
|
|
|
return {
|
|
|
|
prevented: true,
|
|
|
|
rejected: true,
|
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
console.log('Entity expansion attack result:', entityExpansionAttack);
|
|
|
|
expect(entityExpansionAttack.prevented).toEqual(true);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
// Test 5: Quadratic blowup via attribute value normalization
|
|
|
|
const quadraticBlowupAttack = await performanceTracker.measureAsync(
|
|
|
|
'quadratic-blowup-attack',
|
2025-05-26 04:04:51 +00:00
|
|
|
async () => {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Create attribute with many spaces that might be normalized
|
|
|
|
const spaceCount = 100000;
|
|
|
|
const spaces = ' '.repeat(spaceCount);
|
|
|
|
|
|
|
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
|
|
<ID attr="${spaces}">test</ID>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
2025-05-26 04:04:51 +00:00
|
|
|
</Invoice>`;
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
const startTime = Date.now();
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
try {
|
2025-05-29 13:35:36 +00:00
|
|
|
await EInvoice.fromXml(xml);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
const endTime = Date.now();
|
|
|
|
const timeTaken = endTime - startTime;
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
return {
|
2025-05-29 13:35:36 +00:00
|
|
|
prevented: timeTaken < 5000, // Should process in under 5 seconds
|
|
|
|
timeTaken,
|
|
|
|
spaceCount
|
2025-05-26 04:04:51 +00:00
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
|
|
|
prevented: true,
|
2025-05-29 13:35:36 +00:00
|
|
|
rejected: true,
|
|
|
|
error: error.message
|
2025-05-26 04:04:51 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
console.log('Quadratic blowup attack result:', quadraticBlowupAttack);
|
|
|
|
expect(quadraticBlowupAttack.prevented).toEqual(true);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
// Test 6: Multiple large attachments
|
|
|
|
const largeAttachmentsAttack = await performanceTracker.measureAsync(
|
|
|
|
'large-attachments-attack',
|
2025-05-26 04:04:51 +00:00
|
|
|
async () => {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Create multiple large base64 attachments
|
|
|
|
const attachmentSize = 1 * 1024 * 1024; // 1MB each
|
|
|
|
const attachmentCount = 10;
|
|
|
|
const base64Data = Buffer.from('A'.repeat(attachmentSize)).toString('base64');
|
|
|
|
|
|
|
|
let attachments = '';
|
|
|
|
for (let i = 0; i < attachmentCount; i++) {
|
|
|
|
attachments += `
|
|
|
|
<AdditionalDocumentReference>
|
|
|
|
<ID>${i}</ID>
|
|
|
|
<Attachment>
|
|
|
|
<EmbeddedDocumentBinaryObject mimeCode="application/pdf">
|
|
|
|
${base64Data}
|
|
|
|
</EmbeddedDocumentBinaryObject>
|
|
|
|
</Attachment>
|
|
|
|
</AdditionalDocumentReference>`;
|
2025-05-26 04:04:51 +00:00
|
|
|
}
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
|
|
|
|
<ID>test</ID>
|
|
|
|
<IssueDate>2024-01-01</IssueDate>
|
|
|
|
${attachments}
|
2025-05-26 04:04:51 +00:00
|
|
|
</Invoice>`;
|
|
|
|
|
|
|
|
const startMemory = process.memoryUsage();
|
|
|
|
|
|
|
|
try {
|
2025-05-29 13:35:36 +00:00
|
|
|
await EInvoice.fromXml(xml);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
const endMemory = process.memoryUsage();
|
|
|
|
const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed;
|
|
|
|
|
|
|
|
return {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Should handle attachments efficiently
|
|
|
|
efficient: memoryIncrease < attachmentSize * attachmentCount * 5,
|
2025-05-26 04:04:51 +00:00
|
|
|
memoryIncrease,
|
2025-05-29 13:35:36 +00:00
|
|
|
totalSize: attachmentSize * attachmentCount
|
2025-05-26 04:04:51 +00:00
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
2025-05-29 13:35:36 +00:00
|
|
|
efficient: true,
|
2025-05-26 04:04:51 +00:00
|
|
|
rejected: true,
|
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
console.log('Large attachments attack result:', largeAttachmentsAttack);
|
|
|
|
expect(largeAttachmentsAttack.efficient).toEqual(true);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
// Test 7: Format detection with large input
|
|
|
|
const largeFormatDetection = await performanceTracker.measureAsync(
|
|
|
|
'large-format-detection',
|
2025-05-26 04:04:51 +00:00
|
|
|
async () => {
|
2025-05-29 13:35:36 +00:00
|
|
|
// Large input for format detection
|
|
|
|
const size = 5 * 1024 * 1024; // 5MB
|
|
|
|
const content = '<xml>' + 'A'.repeat(size) + '</xml>';
|
|
|
|
|
2025-05-26 04:04:51 +00:00
|
|
|
const startMemory = process.memoryUsage();
|
2025-05-29 13:35:36 +00:00
|
|
|
const startTime = Date.now();
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
try {
|
2025-05-29 13:35:36 +00:00
|
|
|
const format = FormatDetector.detectFormat(content);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
const endMemory = process.memoryUsage();
|
2025-05-29 13:35:36 +00:00
|
|
|
const endTime = Date.now();
|
2025-05-26 04:04:51 +00:00
|
|
|
|
|
|
|
return {
|
2025-05-29 13:35:36 +00:00
|
|
|
efficient: endTime - startTime < 1000, // Should be fast
|
|
|
|
memoryIncrease: endMemory.heapUsed - startMemory.heapUsed,
|
|
|
|
timeTaken: endTime - startTime,
|
|
|
|
format
|
2025-05-26 04:04:51 +00:00
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
2025-05-29 13:35:36 +00:00
|
|
|
efficient: true,
|
2025-05-26 04:04:51 +00:00
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
console.log('Large format detection result:', largeFormatDetection);
|
|
|
|
expect(largeFormatDetection.efficient).toEqual(true);
|
2025-05-26 04:04:51 +00:00
|
|
|
|
2025-05-29 13:35:36 +00:00
|
|
|
console.log('Memory DoS prevention tests completed');
|
2025-05-26 04:04:51 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// Run the test
|
|
|
|
tap.start();
|