Massive v2 rewrite

This commit is contained in:
Caileb 2025-08-02 15:34:04 -05:00
parent 1025f3b523
commit 5f1328f626
77 changed files with 28105 additions and 3542 deletions

264
.tests/plugins.test.js Normal file
View file

@ -0,0 +1,264 @@
import { jest } from '@jest/globals';
// Mock the path and url modules for testing
jest.unstable_mockModule('path', () => ({
resolve: jest.fn(),
extname: jest.fn(),
sep: '/',
isAbsolute: jest.fn(),
normalize: jest.fn()
}));
jest.unstable_mockModule('url', () => ({
pathToFileURL: jest.fn()
}));
// Mock the root directory
jest.unstable_mockModule('../dist/index.js', () => ({
rootDir: '/app/root'
}));
// Import after mocking
const { secureImportModule, hasExport, getExport } = await import('../dist/utils/plugins.js');
const path = await import('path');
const url = await import('url');
describe('Plugins utilities', () => {
beforeEach(() => {
jest.clearAllMocks();
// Default mock implementations
path.normalize.mockImplementation((p) => p);
path.resolve.mockImplementation((root, rel) => `${root}/${rel}`);
path.extname.mockImplementation((p) => {
const parts = p.split('.');
return parts.length > 1 ? `.${parts[parts.length - 1]}` : '';
});
path.isAbsolute.mockImplementation((p) => p.startsWith('/'));
url.pathToFileURL.mockImplementation((p) => ({ href: `file://${p}` }));
});
describe('secureImportModule', () => {
describe('input validation', () => {
test('should reject non-string module paths', async () => {
await expect(secureImportModule(null)).rejects.toThrow('Module path must be a string');
await expect(secureImportModule(undefined)).rejects.toThrow('Module path must be a string');
await expect(secureImportModule(123)).rejects.toThrow('Module path must be a string');
});
test('should reject empty module paths', async () => {
await expect(secureImportModule('')).rejects.toThrow('Module path cannot be empty');
});
test('should reject paths that are too long', async () => {
const longPath = 'a'.repeat(1025) + '.js';
await expect(secureImportModule(longPath)).rejects.toThrow('Module path too long');
});
});
describe('security pattern validation', () => {
test('should reject directory traversal attempts', async () => {
await expect(secureImportModule('../evil.js')).rejects.toThrow('Module path contains blocked pattern');
await expect(secureImportModule('../../system.js')).rejects.toThrow('Module path contains blocked pattern');
await expect(secureImportModule('folder/../escape.js')).rejects.toThrow('Module path contains blocked pattern');
});
test('should reject double slash patterns', async () => {
await expect(secureImportModule('folder//file.js')).rejects.toThrow('Module path contains blocked pattern');
await expect(secureImportModule('//root.js')).rejects.toThrow('Module path contains blocked pattern');
});
test('should reject null byte injections', async () => {
await expect(secureImportModule('file\0.js')).rejects.toThrow('Module path contains blocked pattern');
});
test('should reject control characters', async () => {
await expect(secureImportModule('file\x01.js')).rejects.toThrow('Module path contains blocked pattern');
await expect(secureImportModule('file\x1f.js')).rejects.toThrow('Module path contains blocked pattern');
});
test('should reject node_modules access', async () => {
await expect(secureImportModule('node_modules/evil.js')).rejects.toThrow('Module path contains blocked pattern');
await expect(secureImportModule('NODE_MODULES/evil.js')).rejects.toThrow('Module path contains blocked pattern');
});
test('should reject sensitive file access', async () => {
await expect(secureImportModule('package.json')).rejects.toThrow('Module path contains blocked pattern');
await expect(secureImportModule('.env')).rejects.toThrow('Module path contains blocked pattern');
await expect(secureImportModule('config/.ENV')).rejects.toThrow('Module path contains blocked pattern');
});
test('should reject paths that are too deep', async () => {
const deepPath = 'a/'.repeat(25) + 'file.js';
path.normalize.mockReturnValue(deepPath);
await expect(secureImportModule(deepPath)).rejects.toThrow('Module path too deep');
});
});
describe('file extension validation', () => {
test('should reject invalid file extensions', async () => {
path.extname.mockReturnValue('.txt');
await expect(secureImportModule('file.txt')).rejects.toThrow('Only .js, .mjs files can be imported');
});
test('should reject files without extensions', async () => {
path.extname.mockReturnValue('');
await expect(secureImportModule('file')).rejects.toThrow('Only .js, .mjs files can be imported');
});
test('should accept valid .js extension validation', async () => {
path.extname.mockReturnValue('.js');
path.isAbsolute.mockReturnValue(false);
path.resolve.mockReturnValue('/app/root/valid.js');
path.normalize.mockImplementation((p) => p);
// This will fail at import but pass extension validation
await expect(secureImportModule('valid.js')).rejects.toThrow('Failed to import module');
});
test('should accept valid .mjs extension validation', async () => {
path.extname.mockReturnValue('.mjs');
path.isAbsolute.mockReturnValue(false);
path.resolve.mockReturnValue('/app/root/valid.mjs');
path.normalize.mockImplementation((p) => p);
// This will fail at import but pass extension validation
await expect(secureImportModule('valid.mjs')).rejects.toThrow('Failed to import module');
});
});
describe('path resolution security', () => {
test('should reject absolute paths', async () => {
path.extname.mockReturnValue('.js');
path.isAbsolute.mockReturnValue(true);
await expect(secureImportModule('/absolute/path.js')).rejects.toThrow('Absolute paths are not allowed');
});
test('should reject paths outside application root', async () => {
path.extname.mockReturnValue('.js');
path.isAbsolute.mockReturnValue(false);
path.resolve.mockReturnValue('/outside/root/file.js');
path.normalize.mockImplementation((p) => p);
await expect(secureImportModule('file.js')).rejects.toThrow('Module path outside of application root');
});
test('should detect symbolic link traversal', async () => {
path.extname.mockReturnValue('.js');
path.isAbsolute.mockReturnValue(false);
path.resolve.mockReturnValue('/app/root/../outside.js');
path.normalize.mockImplementation((p) => p);
await expect(secureImportModule('file.js')).rejects.toThrow('Path traversal detected');
});
test('should validate paths within application root', async () => {
path.extname.mockReturnValue('.js');
path.isAbsolute.mockReturnValue(false);
path.resolve.mockReturnValue('/app/root/plugins/test.js');
path.normalize.mockImplementation((p) => p);
// This will fail at import but pass path validation
await expect(secureImportModule('plugins/test.js')).rejects.toThrow('Failed to import module');
});
});
describe('import operation and error handling', () => {
test('should handle import failures gracefully', async () => {
path.extname.mockReturnValue('.js');
path.isAbsolute.mockReturnValue(false);
path.resolve.mockReturnValue('/app/root/file.js');
path.normalize.mockImplementation((p) => p);
// Since we can't easily mock import() in ES modules,
// this will naturally fail and test our error handling
await expect(secureImportModule('file.js')).rejects.toThrow('Failed to import module');
});
test('should handle non-Error exceptions in path validation', async () => {
path.extname.mockReturnValue('.js');
path.isAbsolute.mockReturnValue(false);
path.resolve.mockImplementation(() => {
throw "Non-error exception";
});
await expect(secureImportModule('file.js')).rejects.toThrow('Module import failed');
});
test('should handle unknown errors gracefully', async () => {
path.extname.mockImplementation(() => {
throw { unknown: 'error' };
});
await expect(secureImportModule('file.js')).rejects.toThrow('Module import failed due to unknown error');
});
});
});
describe('hasExport', () => {
test('should return true for existing exports', () => {
const module = { testFunction: () => {}, testValue: 'test' };
expect(hasExport(module, 'testFunction')).toBe(true);
expect(hasExport(module, 'testValue')).toBe(true);
});
test('should return false for non-existent exports', () => {
const module = { testFunction: () => {} };
expect(hasExport(module, 'nonExistent')).toBe(false);
});
test('should return false for undefined exports', () => {
const module = { testValue: undefined };
expect(hasExport(module, 'testValue')).toBe(false);
});
test('should handle edge cases', () => {
const module = {};
expect(hasExport(module, 'toString')).toBe(true); // inherited from Object.prototype
expect(hasExport(module, 'constructor')).toBe(true); // inherited from Object.prototype
expect(hasExport(module, 'nonExistentMethod')).toBe(false);
});
});
describe('getExport', () => {
test('should return export values correctly', () => {
const testFunction = () => 'test';
const module = {
testFunction,
testValue: 'hello'
};
expect(getExport(module, 'testFunction')).toBe(testFunction);
expect(getExport(module, 'testValue')).toBe('hello');
});
test('should return undefined for non-existent exports', () => {
const module = { testFunction: () => {} };
expect(getExport(module, 'nonExistent')).toBeUndefined();
});
test('should return actual values including falsy ones', () => {
const module = {
zero: 0,
false: false,
empty: '',
null: null
};
expect(getExport(module, 'zero')).toBe(0);
expect(getExport(module, 'false')).toBe(false);
expect(getExport(module, 'empty')).toBe('');
expect(getExport(module, 'null')).toBe(null);
});
});
});