mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-06-28 05:29:29 +00:00
280 lines
7.1 KiB
TypeScript
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);
|
|
});
|
|
});
|
|
});
|
|
});
|