import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../plugins.js';
import { EInvoice, PDFExtractor } from '../../../ts/index.js';
import { PerformanceTracker } from '../performance.tracker.js';
import * as path from 'path';
import * as fs from 'fs';
const performanceTracker = new PerformanceTracker('SEC-05: Path Traversal Prevention');
tap.test('SEC-05: Path Traversal Prevention - should prevent directory traversal attacks', async () => {
// Test 1: Path traversal in XML attachment references
const xmlPathTraversal = await performanceTracker.measureAsync(
'xml-path-traversal-prevention',
async () => {
const traversalPaths = [
'../../../etc/passwd',
'..\\..\\..\\windows\\system32\\config\\sam',
'../../../../../../../../etc/shadow',
'./../.../.././../etc/hosts',
'..%2F..%2F..%2Fetc%2Fpasswd',
'file:///etc/passwd',
'file://C:\\Windows\\System32\\drivers\\etc\\hosts'
];
const results = [];
for (const maliciousPath of traversalPaths) {
// Test path traversal in attachment references
const xml = `
TEST-001
1
${maliciousPath}
VGVzdCBkYXRh
`;
try {
const invoice = await EInvoice.fromXml(xml);
// If parsing succeeds, the paths are just treated as data
results.push({
path: maliciousPath,
parsed: true,
// The library should not interpret these as actual file paths
safe: true
});
} catch (error) {
results.push({
path: maliciousPath,
parsed: false,
error: error.message
});
}
}
return results;
}
);
console.log('XML path traversal results:', xmlPathTraversal);
xmlPathTraversal.forEach(result => {
// Path strings in XML should be treated as data, not file paths
expect(result.parsed !== undefined).toEqual(true);
});
// Test 2: Unicode and encoding bypass attempts
const encodingBypass = await performanceTracker.measureAsync(
'encoding-bypass-attempts',
async () => {
const encodedPaths = [
'..%c0%af..%c0%afetc%c0%afpasswd', // Overlong UTF-8
'..%25c0%25af..%25c0%25afetc%25c0%25afpasswd', // Double encoding
'..%c1%9c..%c1%9cetc%c1%9cpasswd', // Invalid UTF-8
'\u002e\u002e/\u002e\u002e/etc/passwd', // Unicode dots
'..%u002f..%u002fetc%u002fpasswd', // IIS Unicode
'..%255c..%255c..%255cwindows%255csystem32' // Double encoded backslash
];
const results = [];
for (const encodedPath of encodedPaths) {
const xml = `
TEST-002
${encodedPath}
${encodedPath}
`;
try {
const invoice = await EInvoice.fromXml(xml);
results.push({
original: encodedPath,
parsed: true,
safe: true
});
} catch (error) {
results.push({
original: encodedPath,
parsed: false,
error: error.message
});
}
}
return results;
}
);
console.log('Encoding bypass results:', encodingBypass);
encodingBypass.forEach(result => {
expect(result.parsed !== undefined).toEqual(true);
});
// Test 3: Path traversal in PDF metadata
const pdfPathTraversal = await performanceTracker.measureAsync(
'pdf-path-traversal-prevention',
async () => {
const results = [];
// Create a mock PDF with path traversal attempts in metadata
const traversalPaths = [
'../../../sensitive/data.xml',
'..\\..\\..\\config\\secret.xml',
'file:///etc/invoice.xml'
];
for (const maliciousPath of traversalPaths) {
// Mock PDF with embedded file reference
const pdfContent = Buffer.from(`%PDF-1.4
1 0 obj
<>>>>>
endobj
2 0 obj
<>>>
endobj
3 0 obj
<>
stream
test
endstream
endobj
xref
0 4
0000000000 65535 f
0000000015 00000 n
0000000100 00000 n
0000000200 00000 n
trailer
<>
startxref
300
%%EOF`);
try {
const extractor = new PDFExtractor();
const result = await extractor.extractXml(pdfContent);
results.push({
path: maliciousPath,
extracted: result.success,
xmlFound: !!result.xml,
// PDF extractor should not follow file paths
safe: true
});
} catch (error) {
results.push({
path: maliciousPath,
extracted: false,
error: error.message
});
}
}
return results;
}
);
console.log('PDF path traversal results:', pdfPathTraversal);
pdfPathTraversal.forEach(result => {
// Path references in PDFs should not be followed
expect(result.safe || result.extracted === false).toEqual(true);
});
// Test 4: Null byte injection for path truncation
const nullByteInjection = await performanceTracker.measureAsync(
'null-byte-injection',
async () => {
const nullBytePaths = [
'invoice.xml\x00.pdf',
'data\x00../../../etc/passwd',
'file.xml\x00.jpg',
'../uploads/invoice.xml\x00.exe'
];
const results = [];
for (const nullPath of nullBytePaths) {
const xml = `
TEST-003
${nullPath}
${nullPath}
`;
try {
const invoice = await EInvoice.fromXml(xml);
results.push({
path: nullPath.replace(/\x00/g, '\\x00'),
parsed: true,
safe: true
});
} catch (error) {
results.push({
path: nullPath.replace(/\x00/g, '\\x00'),
parsed: false,
error: error.message
});
}
}
return results;
}
);
console.log('Null byte injection results:', nullByteInjection);
nullByteInjection.forEach(result => {
expect(result.parsed !== undefined).toEqual(true);
});
// Test 5: Windows UNC path injection
const uncPathInjection = await performanceTracker.measureAsync(
'unc-path-injection',
async () => {
const uncPaths = [
'\\\\attacker.com\\share\\evil.xml',
'\\\\127.0.0.1\\c$\\windows\\system32',
'//attacker.com/share/payload.xml',
'\\\\?\\UNC\\attacker\\share\\file'
];
const results = [];
for (const uncPath of uncPaths) {
const xml = `
TEST-004
${uncPath}
${uncPath}
`;
try {
const invoice = await EInvoice.fromXml(xml);
results.push({
path: uncPath,
parsed: true,
safe: true
});
} catch (error) {
results.push({
path: uncPath,
parsed: false,
error: error.message
});
}
}
return results;
}
);
console.log('UNC path injection results:', uncPathInjection);
uncPathInjection.forEach(result => {
// UNC paths in XML data should be treated as strings, not executed
expect(result.parsed !== undefined).toEqual(true);
});
// Test 6: Zip slip vulnerability simulation
const zipSlipTest = await performanceTracker.measureAsync(
'zip-slip-prevention',
async () => {
const zipSlipPaths = [
'../../../../../../tmp/evil.xml',
'../../../etc/invoice.xml',
'..\\..\\..\\..\\windows\\temp\\malicious.xml'
];
const results = [];
for (const slipPath of zipSlipPaths) {
// Simulate a filename that might come from a zip entry
const xml = `
TEST-005
1
PD94bWwgdmVyc2lvbj0iMS4wIj8+Cjxyb290Lz4=
`;
try {
const invoice = await EInvoice.fromXml(xml);
// The library should not extract files to the filesystem
results.push({
path: slipPath,
parsed: true,
safe: true,
wouldExtract: false
});
} catch (error) {
results.push({
path: slipPath,
parsed: false,
error: error.message
});
}
}
return results;
}
);
console.log('Zip slip test results:', zipSlipTest);
zipSlipTest.forEach(result => {
// The library should not extract embedded files to the filesystem
expect(result.safe || result.parsed === false).toEqual(true);
if (result.wouldExtract !== undefined) {
expect(result.wouldExtract).toEqual(false);
}
});
console.log('Path traversal prevention tests completed');
});
// Run the test
tap.start();