import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import { EInvoice, FormatDetector } from '../../../ts/index.js'; import { PerformanceTracker } from '../performance.tracker.js'; const performanceTracker = new PerformanceTracker('SEC-06: Memory DoS Prevention'); tap.test('SEC-06: Memory DoS Prevention - should prevent memory exhaustion attacks', async () => { // Test 1: Large attribute count attack (reduced for practical testing) const largeAttributeAttack = await performanceTracker.measureAsync( 'large-attribute-count-attack', async () => { // Create XML with many attributes (reduced from 1M to 10K for practical testing) let attributes = ''; const attrCount = 10000; for (let i = 0; i < attrCount; i++) { attributes += ` attr${i}="value${i}"`; } const maliciousXML = ` test 2024-01-01 `; const startMemory = process.memoryUsage(); const startTime = Date.now(); try { await EInvoice.fromXml(maliciousXML); 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 }; } } ); console.log('Large attribute attack result:', largeAttributeAttack); expect(largeAttributeAttack.prevented).toEqual(true); // Test 2: Deep nesting attack (reduced depth) const deepNestingAttack = await performanceTracker.measureAsync( 'deep-nesting-attack', async () => { // Create deeply nested XML (reduced from 50K to 500 for practical testing) const depth = 500; let xml = '\n'; for (let i = 0; i < depth; i++) { xml += ``; } xml += 'data'; for (let i = 0; i < depth; i++) { xml += ``; } xml += 'test2024-01-01'; const startMemory = process.memoryUsage(); try { await EInvoice.fromXml(xml); 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 }; } } ); console.log('Deep nesting attack result:', deepNestingAttack); expect(deepNestingAttack.prevented).toEqual(true); // Test 3: Large element content const largeContentAttack = await performanceTracker.measureAsync( 'large-content-attack', async () => { // Create XML with very large content const contentSize = 10 * 1024 * 1024; // 10MB const largeContent = 'A'.repeat(contentSize); const xml = ` test ${largeContent} 2024-01-01 `; const startMemory = process.memoryUsage(); try { await EInvoice.fromXml(xml); const endMemory = process.memoryUsage(); const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; return { // Should handle large content efficiently efficient: memoryIncrease < contentSize * 3, // Allow up to 3x content size memoryIncrease, contentSize }; } catch (error) { return { efficient: true, rejected: true, error: error.message }; } } ); console.log('Large content attack result:', largeContentAttack); expect(largeContentAttack.efficient).toEqual(true); // Test 4: Entity expansion attack const entityExpansionAttack = await performanceTracker.measureAsync( 'entity-expansion-attack', async () => { // Billion laughs attack variant const xml = ` ]> &lol5; 2024-01-01 `; const startMemory = process.memoryUsage(); try { await EInvoice.fromXml(xml); const endMemory = process.memoryUsage(); const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; return { prevented: memoryIncrease < 10 * 1024 * 1024, // Less than 10MB memoryIncrease }; } catch (error) { // Parser should reject or limit entity expansion return { prevented: true, rejected: true, error: error.message }; } } ); console.log('Entity expansion attack result:', entityExpansionAttack); expect(entityExpansionAttack.prevented).toEqual(true); // Test 5: Quadratic blowup via attribute value normalization const quadraticBlowupAttack = await performanceTracker.measureAsync( 'quadratic-blowup-attack', async () => { // Create attribute with many spaces that might be normalized const spaceCount = 100000; const spaces = ' '.repeat(spaceCount); const xml = ` test 2024-01-01 `; const startTime = Date.now(); try { await EInvoice.fromXml(xml); const endTime = Date.now(); const timeTaken = endTime - startTime; return { prevented: timeTaken < 5000, // Should process in under 5 seconds timeTaken, spaceCount }; } catch (error) { return { prevented: true, rejected: true, error: error.message }; } } ); console.log('Quadratic blowup attack result:', quadraticBlowupAttack); expect(quadraticBlowupAttack.prevented).toEqual(true); // Test 6: Multiple large attachments const largeAttachmentsAttack = await performanceTracker.measureAsync( 'large-attachments-attack', async () => { // 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 += ` ${i} ${base64Data} `; } const xml = ` test 2024-01-01 ${attachments} `; const startMemory = process.memoryUsage(); try { await EInvoice.fromXml(xml); const endMemory = process.memoryUsage(); const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; return { // Should handle attachments efficiently efficient: memoryIncrease < attachmentSize * attachmentCount * 5, memoryIncrease, totalSize: attachmentSize * attachmentCount }; } catch (error) { return { efficient: true, rejected: true, error: error.message }; } } ); console.log('Large attachments attack result:', largeAttachmentsAttack); expect(largeAttachmentsAttack.efficient).toEqual(true); // Test 7: Format detection with large input const largeFormatDetection = await performanceTracker.measureAsync( 'large-format-detection', async () => { // Large input for format detection const size = 5 * 1024 * 1024; // 5MB const content = '' + 'A'.repeat(size) + ''; const startMemory = process.memoryUsage(); const startTime = Date.now(); try { const format = FormatDetector.detectFormat(content); const endMemory = process.memoryUsage(); const endTime = Date.now(); return { efficient: endTime - startTime < 1000, // Should be fast memoryIncrease: endMemory.heapUsed - startMemory.heapUsed, timeTaken: endTime - startTime, format }; } catch (error) { return { efficient: true, error: error.message }; } } ); console.log('Large format detection result:', largeFormatDetection); expect(largeFormatDetection.efficient).toEqual(true); console.log('Memory DoS prevention tests completed'); }); // Run the test tap.start();