import { tap } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import { EInvoice } 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 (t) => { const einvoice = new EInvoice(); // Test 1: Large attribute count attack const largeAttributeAttack = await performanceTracker.measureAsync( 'large-attribute-count-attack', async () => { // Create XML with excessive attributes let attributes = ''; const attrCount = 1000000; for (let i = 0; i < attrCount; i++) { attributes += ` attr${i}="value${i}"`; } const maliciousXML = ` test `; const startMemory = process.memoryUsage(); const startTime = Date.now(); try { await einvoice.parseXML(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 }; } } ); t.ok(largeAttributeAttack.prevented, 'Large attribute count attack was prevented'); // Test 2: Deep recursion attack const deepRecursionAttack = await performanceTracker.measureAsync( 'deep-recursion-attack', async () => { // Create deeply nested XML const depth = 50000; let xml = '\n'; for (let i = 0; i < depth; i++) { xml += ``; } xml += 'data'; for (let i = depth - 1; i >= 0; i--) { xml += ``; } xml += ''; const startMemory = process.memoryUsage(); try { await einvoice.parseXML(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 }; } } ); t.ok(deepRecursionAttack.prevented, 'Deep recursion attack was prevented'); // Test 3: Large text node attack const largeTextNodeAttack = await performanceTracker.measureAsync( 'large-text-node-attack', async () => { // Create XML with huge text content const textSize = 500 * 1024 * 1024; // 500MB of text const chunk = 'A'.repeat(1024 * 1024); // 1MB chunks const maliciousXML = ` ${chunk} `; const startMemory = process.memoryUsage(); const startTime = Date.now(); try { // Simulate streaming or chunked processing for (let i = 0; i < 500; i++) { await einvoice.parseXML(maliciousXML); // Check memory growth const currentMemory = process.memoryUsage(); const memoryGrowth = currentMemory.heapUsed - startMemory.heapUsed; if (memoryGrowth > 200 * 1024 * 1024) { throw new Error('Memory limit exceeded'); } } const endTime = Date.now(); const finalMemory = process.memoryUsage(); return { prevented: false, memoryGrowth: finalMemory.heapUsed - startMemory.heapUsed, timeTaken: endTime - startTime }; } catch (error) { return { prevented: true, limited: true, error: error.message }; } } ); t.ok(largeTextNodeAttack.prevented, 'Large text node attack was prevented'); // Test 4: Namespace pollution attack const namespacePollutionAttack = await performanceTracker.measureAsync( 'namespace-pollution-attack', async () => { // Create XML with excessive namespaces let namespaces = ''; const nsCount = 100000; for (let i = 0; i < nsCount; i++) { namespaces += ` xmlns:ns${i}="http://example.com/ns${i}"`; } const maliciousXML = ` test `; const startMemory = process.memoryUsage(); try { await einvoice.parseXML(maliciousXML); const endMemory = process.memoryUsage(); const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; return { prevented: memoryIncrease < 50 * 1024 * 1024, memoryIncrease, namespaceCount: nsCount }; } catch (error) { return { prevented: true, rejected: true }; } } ); t.ok(namespacePollutionAttack.prevented, 'Namespace pollution attack was prevented'); // Test 5: Entity expansion memory attack const entityExpansionMemory = await performanceTracker.measureAsync( 'entity-expansion-memory-attack', async () => { // Create entities that expand exponentially const maliciousXML = ` ]> &level3; `; const startMemory = process.memoryUsage(); const memoryLimit = 100 * 1024 * 1024; // 100MB limit try { await einvoice.parseXML(maliciousXML); const endMemory = process.memoryUsage(); const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; return { prevented: memoryIncrease < memoryLimit, memoryIncrease, expansionFactor: Math.pow(10, 3) // Expected expansion }; } catch (error) { return { prevented: true, rejected: true, error: error.message }; } } ); t.ok(entityExpansionMemory.prevented, 'Entity expansion memory attack was prevented'); // Test 6: Array allocation attack const arrayAllocationAttack = await performanceTracker.measureAsync( 'array-allocation-attack', async () => { // Create XML that forces large array allocations let elements = ''; const elementCount = 10000000; for (let i = 0; i < elementCount; i++) { elements += ``; } const maliciousXML = ` ${elements} `; const startMemory = process.memoryUsage(); try { await einvoice.parseXML(maliciousXML); const endMemory = process.memoryUsage(); const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; return { prevented: memoryIncrease < 200 * 1024 * 1024, memoryIncrease, elementCount }; } catch (error) { return { prevented: true, rejected: true }; } } ); t.ok(arrayAllocationAttack.prevented, 'Array allocation attack was prevented'); // Test 7: Memory leak through repeated operations const memoryLeakTest = await performanceTracker.measureAsync( 'memory-leak-prevention', async () => { const iterations = 1000; const samples = []; // Force GC if available if (global.gc) { global.gc(); } const baselineMemory = process.memoryUsage().heapUsed; for (let i = 0; i < iterations; i++) { const xml = ` INV-${i} ${Math.random() * 1000} `; await einvoice.parseXML(xml); if (i % 100 === 0) { // Sample memory every 100 iterations const currentMemory = process.memoryUsage().heapUsed; samples.push({ iteration: i, memory: currentMemory - baselineMemory }); } } // Calculate memory growth trend const firstSample = samples[0]; const lastSample = samples[samples.length - 1]; const memoryGrowthRate = (lastSample.memory - firstSample.memory) / (lastSample.iteration - firstSample.iteration); return { prevented: memoryGrowthRate < 1000, // Less than 1KB per iteration memoryGrowthRate, totalIterations: iterations, samples }; } ); t.ok(memoryLeakTest.prevented, 'Memory leak through repeated operations was prevented'); // Test 8: Concurrent memory attacks const concurrentMemoryAttack = await performanceTracker.measureAsync( 'concurrent-memory-attacks', async () => { const concurrentAttacks = 10; const startMemory = process.memoryUsage(); // Create multiple large XML documents const createLargeXML = (id: number) => { const size = 10 * 1024 * 1024; // 10MB const data = 'X'.repeat(size); return ` ${id} ${data} `; }; try { // Process multiple large documents concurrently const promises = []; for (let i = 0; i < concurrentAttacks; i++) { promises.push(einvoice.parseXML(createLargeXML(i))); } await Promise.all(promises); const endMemory = process.memoryUsage(); const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; return { prevented: memoryIncrease < 500 * 1024 * 1024, // Less than 500MB total memoryIncrease, concurrentCount: concurrentAttacks }; } catch (error) { return { prevented: true, rejected: true, error: error.message }; } } ); t.ok(concurrentMemoryAttack.prevented, 'Concurrent memory attacks were prevented'); // Test 9: Cache pollution attack const cachePollutionAttack = await performanceTracker.measureAsync( 'cache-pollution-attack', async () => { const uniqueDocuments = 10000; const startMemory = process.memoryUsage(); try { // Parse many unique documents to pollute cache for (let i = 0; i < uniqueDocuments; i++) { const xml = ` ID-${Math.random()}-${Date.now()}-${i} ${Math.random().toString(36).substring(2)} `; await einvoice.parseXML(xml); // Check memory growth periodically if (i % 1000 === 0) { const currentMemory = process.memoryUsage(); const memoryGrowth = currentMemory.heapUsed - startMemory.heapUsed; if (memoryGrowth > 100 * 1024 * 1024) { throw new Error('Cache memory limit exceeded'); } } } const endMemory = process.memoryUsage(); const totalMemoryGrowth = endMemory.heapUsed - startMemory.heapUsed; return { prevented: totalMemoryGrowth < 100 * 1024 * 1024, memoryGrowth: totalMemoryGrowth, documentsProcessed: uniqueDocuments }; } catch (error) { return { prevented: true, limited: true, error: error.message }; } } ); t.ok(cachePollutionAttack.prevented, 'Cache pollution attack was prevented'); // Test 10: Memory exhaustion recovery const memoryExhaustionRecovery = await performanceTracker.measureAsync( 'memory-exhaustion-recovery', async () => { const results = { attacksAttempted: 0, attacksPrevented: 0, recovered: false }; // Try various memory attacks const attacks = [ () => 'A'.repeat(100 * 1024 * 1024), // 100MB string () => new Array(10000000).fill('data'), // Large array () => { const obj = {}; for(let i = 0; i < 1000000; i++) obj[`key${i}`] = i; return obj; } // Large object ]; for (const attack of attacks) { results.attacksAttempted++; try { const payload = attack(); const xml = ` ${JSON.stringify(payload).substring(0, 1000)} `; await einvoice.parseXML(xml); } catch (error) { results.attacksPrevented++; } } // Test if system recovered and can process normal documents try { const normalXML = ` NORMAL-001 100.00 `; await einvoice.parseXML(normalXML); results.recovered = true; } catch (error) { results.recovered = false; } return results; } ); t.equal(memoryExhaustionRecovery.attacksPrevented, memoryExhaustionRecovery.attacksAttempted, 'All memory attacks were prevented'); t.ok(memoryExhaustionRecovery.recovered, 'System recovered after memory attacks'); // Print performance summary performanceTracker.printSummary(); }); // Run the test tap.start();