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-02: XML Bomb Prevention'); tap.test('SEC-02: XML Bomb Prevention - should prevent XML bomb attacks', async (t) => { const einvoice = new EInvoice(); // 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.parseXML(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 t.ok(timeTaken < 5000, `Parsing completed in ${timeTaken}ms (limit: 5000ms)`); t.ok(memoryIncrease < 50 * 1024 * 1024, `Memory increase: ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB (limit: 50MB)`); return { prevented: true, method: 'limited', timeTaken, memoryIncrease }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(billionLaughs.prevented, 'Billion laughs attack was prevented'); // 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.parseXML(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 t.ok(timeTaken < 2000, `Parsing completed in ${timeTaken}ms`); t.ok(memoryIncrease < 100 * 1024 * 1024, `Memory increase reasonable: ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`); return { prevented: true, method: 'handled', timeTaken, memoryIncrease }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(quadraticBlowup.prevented, 'Quadratic blowup attack was handled'); // Test 3: Recursive Entity Reference const recursiveEntity = await performanceTracker.measureAsync( 'recursive-entity-attack', async () => { const bombXML = ` ]> &a; `; try { await einvoice.parseXML(bombXML); return { prevented: true, method: 'handled' }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(recursiveEntity.prevented, 'Recursive entity reference was prevented'); // Test 4: External Entity Expansion Attack const externalEntityExpansion = await performanceTracker.measureAsync( 'external-entity-expansion', async () => { const bombXML = ` "> "> "> %pe1; %pe2; %pe3; ]> test `; try { await einvoice.parseXML(bombXML); return { prevented: true, method: 'handled' }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(externalEntityExpansion.prevented, 'External entity expansion was prevented'); // 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.parseXML(bombXML); const endTime = Date.now(); const timeTaken = endTime - startTime; // Should handle deep nesting without stack overflow t.ok(timeTaken < 5000, `Deep nesting handled in ${timeTaken}ms`); return { prevented: true, method: 'handled', timeTaken }; } catch (error) { // Stack overflow or depth limit reached return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(deepNesting.prevented, 'Deep nesting attack was prevented'); // Test 6: Attribute Blowup const attributeBlowup = await performanceTracker.measureAsync( 'attribute-blowup-attack', async () => { let attributes = ''; for (let i = 0; i < 100000; i++) { attributes += ` attr${i}="value${i}"`; } const bombXML = ` test `; const startTime = Date.now(); const startMemory = process.memoryUsage(); try { await einvoice.parseXML(bombXML); const endTime = Date.now(); const endMemory = process.memoryUsage(); const timeTaken = endTime - startTime; const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; t.ok(timeTaken < 10000, `Attribute parsing completed in ${timeTaken}ms`); t.ok(memoryIncrease < 200 * 1024 * 1024, `Memory increase: ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`); return { prevented: true, method: 'handled', timeTaken, memoryIncrease }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(attributeBlowup.prevented, 'Attribute blowup attack was handled'); // Test 7: Comment Bomb const commentBomb = await performanceTracker.measureAsync( 'comment-bomb-attack', async () => { const longComment = ''; const bombXML = ` ${longComment} test ${longComment} `; const startTime = Date.now(); try { await einvoice.parseXML(bombXML); const endTime = Date.now(); const timeTaken = endTime - startTime; t.ok(timeTaken < 5000, `Comment parsing completed in ${timeTaken}ms`); return { prevented: true, method: 'handled', timeTaken }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(commentBomb.prevented, 'Comment bomb attack was handled'); // Test 8: Processing Instruction Bomb const processingInstructionBomb = await performanceTracker.measureAsync( 'pi-bomb-attack', async () => { let pis = ''; for (let i = 0; i < 100000; i++) { pis += ``; } const bombXML = ` ${pis} test `; const startTime = Date.now(); try { await einvoice.parseXML(bombXML); const endTime = Date.now(); const timeTaken = endTime - startTime; t.ok(timeTaken < 10000, `PI parsing completed in ${timeTaken}ms`); return { prevented: true, method: 'handled', timeTaken }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(processingInstructionBomb.prevented, 'Processing instruction bomb was handled'); // 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.parseXML(bombXML); const endTime = Date.now(); const endMemory = process.memoryUsage(); const timeTaken = endTime - startTime; const memoryIncrease = endMemory.heapUsed - startMemory.heapUsed; t.ok(timeTaken < 5000, `CDATA parsing completed in ${timeTaken}ms`); t.ok(memoryIncrease < 200 * 1024 * 1024, `Memory increase: ${(memoryIncrease / 1024 / 1024).toFixed(2)}MB`); return { prevented: true, method: 'handled', timeTaken, memoryIncrease }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(cdataBomb.prevented, 'CDATA bomb attack was handled'); // 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.parseXML(bombXML); const endTime = Date.now(); const timeTaken = endTime - startTime; t.ok(timeTaken < 10000, `Namespace parsing completed in ${timeTaken}ms`); return { prevented: true, method: 'handled', timeTaken }; } catch (error) { return { prevented: true, method: 'rejected', error: error.message }; } } ); t.ok(namespaceBomb.prevented, 'Namespace bomb attack was handled'); // Print performance summary performanceTracker.printSummary(); }); // Run the test tap.start();