import { expect, 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-02: XML Bomb Prevention'); tap.test('SEC-02: XML Bomb Prevention - should prevent XML bomb attacks', async () => { // Test 1: Billion Laughs Attack (Exponential Entity Expansion) const billionLaughs = await performanceTracker.measureAsync( 'billion-laughs-attack', async () => { const bombXML = ` ]> &lol9; `; const startTime = Date.now(); const startMemory = process.memoryUsage(); try { await EInvoice.fromXml(bombXML); const endTime = Date.now(); const endMemory = process.memoryUsage(); const timeTaken = endTime - startTime; const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; // Should not take excessive time or memory expect(timeTaken).toBeLessThan(5000); expect(memoryIncrease).toBeLessThan(50 * 1024 * 1024); return { prevented: true, method: 'limited', timeTaken, memoryIncrease }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(billionLaughs.prevented).toBeTrue(); // Test 2: Quadratic Blowup Attack const quadraticBlowup = await performanceTracker.measureAsync( 'quadratic-blowup-attack', async () => { // Create a string that repeats many times const longString = 'A'.repeat(50000); const bombXML = ` ]> &x; &x; &x; &x; &x; &x; &x; &x; &x; &x; `; const startTime = Date.now(); const startMemory = process.memoryUsage(); try { await EInvoice.fromXml(bombXML); const endTime = Date.now(); const endMemory = process.memoryUsage(); const timeTaken = endTime - startTime; const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; // Should handle without quadratic memory growth expect(timeTaken).toBeLessThan(2000); expect(memoryIncrease).toBeLessThan(100 * 1024 * 1024); return { prevented: true, method: 'handled', timeTaken, memoryIncrease }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(quadraticBlowup.prevented).toBeTrue(); // Test 3: Recursive Entity Reference const recursiveEntity = await performanceTracker.measureAsync( 'recursive-entity-attack', async () => { const bombXML = ` ]> &a; `; try { await EInvoice.fromXml(bombXML); return { prevented: true, method: 'handled' }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(recursiveEntity.prevented).toBeTrue(); // Test 4: External Entity Expansion Attack const externalEntityExpansion = await performanceTracker.measureAsync( 'external-entity-expansion', async () => { const bombXML = ` "> "> "> %pe1; %pe2; %pe3; ]> test `; try { await EInvoice.fromXml(bombXML); return { prevented: true, method: 'handled' }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(externalEntityExpansion.prevented).toBeTrue(); // Test 5: Deep Nesting Attack const deepNesting = await performanceTracker.measureAsync( 'deep-nesting-attack', async () => { let xmlContent = ''; const depth = 10000; // Create deeply nested structure for (let i = 0; i < depth; i++) { xmlContent += ''; } xmlContent += 'data'; for (let i = depth - 1; i >= 0; i--) { xmlContent += ''; } xmlContent += ''; const bombXML = `${xmlContent}`; const startTime = Date.now(); try { await EInvoice.fromXml(bombXML); const endTime = Date.now(); const timeTaken = endTime - startTime; // Should handle deep nesting without stack overflow expect(timeTaken).toBeLessThan(5000); return { prevented: true, method: 'handled', timeTaken }; } catch (error) { // Stack overflow or depth limit reached return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(deepNesting.prevented).toBeTrue(); // Test 6: Attribute Blowup const attributeBlowup = await performanceTracker.measureAsync( 'attribute-blowup-attack', async () => { let attributes = ''; for (let i = 0; i < 1000; i++) { // Reduced for faster testing attributes += ` attr${i}="value${i}"`; } const bombXML = ` test `; const startTime = Date.now(); const startMemory = process.memoryUsage(); try { await EInvoice.fromXml(bombXML); const endTime = Date.now(); const endMemory = process.memoryUsage(); const timeTaken = endTime - startTime; const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; expect(timeTaken).toBeLessThan(10000); expect(memoryIncrease).toBeLessThan(200 * 1024 * 1024); return { prevented: true, method: 'handled', timeTaken, memoryIncrease }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(attributeBlowup.prevented).toBeTrue(); // Test 7: Comment Bomb const commentBomb = await performanceTracker.measureAsync( 'comment-bomb-attack', async () => { const longComment = ''; // Reduced for faster testing const bombXML = ` ${longComment} test ${longComment} `; const startTime = Date.now(); try { await EInvoice.fromXml(bombXML); const endTime = Date.now(); const timeTaken = endTime - startTime; expect(timeTaken).toBeLessThan(5000); return { prevented: true, method: 'handled', timeTaken }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(commentBomb.prevented).toBeTrue(); // Test 8: Processing Instruction Bomb const processingInstructionBomb = await performanceTracker.measureAsync( 'pi-bomb-attack', async () => { let pis = ''; for (let i = 0; i < 1000; i++) { // Reduced for faster testing pis += ``; } const bombXML = ` ${pis} test `; const startTime = Date.now(); try { await EInvoice.fromXml(bombXML); const endTime = Date.now(); const timeTaken = endTime - startTime; expect(timeTaken).toBeLessThan(10000); return { prevented: true, method: 'handled', timeTaken }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(processingInstructionBomb.prevented).toBeTrue(); // Test 9: CDATA Bomb const cdataBomb = await performanceTracker.measureAsync( 'cdata-bomb-attack', async () => { const largeCDATA = ''; const bombXML = ` ${largeCDATA} `; const startTime = Date.now(); const startMemory = process.memoryUsage(); try { await EInvoice.fromXml(bombXML); const endTime = Date.now(); const endMemory = process.memoryUsage(); const timeTaken = endTime - startTime; const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; expect(timeTaken).toBeLessThan(5000); expect(memoryIncrease).toBeLessThan(200 * 1024 * 1024); return { prevented: true, method: 'handled', timeTaken, memoryIncrease }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(cdataBomb.prevented).toBeTrue(); // Test 10: Namespace Bomb const namespaceBomb = await performanceTracker.measureAsync( 'namespace-bomb-attack', async () => { let namespaces = ''; for (let i = 0; i < 10000; i++) { namespaces += ` xmlns:ns${i}="http://example.com/ns${i}"`; } const bombXML = ` test `; const startTime = Date.now(); try { await EInvoice.fromXml(bombXML); const endTime = Date.now(); const timeTaken = endTime - startTime; expect(timeTaken).toBeLessThan(10000); return { prevented: true, method: 'handled', timeTaken }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); expect(namespaceBomb.prevented).toBeTrue(); // Performance summary is handled by the tracker }); // Run the test tap.start();