ssh-bridge-ai
Version:
One Command Magic SSH with Invisible Analytics - Connect to any server instantly with 'sshbridge user@server'. Zero setup, zero friction, pure magic. Industry-standard security with behind-the-scenes business intelligence.
205 lines (175 loc) • 5.47 kB
JavaScript
const path = require('path');
/**
* Secure Command Sanitizer - WHITELIST ONLY APPROACH
*
* Prevents execution of dangerous commands by only allowing safe commands
*/
class CommandSanitizer {
constructor(options = {}) {
// WHITELIST ONLY - NO EXCEPTIONS
this.whitelistedCommands = [
'ls', 'cat', 'grep', 'find', 'ps', 'top', 'df', 'du',
'whoami', 'pwd', 'date', 'uptime', 'free', 'netstat',
'head', 'tail', 'wc', 'sort', 'uniq', 'cut', 'awk',
'sed', 'tr', 'tee', 'echo', 'printf', 'test', 'expr',
'bc', 'dc', 'cal', 'clear', 'history', 'alias', 'type',
'which', 'whereis', 'locate', 'updatedb', 'man', 'info',
'help', 'apropos', 'whatis', 'tty', 'stty', 'reset'
];
this.whitelistedArguments = [
'-l', '-a', '-h', '--help', '--version', '-v', '-V',
'-r', '-R', '-f', '-F', '-i', '-n', '-c', '-w',
'-x', '-d', '-s', '-t', '-u', '-g', '-o', '-p'
];
// Maximum limits for security
this.maxCommandLength = 1000;
this.maxArgumentLength = 100;
this.maxArguments = 10;
}
/**
* Sanitize and validate command
* @param {string} command - Command to sanitize
* @returns {string} - Sanitized command
* @throws {Error} - If command is dangerous or invalid
*/
sanitizeCommand(command) {
// Basic validation
if (!command || typeof command !== 'string') {
throw new Error('Command must be a non-empty string');
}
// Length validation
if (command.length > this.maxCommandLength) {
throw new Error(`Command too long: ${command.length} > ${this.maxCommandLength}`);
}
// Null byte check
if (command.includes('\x00')) {
throw new Error('Command contains null bytes');
}
// Control character check
if (/[\x01-\x1F\x7F]/.test(command)) {
throw new Error('Command contains control characters');
}
// Unicode normalization
const normalized = command.normalize('NFC');
if (normalized !== command) {
throw new Error('Command contains non-normalized Unicode');
}
// Parse command into executable and arguments
const parts = command.trim().split(/\s+/);
if (parts.length > this.maxArguments) {
throw new Error(`Too many arguments: ${parts.length} > ${this.maxArguments}`);
}
const executable = parts[0];
const args = parts.slice(1);
// Check if executable is whitelisted
if (!this.whitelistedCommands.includes(executable)) {
throw new Error(`Command '${executable}' not allowed`);
}
// Check if arguments are safe
for (const arg of args) {
if (!this.isArgumentSafe(arg)) {
throw new Error(`Argument '${arg}' not allowed`);
}
}
return command;
}
/**
* Check if argument is safe
* @param {string} arg - Argument to check
* @returns {boolean} - True if safe
*/
isArgumentSafe(arg) {
// Check if argument is whitelisted
if (this.whitelistedArguments.includes(arg)) {
return true;
}
// Check if argument contains only safe characters (including quotes and wildcards)
if (!/^[a-zA-Z0-9\/\._\-"*?\[\]{}]+$/.test(arg)) {
return false;
}
// Check length
if (arg.length > this.maxArgumentLength) {
return false;
}
// Block path traversal
if (this.containsPathTraversal(arg)) {
return false;
}
return true;
}
/**
* Check for path traversal attempts
* @param {string} input - Input to check
* @returns {boolean} - True if traversal detected
*/
containsPathTraversal(input) {
// Block ALL absolute paths
if (input.includes('/') || input.includes('\\')) {
return true;
}
// Block directory traversal
if (input.includes('..')) {
return true;
}
// Block encoded traversal
const encodedPatterns = [
/%2e%2e%2f/i, // URL encoded ../
/%252e%252e%252f/i, // Double URL encoded ../
/%c0%ae%c0%ae%c0%af/i, // UTF-8 encoded ../
/\u2215/i, // Unicode slash
/\u2216/i // Unicode backslash
];
for (const pattern of encodedPatterns) {
if (pattern.test(input)) {
return true;
}
}
return false;
}
/**
* Legacy method for backward compatibility
* @deprecated Use sanitizeCommand instead
*/
isCommandSafe(command) {
try {
this.sanitizeCommand(command);
return true;
} catch (error) {
return false;
}
}
/**
* Get list of allowed commands
* @returns {string[]} - List of allowed commands
*/
getAllowedCommands() {
return [...this.whitelistedCommands];
}
/**
* Get list of allowed arguments
* @returns {string[]} - List of allowed arguments
*/
getAllowedArguments() {
return [...this.whitelistedArguments];
}
/**
* Add custom allowed command (for admin use only)
* @param {string} command - Command to add
*/
addAllowedCommand(command) {
if (typeof command === 'string' && command.trim()) {
this.whitelistedCommands.push(command.trim());
}
}
/**
* Remove custom allowed command
* @param {string} command - Command to remove
*/
removeAllowedCommand(command) {
const index = this.whitelistedCommands.indexOf(command);
if (index > -1) {
this.whitelistedCommands.splice(index, 1);
}
}
}
module.exports = { CommandSanitizer };