UNPKG

ssh-bridge-ai

Version:

AI-Powered SSH Tool with Bulletproof Connections & Enterprise Sandbox Security + Cursor-like Confirmation - Enable AI assistants to securely SSH into your servers with persistent sessions, keepalive, automatic recovery, sandbox command testing, and user c

183 lines (158 loc) 5.22 kB
const axios = require('axios'); const { Config } = require('./config'); const { ERROR_CODES, SECURITY, NETWORK } = require('./utils/constants'); const { ErrorHandler, NetworkError, SecurityError } = require('./utils/errors'); const logger = require('./utils/logger'); class APIClient { constructor() { // Use fallback URL from constants if environment variable not set this.baseURL = process.env.SSHBRIDGE_API_URL || require('./utils/constants').API.BASE_URL; if (!this.baseURL) { throw new Error('SSHBRIDGE_API_URL environment variable must be set for security. No hardcoded endpoints allowed.'); } // SECURITY: Validate URL format try { const url = new URL(this.baseURL); if (!['http:', 'https:'].includes(url.protocol)) { throw new Error('API URL must use HTTP or HTTPS protocol'); } } catch (error) { throw new Error(`Invalid API URL format: ${this.baseURL}`); } this.config = new Config(); } // SECURITY: Enhanced request method with validation async request(method, endpoint, data = null) { // SECURITY: Validate endpoint to prevent SSRF if (!this.isValidEndpoint(endpoint)) { throw new Error('Invalid endpoint detected'); } const headers = { 'Content-Type': 'application/json', }; const apiKey = this.config.getApiKey(); if (apiKey) { headers['Authorization'] = `Bearer ${apiKey}`; } try { const response = await axios({ method, url: `${this.baseURL}${endpoint}`, headers, data, timeout: NETWORK.HTTP_TIMEOUT, // SECURITY: Prevent redirects to external domains maxRedirects: 0, // SECURITY: Validate response status validateStatus: (status) => status >= 200 && status < 300 }); return response.data; } catch (error) { if (error.response) { throw new Error(error.response.data.message || 'API request failed'); } else if (error.request) { throw new Error('Network error - please check your connection'); } else { throw new Error('Request failed'); } } } // SECURITY: Endpoint validation to prevent SSRF isValidEndpoint(endpoint) { if (!endpoint || typeof endpoint !== 'string') { return false; } // Block dangerous endpoints const dangerousEndpoints = [ /^\/\//, // Absolute URLs /^https?:\/\//, // Full URLs /\.\./, // Directory traversal /\/etc\//, // System directories /\/proc\//, // Process info /\/sys\//, // System info /\/dev\//, // Device files /\/root\//, // Root directory /\/var\//, // Variable data /\/usr\// // User programs ]; return !dangerousEndpoints.some(pattern => pattern.test(endpoint)); } async register(email) { return this.request('POST', '/auth/register', { email }); } async verifyEmail(email, code) { return this.request('POST', '/auth/verify', { email, code }); } async validateUsage() { return this.request('GET', '/usage/validate'); } async logUsage(server, command) { return this.request('POST', '/usage/log', { server, command, timestamp: new Date().toISOString() }); } async getUsage() { return this.request('GET', '/usage'); } async getUserStatus(email) { try { // First try to get usage stats (requires authentication) const usage = await this.request('GET', '/usage'); return { tier: usage.plan === 'pro' ? 'pro' : 'free', serversUsed: usage.servers || 0, commandsUsed: usage.used || 0, serversLimit: usage.maxServers || 5, commandsLimit: usage.limit || 50 }; } catch (error) { // If user is not authenticated (no API key), return default free tier status if (error.message.includes('401') || error.message.includes('Unauthorized') || error.message.includes('Network error') || !this.config.getApiKey()) { return { tier: 'free', serversUsed: 0, commandsUsed: 0, serversLimit: 5, commandsLimit: 50 }; } // For other errors, re-throw throw error; } } async getServers() { return this.request('GET', '/servers'); } async upgradeAccount(plan) { return this.request('POST', '/billing/upgrade', { plan }); } async executeSSHCommand(host, username, command, options = {}) { const payload = { host, username, command, port: options.port || 22, private_key: options.private_key || null, password: options.password || null }; return this.request('POST', '/ssh/execute', payload); } async getExecutionToken(mode = 'hybrid') { return this.request('POST', '/ssh/get-token', { mode }); } async reportUsage(token, server, command, executionTime, success) { return this.request('POST', '/ssh/report-usage', { token, server, command, execution_time: executionTime, success }); } } module.exports = { APIClient };