docudb
Version:
Document-based NoSQL database for NodeJS
359 lines • 12.3 kB
JavaScript
import { Database, Schema } from '../index.js';
import { expect } from 'chai';
import { cleanTestDataDir } from './utils.js';
const dangerousPaths = [
// === BASIC TRAVERSAL PATH ===
'../',
'../../',
'../../../',
'../../../../etc/passwd',
'../../../windows/system32/config/sam',
'..\\..\\..\\windows\\system32\\drivers\\etc\\hosts',
'folder/../../../etc/shadow',
'uploads/../../../database.sqlite',
// === PATH TRAVERSAL WITH SPECIFIC FILES ===
'../etc/passwd',
'../etc/shadow',
'../etc/hosts',
'../../windows/system32/config/sam',
'../../../proc/self/environ',
'../../../../var/log/apache2/access.log',
'../../../home/user/.ssh/id_rsa',
'../../root/.bash_history',
// === URL ENCODING ===
'%2e%2e%2f',
'%2e%2e/',
'%2e%2e%5c',
'%2e%2e\\',
'%2e%2e%2f%2e%2e%2f%2e%2e%2fetc%2fpasswd',
'%2e%2e%2f%2e%2e%2f%2e%2e%2fwindows%2fsystem32%2fconfig%2fsam',
'..%2f..%2f..%2fetc%2fpasswd',
'..%5c..%5c..%5cwindows%5csystem32%5cconfig%5csam',
// === DOUBLE URL ENCODING ===
'%252e%252e%252f',
'%252e%252e%255c',
'%c0%ae%c0%ae%c0%af',
'%c1%9c',
// === UNICODE ENCODING ===
'..%c0%af',
'..%c1%9c',
'\u002e\u002e\u002f',
'\u002e\u002e\u005c',
// === NULL BYTES ===
'../etc/passwd\x00',
'../../etc/shadow\u0000',
'file.txt\x00.jpg',
'config.ini\x00.png',
// === CONTROL CHARACTERS ===
'file\x01name.txt',
'folder\x1fname',
'document\x7fname.pdf',
'image\x0aname.jpg',
'script\x0dname.js',
'data\x09file.csv',
// === TEMPLATE INJECTION ===
'${java:version}',
'${env:PATH}',
'${sys:user.name}',
'${date:MM-dd-yyyy}',
'#{7*7}',
'{{7*7}}',
// === ACCESS TO SYSTEM DIRECTORIES ===
'/etc/passwd',
'/etc/shadow',
'/proc/version',
'/proc/self/environ',
'/proc/self/cmdline',
'/sys/class/dmi/id/product_name',
'/dev/random',
'/bin/sh',
'/usr/bin/id',
// === WINDOWS SYSTEM PATHS ===
'C:\\windows\\system32\\config\\sam',
'C:\\windows\\system32\\drivers\\etc\\hosts',
'C:\\boot.ini',
'C:\\autoexec.bat',
'\\windows\\system32\\config\\sam',
'\\windows\\repair\\sam',
// === HOME DIRECTORY ACCESS ===
'~/',
'~root/',
'~admin/',
'~/.bashrc',
'~/.ssh/',
'~/../../etc/passwd',
// === DOUBLE SLASH AND VARIATIONS ===
'folder//file.txt',
'path///to////file',
'dir\\\\file.txt',
'folder/.//file.txt',
'path/.//../file.txt',
// === DANGEROUS ABSOLUTE PATHS ===
'/',
'\\',
'/root/',
'/admin/',
'/var/www/',
'/usr/local/',
// === COMPLEX COMBINATIONS ===
'../../../etc/passwd%00',
'..\\..\\..\\windows\\system32\\config\\sam%00',
'folder/../../../etc/passwd#',
'uploads/../../database/config.ini?admin=true',
'files/../../../app/config/database.yml',
// === BYPASS ATTEMPTS ===
'....//',
'....\\\\',
'..../',
'....\\',
'.../',
'...\\',
'....//....//....//etc/passwd',
'....\\\\....\\\\....\\\\windows\\system32\\config\\sam',
// === FILTER EVASION ===
'..%252f..%252f..%252fetc%252fpasswd',
'..\\\\..\\\\..\\\\etc\\passwd',
'folder/..;/..;/..;/etc/passwd',
'dir\\..;\\..;\\..;\\windows\\system32\\config\\sam',
// === LOG POISONING ATTEMPTS ===
'../../../var/log/apache2/access.log',
'../../var/log/nginx/error.log',
'../../../var/log/auth.log',
'../../windows/system32/logfiles/',
// === CONFIGURATION FILES ===
'../../../etc/apache2/apache2.conf',
'../../nginx/nginx.conf',
'../../../app/config/database.yml',
'../../config/settings.ini',
'../wp-config.php',
'../../.env',
'../../../application.properties',
// === EXTREME CASES ===
'', // Empty string
' ', // Spaces only
'.', // Current directory
'..', // Parent directory
'...', // Triple period
'....', // Quadruple period
'\t', // Tab
'\n', // New line
'\r', // Carriage return
'\r\n', // CRLF
// === MIXED CASE EVASION ===
'../ETC/passwd',
'..\\WINDOWS\\system32\\CONFIG\\sam',
'../Etc/Passwd',
'..\\Windows\\System32\\Config\\Sam'
];
describe('DocuDB - Error Handling', () => {
let db;
const testDbName = 'testErrors';
beforeEach(async () => {
await cleanTestDataDir(testDbName);
db = new Database({
name: testDbName,
compression: false
});
await db.initialize();
});
afterEach(async () => {
await cleanTestDataDir(testDbName);
});
describe('Initialization Errors', () => {
it('should handle errors when initializing with invalid directory', async () => {
try {
const invalidDb = new Database({
name: 'invalid/db', // Name with invalid characters for directory
dataDir: '/ruta/inexistente/invalid/db'
});
await invalidDb.initialize();
expect.fail('Should have thrown an error');
}
catch (error) {
expect(error.message).to.include('Invalid database name');
}
});
});
describe('Collection Errors', () => {
it('should reject invalid collection names', () => {
try {
db.collection('');
expect.fail('Should have thrown an error');
}
catch (error) {
expect(error.message).to.include('Collection name');
}
try {
db.collection(null);
expect.fail('Should have thrown an error');
}
catch (error) {
expect(error.message).to.include('Collection name');
}
try {
db.collection(123);
expect.fail('Should have thrown an error');
}
catch (error) {
expect(error.message).to.include('Collection name');
}
});
it('should handle errors when deleting non-existent collections', async () => {
const result = await db.dropCollection('coleccionInexistente');
expect(result).to.be.false;
});
});
describe('Schema Validation Errors', () => {
let productos;
beforeEach(() => {
const productoSchema = new Schema({
name: { type: 'string', required: true },
price: {
type: 'number',
required: true,
validate: { min: 0 }
},
stock: { type: 'number', default: 0 }
}, { strict: true });
productos = db.collection('productos', { schema: productoSchema });
});
it('should reject documents without required fields', async () => {
try {
await productos.insertOne({ price: 100 }); // Missing name
expect.fail('Should have thrown a validation error');
}
catch (error) {
expect(error.message).to.include('required');
}
});
it('should reject documents with incorrect types', async () => {
try {
await productos.insertOne({
name: 'Test Product',
price: 'hundred' // Should be a number
});
expect.fail('Should have thrown a validation error');
}
catch (error) {
expect(error.message).to.include('type');
}
});
it('should reject documents with out-of-range values', async () => {
try {
await productos.insertOne({
name: 'Test Product',
price: -10 // Should be >= 0
});
expect.fail('Should have thrown a validation error');
}
catch (error) {
expect(error.message).to.include('greater than or equal to 0');
}
});
it('should reject undefined fields in strict mode', async () => {
try {
await productos.insertOne({
name: 'Test Product',
price: 100,
invalidField: 'value' // Not in schema
});
expect.fail('Should have thrown a validation error');
}
catch (error) {
expect(error.message).to.include('allowed');
}
});
});
describe('CRUD Operation Errors', () => {
let productos;
beforeEach(async () => {
productos = db.collection('productos');
});
it('should handle errors when searching with invalid ID', async () => {
try {
await productos.findById('id-no-valido');
expect.fail('Should have thrown an error');
}
catch (error) {
expect(error.message).to.include('Invalid document ID format');
}
});
it('should handle errors when updating with invalid ID', async () => {
try {
await productos.updateById('id-no-valido', { $set: { campo: 'valor' } });
expect.fail('Should have thrown an error');
}
catch (error) {
expect(error.message).to.include('Invalid document ID format');
}
});
it('should handle errors when deleting with invalid ID', async () => {
try {
await productos.deleteById('id-no-valido');
expect.fail('Should have thrown an error');
}
catch (error) {
expect(error.message).to.include('Invalid document ID format');
}
});
it('should accept valid MongoDB-style IDs', async () => {
const validMongoId = '507f1f77bcf86cd799439011'; // Valid 24-char hex
try {
// Just testing validation, not actual operation
await productos.findById(validMongoId);
}
catch (error) {
// Should fail with NOT_FOUND, not INVALID_ID
expect(error.message).to.not.include('Invalid document ID format');
}
});
it('should accept valid UUID v4 IDs', async () => {
const validUuid = '123e4567-e89b-42d3-a456-556642440000'; // Valid UUID format
try {
// Just testing validation, not actual operation
await productos.findById(validUuid);
}
catch (error) {
// Should fail with NOT_FOUND, not INVALID_ID
expect(error.message).to.not.include('Invalid document ID format');
}
});
it('should handle errors with invalid update operators', async () => {
// First insert a document
const doc = await productos.insertOne({ campo: 'valor' });
try {
await productos.updateById(doc._id, { $operadorInvalido: { campo: 'nuevo' } });
expect.fail('Should have thrown an error');
}
catch (error) {
expect(error.message).to.include('operator');
}
});
});
});
describe('DocuDB - Validate Collection Name', () => {
let db;
beforeEach(async () => {
await cleanTestDataDir('testValidateCollectionName');
});
afterEach(async () => {
await cleanTestDataDir('testValidateCollectionName');
});
it('should reject names that contain dangerous characters', async () => {
for (const name of dangerousPaths) {
try {
db = new Database({
name,
compression: false
});
await db.initialize();
console.log(name);
expect.fail(`Should have thrown an error for name: ${name}`);
}
catch (error) {
expect(error.message).to.include('Invalid database name');
}
}
});
});
//# sourceMappingURL=error_handling.test.js.map