vibetunnel/web/src/test/unit/session-validation.test.ts
2025-06-19 01:39:27 +02:00

280 lines
7.1 KiB
TypeScript

import { describe, it, expect } from 'vitest';
// Session validation utilities that should be in the actual code
const validateSessionId = (id: unknown): boolean => {
return typeof id === 'string' && /^[a-f0-9-]+$/.test(id);
};
const validateCommand = (command: unknown): boolean => {
return (
Array.isArray(command) &&
command.length > 0 &&
command.every((arg) => typeof arg === 'string' && arg.length > 0)
);
};
const validateWorkingDir = (dir: unknown): boolean => {
return typeof dir === 'string' && dir.length > 0 && !dir.includes('\0');
};
const sanitizePath = (path: string): string => {
// Remove null bytes and normalize
return path.replace(/\0/g, '').normalize();
};
const isValidSessionName = (name: unknown): boolean => {
return (
typeof name === 'string' &&
name.length > 0 &&
name.length <= 255 &&
// eslint-disable-next-line no-control-regex
!/[<>:"|?*\x00-\x1f]/.test(name)
);
};
describe('Session Validation', () => {
describe('validateSessionId', () => {
it('should accept valid session IDs', () => {
const validIds = [
'abc123def456',
'123e4567-e89b-12d3-a456-426614174000',
'a1b2c3d4-e5f6-7890-abcd-ef1234567890',
'a1b2c3d4',
];
validIds.forEach((id) => {
expect(validateSessionId(id)).toBe(true);
});
});
it('should reject invalid session IDs', () => {
const invalidIds = [
'',
null,
undefined,
123,
'session with spaces',
'../../../etc/passwd',
'session;rm -rf /',
'session$variable',
'session`command`',
];
invalidIds.forEach((id) => {
expect(validateSessionId(id)).toBe(false);
});
});
});
describe('validateCommand', () => {
it('should accept valid commands', () => {
const validCommands = [
['bash'],
['ls', '-la'],
['node', 'app.js'],
['python', '-m', 'http.server', '8000'],
['vim', 'file.txt'],
];
validCommands.forEach((cmd) => {
expect(validateCommand(cmd)).toBe(true);
});
});
it('should reject invalid commands', () => {
const invalidCommands = [
[],
null,
undefined,
'bash',
[''],
[123],
[null],
['bash', null],
['bash', 123],
];
invalidCommands.forEach((cmd) => {
expect(validateCommand(cmd)).toBe(false);
});
});
});
describe('validateWorkingDir', () => {
it('should accept valid directories', () => {
const validDirs = [
'/home/user',
'/tmp',
'.',
'..',
'/home/user/projects/my-app',
'C:\\Users\\User\\Documents',
];
validDirs.forEach((dir) => {
expect(validateWorkingDir(dir)).toBe(true);
});
});
it('should reject invalid directories', () => {
const invalidDirs = ['', null, undefined, 123, '/path/with\0null', '\0/etc/passwd'];
invalidDirs.forEach((dir) => {
expect(validateWorkingDir(dir)).toBe(false);
});
});
});
describe('isValidSessionName', () => {
it('should accept valid session names', () => {
const validNames = [
'My Session',
'Project Build',
'test-123',
'Development Server',
'SSH to production',
];
validNames.forEach((name) => {
expect(isValidSessionName(name)).toBe(true);
});
});
it('should reject invalid session names', () => {
const invalidNames = [
'',
null,
undefined,
'a'.repeat(256),
'session<script>',
'session>redirect',
'session:colon',
'session"quote',
'session|pipe',
'session?question',
'session*asterisk',
'session\0null',
'session\x01control',
];
invalidNames.forEach((name) => {
expect(isValidSessionName(name)).toBe(false);
});
});
});
describe('sanitizePath', () => {
it('should remove null bytes', () => {
expect(sanitizePath('/path/with\0null')).toBe('/path/withnull');
expect(sanitizePath('\0/etc/passwd')).toBe('/etc/passwd');
expect(sanitizePath('file\0\0\0.txt')).toBe('file.txt');
});
it('should normalize paths', () => {
expect(sanitizePath('/path//to///file')).toBe('/path//to///file');
expect(sanitizePath('café.txt')).toBe('café.txt');
});
it('should handle clean paths', () => {
expect(sanitizePath('/home/user')).toBe('/home/user');
expect(sanitizePath('file.txt')).toBe('file.txt');
});
});
describe('Environment Variable Validation', () => {
const isValidEnvVar = (env: unknown): boolean => {
if (typeof env !== 'object' || env === null) return false;
for (const [key, value] of Object.entries(env)) {
// Key must be valid env var name
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) return false;
// Value must be string
if (typeof value !== 'string') return false;
// No null bytes
if (value.includes('\0')) return false;
}
return true;
};
it('should accept valid environment variables', () => {
const validEnvs = [
{ PATH: '/usr/bin:/usr/local/bin' },
{ NODE_ENV: 'production' },
{ HOME: '/home/user', SHELL: '/bin/bash' },
{ API_KEY: 'secret123', PORT: '3000' },
];
validEnvs.forEach((env) => {
expect(isValidEnvVar(env)).toBe(true);
});
});
it('should reject invalid environment variables', () => {
const invalidEnvs = [
null,
undefined,
'not an object',
{ '': 'empty key' },
{ '123start': 'number start' },
{ 'has-dash': 'invalid char' },
{ 'has space': 'invalid char' },
{ valid: 123 },
{ valid: null },
{ valid: undefined },
{ valid: 'has\0null' },
];
invalidEnvs.forEach((env) => {
expect(isValidEnvVar(env)).toBe(false);
});
});
});
describe('Command Injection Prevention', () => {
const hasDangerousPatterns = (input: string): boolean => {
const dangerous = [
/[;&|`$(){}[\]<>]/, // Shell metacharacters
/\.\./, // Directory traversal
/\0/, // Null bytes
/\n|\r/, // Newlines
];
return dangerous.some((pattern) => pattern.test(input));
};
it('should detect dangerous patterns', () => {
const dangerous = [
'command; rm -rf /',
'command && evil',
'command || evil',
'command | evil',
'command `evil`',
'command $(evil)',
'command > /etc/passwd',
'command < /etc/shadow',
'../../../etc/passwd',
'file\0.txt',
'multi\nline',
];
dangerous.forEach((input) => {
expect(hasDangerousPatterns(input)).toBe(true);
});
});
it('should allow safe patterns', () => {
const safe = [
'normal-file.txt',
'my_session_123',
'/home/user/project',
'Project Name',
'test@example.com',
];
safe.forEach((input) => {
expect(hasDangerousPatterns(input)).toBe(false);
});
});
});
});