/**
* Security Edge Cases Tests
* Comprehensive tests for security vulnerabilities and edge cases
*/
import {
validatePath,
isBinaryFile,
isSafeRegex,
sanitizeCommand,
validateSQLQuery,
applySecurityChecks,
} from '../src/security';
import { DEFAULT_CONFIG } from '../src/config';
describe('Security Edge Cases', () => {
describe('validatePath - Path Traversal Variants', () => {
test('should reject ../ traversal', () => {
expect(validatePath('../etc/passwd', '/safe/dir')).toBe(false);
});
test('should reject ../../ traversal', () => {
expect(validatePath('../../etc/passwd', '/safe/dir')).toBe(false);
});
test('should reject Windows-style ..\\ traversal', () => {
expect(validatePath('..\\windows\\system32', '/safe/dir')).toBe(false);
});
test('should reject mixed separator traversal', () => {
expect(validatePath('..\\../etc/passwd', '/safe/dir')).toBe(false);
});
// Note: URL-encoded paths are not decoded by validatePath - this is by design
// as the function works with raw paths, not URL-encoded strings
test('should handle encoded traversal %2e%2e (not decoded)', () => {
// The encoded string is treated as a literal filename, not a traversal
expect(validatePath('%2e%2e/etc/passwd', '/safe/dir')).toBe(true);
});
test('should handle double-encoded traversal (not decoded)', () => {
expect(validatePath('%252e%252e/etc/passwd', '/safe/dir')).toBe(true);
});
test('should reject UNC paths (Windows)', () => {
expect(validatePath('\\\\server\\share\\file.txt', '/safe/dir')).toBe(false);
});
test('should reject UNC paths with leading slashes', () => {
expect(validatePath('\\\\\\\\server\\\\share', '/safe/dir')).toBe(false);
});
test('should handle path with null byte (treated as literal)', () => {
// Null bytes in paths are handled by the OS, not our validator
expect(validatePath('file.txt\0.jpg', '/safe/dir')).toBe(true);
});
test('should reject path with trailing dot', () => {
// Windows treats trailing dots specially
expect(validatePath('file.txt.', '/safe/dir')).toBe(true); // Relative path, allowed
});
test('should allow valid relative path', () => {
expect(validatePath('subdir/file.txt', '/safe/dir')).toBe(true);
});
test('should allow valid nested path', () => {
expect(validatePath('a/b/c/d/file.txt', '/safe/dir')).toBe(true);
});
test('should reject empty path', () => {
expect(validatePath('', '/safe/dir')).toBe(false);
});
test('should reject empty base path', () => {
expect(validatePath('file.txt', '')).toBe(false);
});
test('should handle path with spaces', () => {
expect(validatePath('my file.txt', '/safe/dir')).toBe(true);
});
test('should handle path with unicode', () => {
expect(validatePath('файл.txt', '/safe/dir')).toBe(true);
});
});
describe('isSafeRegex - ReDoS Patterns', () => {
test('should reject catastrophic backtracking (.*)(.*)', () => {
expect(isSafeRegex('(.*)(.*)')).toBe(false);
});
test('should reject nested quantifiers (.+)+', () => {
expect(isSafeRegex('(.+)+')).toBe(false);
});
test('should reject character class repetition ([a-z]+)+', () => {
expect(isSafeRegex('([a-z]+)+')).toBe(false);
});
test('should reject alternation repetition (a|b)+', () => {
expect(isSafeRegex('(a|b)+')).toBe(false);
});
test('should reject double star (.*?)**', () => {
expect(isSafeRegex('(.*?)**')).toBe(false);
});
test('should reject complex nested quantifiers', () => {
expect(isSafeRegex('(a+)+$')).toBe(false);
});
test('should reject overlapping ranges', () => {
expect(isSafeRegex('(a|a)+')).toBe(false);
});
test('should allow safe character class', () => {
expect(isSafeRegex('[a-z]+')).toBe(true);
});
test('should allow simple quantifier', () => {
expect(isSafeRegex('hello{3}')).toBe(true);
});
test('should allow anchored pattern', () => {
expect(isSafeRegex('^hello$')).toBe(true);
});
test('should allow optional group', () => {
expect(isSafeRegex('hello(world)?')).toBe(true);
});
test('should reject overly long pattern', () => {
expect(isSafeRegex('a'.repeat(600))).toBe(false);
});
test('should handle empty pattern', () => {
expect(isSafeRegex('')).toBe(false);
});
test('should handle null input', () => {
expect(isSafeRegex(null as unknown as string)).toBe(false);
});
});
describe('sanitizeCommand - Command Injection', () => {
test('should allow safe commands', () => {
expect(sanitizeCommand('ls -la').safe).toBe(true);
expect(sanitizeCommand('git status').safe).toBe(true);
expect(sanitizeCommand('npm install').safe).toBe(true);
});
test('should reject rm -rf', () => {
expect(sanitizeCommand('rm -rf /').safe).toBe(false);
expect(sanitizeCommand('rm -rf node_modules').safe).toBe(false);
});
test('should reject sudo', () => {
expect(sanitizeCommand('sudo apt update').safe).toBe(false);
expect(sanitizeCommand('su root').safe).toBe(false);
});
test('should reject command substitution $()', () => {
expect(sanitizeCommand('echo $(whoami)').safe).toBe(false);
});
test('should reject command substitution backticks', () => {
expect(sanitizeCommand('echo `id`').safe).toBe(false);
});
test('should reject excessive pipes', () => {
expect(sanitizeCommand('cmd1 | cmd2 | cmd3 | cmd4').safe).toBe(false);
});
test('should reject multiple semicolons', () => {
expect(sanitizeCommand('cmd1; cmd2; cmd3').safe).toBe(false);
});
test('should reject null byte injection', () => {
expect(sanitizeCommand('ls\0.txt').safe).toBe(false);
});
test('should reject URL-encoded null byte', () => {
expect(sanitizeCommand('ls%00.txt').safe).toBe(false);
});
test('should reject IFS tampering', () => {
expect(sanitizeCommand('IFS=$\'\\n\\t\\r\'; cmd').safe).toBe(false);
});
test('should handle PATH manipulation (allowed - env var assignment)', () => {
// PATH= is a valid env var assignment, not inherently dangerous
expect(sanitizeCommand('PATH=/tmp cmd').safe).toBe(true);
});
test('should handle ampersand chaining (allowed)', () => {
// && is a valid shell operator
expect(sanitizeCommand('cmd1 && cmd2').safe).toBe(true);
});
test('should handle OR chaining (allowed)', () => {
// || is a valid shell operator
expect(sanitizeCommand('cmd1 || cmd2').safe).toBe(true);
});
test('should handle empty command', () => {
expect(sanitizeCommand('').safe).toBe(false);
});
test('should handle null input', () => {
expect(sanitizeCommand(null as unknown as string).safe).toBe(false);
});
test('should handle non-string input', () => {
expect(sanitizeCommand(123 as unknown as string).safe).toBe(false);
});
});
describe('validateSQLQuery - SQL Injection', () => {
test('should allow SELECT queries', () => {
expect(validateSQLQuery('SELECT * FROM users').valid).toBe(true);
expect(validateSQLQuery('SELECT id, name FROM users WHERE active = 1').valid).toBe(true);
});
test('should allow PRAGMA queries', () => {
expect(validateSQLQuery('PRAGMA table_info(users)').valid).toBe(true);
});
test('should reject DROP', () => {
expect(validateSQLQuery('DROP TABLE users').valid).toBe(false);
});
test('should reject DELETE', () => {
expect(validateSQLQuery('DELETE FROM users WHERE id = 1').valid).toBe(false);
});
test('should reject UPDATE', () => {
expect(validateSQLQuery('UPDATE users SET name = "John"').valid).toBe(false);
});
test('should reject INSERT', () => {
expect(validateSQLQuery('INSERT INTO users VALUES (1, "John")').valid).toBe(false);
});
test('should reject ALTER', () => {
expect(validateSQLQuery('ALTER TABLE users ADD COLUMN email TEXT').valid).toBe(false);
});
test('should reject CREATE', () => {
expect(validateSQLQuery('CREATE TABLE test (id INT)').valid).toBe(false);
});
test('should reject SQL comment injection --', () => {
expect(validateSQLQuery('SELECT * FROM users; DROP TABLE users--').valid).toBe(false);
});
test('should reject SQL comment injection /* */', () => {
expect(validateSQLQuery('SELECT * FROM users/* comment */DROP TABLE users').valid).toBe(false);
});
test('should reject stacked queries with semicolon', () => {
expect(validateSQLQuery('SELECT 1; DROP TABLE users').valid).toBe(false);
});
test('should handle UNION (allowed for read-only queries)', () => {
// UNION is allowed as it's a read-only operation
expect(validateSQLQuery('SELECT * FROM users UNION SELECT * FROM passwords').valid).toBe(true);
});
test('should handle empty query', () => {
expect(validateSQLQuery('').valid).toBe(false);
});
test('should handle null input', () => {
expect(validateSQLQuery(null as unknown as string).valid).toBe(false);
});
test('should handle case-insensitive keywords', () => {
expect(validateSQLQuery('drop table users').valid).toBe(false);
expect(validateSQLQuery('Drop Table users').valid).toBe(false);
});
});
describe('applySecurityChecks - Combined Checks', () => {
test('should apply all checks when enabled', () => {
const result = applySecurityChecks('../evil.txt', 'text\0binary', '(.*)(.*)');
expect(result.validPath).toBe(false);
expect(result.isBinary).toBe(true);
expect(result.safeRegex).toBe(false);
});
test('should skip checks when disabled', () => {
const result = applySecurityChecks('../evil.txt', 'text\0binary', '(.*)(.*)', {
...DEFAULT_CONFIG,
pathValidationEnabled: false,
binaryFileDetection: false,
regexReDoSProtection: false,
});
expect(result.validPath).toBe(true);
expect(result.isBinary).toBe(false);
expect(result.safeRegex).toBe(true);
});
test('should handle partial check disabling', () => {
const result = applySecurityChecks('../evil.txt', 'text\0binary', '(.*)(.*)', {
...DEFAULT_CONFIG,
pathValidationEnabled: false,
});
expect(result.validPath).toBe(true);
expect(result.isBinary).toBe(true);
expect(result.safeRegex).toBe(false);
});
});
describe('isBinaryFile - Edge Cases', () => {
test('should detect null byte in first 8KB', () => {
const content = 'a'.repeat(5000) + '\0' + 'b'.repeat(5000);
expect(isBinaryFile(content)).toBe(true);
});
test('should not detect null byte beyond 8KB', () => {
const content = 'a'.repeat(10000) + '\0';
expect(isBinaryFile(content)).toBe(false);
});
test('should handle empty content', () => {
expect(isBinaryFile('')).toBe(false);
});
test('should handle pure text', () => {
expect(isBinaryFile('Hello, World!')).toBe(false);
});
test('should handle binary-like text', () => {
expect(isBinaryFile('01001000 01100101')).toBe(false); // Text representation of binary
});
});
});