@lethevimlet/sshift
Advanced tools
+1
-1
| { | ||
| "name": "@lethevimlet/sshift", | ||
| "version": "1.3.9", | ||
| "version": "1.4.0", | ||
| "description": "Web-based SSH + SFTP terminal client with tabs and bookmarks", | ||
@@ -5,0 +5,0 @@ "main": "src/server/index.js", |
@@ -84,7 +84,56 @@ /** | ||
| } catch (err) { | ||
| console.error('[SFTP] Error in sftp-list:', err.message); | ||
| socket.emit('sftp-error', { message: err.message }); | ||
| console.error('[SFTP] Error in sftp-list:', err.message, 'code:', err.code); | ||
| const homeDir = sftpManager.home(data.sessionId); | ||
| const isPermissionDenied = err.code === 3 | ||
| || (err.message && ( | ||
| err.message.includes('Permission denied') | ||
| || err.message.includes('permission denied') | ||
| || err.message.includes('EACCES') | ||
| )); | ||
| // If listing "/" fails with permission denied and we know the home | ||
| // directory, try falling back to it automatically. This handles the | ||
| // common case of chrooted SFTP servers that restrict root access. | ||
| if (isPermissionDenied && homeDir && data.path === '/') { | ||
| console.log('[SFTP] Permission denied on /, falling back to home directory:', homeDir); | ||
| try { | ||
| const homeFiles = await sftpManager.list(data.sessionId, homeDir); | ||
| socket.emit('sftp-list-result', { | ||
| path: homeDir, | ||
| files: homeFiles, | ||
| sessionId: data.sessionId, | ||
| redirectedFrom: '/', | ||
| homeDir | ||
| }); | ||
| return; | ||
| } catch (homeErr) { | ||
| console.error('[SFTP] Home directory also failed:', homeErr.message); | ||
| socket.emit('sftp-error', { | ||
| message: homeErr.message, | ||
| sessionId: data.sessionId, | ||
| homeDir | ||
| }); | ||
| return; | ||
| } | ||
| } | ||
| socket.emit('sftp-error', { | ||
| message: err.message, | ||
| sessionId: data.sessionId, | ||
| homeDir, | ||
| isPermissionDenied | ||
| }); | ||
| } | ||
| }); | ||
| // SFTP get home directory | ||
| socket.on('sftp-home', (data) => { | ||
| const homeDir = sftpManager.home(data.sessionId); | ||
| socket.emit('sftp-home-result', { | ||
| sessionId: data.sessionId, | ||
| homeDir | ||
| }); | ||
| }); | ||
| // SFTP download (streamed) | ||
@@ -91,0 +140,0 @@ socket.on('sftp-download', async (data) => { |
@@ -89,12 +89,26 @@ const { Client } = require('ssh2'); | ||
| socket: socket, | ||
| // Connection info for sessions API | ||
| host: options.host, | ||
| port: options.port || 22, | ||
| username: options.username, | ||
| connectedAt: Date.now() | ||
| connectedAt: Date.now(), | ||
| homeDir: null | ||
| }; | ||
| this.sessions.set(sessionId, session); | ||
| console.log(`[SFTP] SFTP session started: ${sessionId}`); | ||
| resolve(sessionId); | ||
| // Resolve the user's home directory so we can fall back to it | ||
| // when listing "/" fails (e.g. chrooted SFTP or restricted | ||
| // server configs). realpath('.') returns the absolute path of | ||
| // the remote working directory, which defaults to the user's | ||
| // home on most SSH servers. | ||
| sftp.realpath('.', (realpathErr, resolvedPath) => { | ||
| if (!realpathErr && resolvedPath) { | ||
| session.homeDir = resolvedPath; | ||
| console.log(`[SFTP] Resolved home directory for ${options.username}: ${resolvedPath}`); | ||
| } else { | ||
| console.warn(`[SFTP] Could not resolve home directory: ${realpathErr?.message}`); | ||
| } | ||
| this.sessions.set(sessionId, session); | ||
| console.log(`[SFTP] SFTP session started: ${sessionId}`); | ||
| resolve(sessionId); | ||
| }); | ||
| }); | ||
@@ -118,8 +132,32 @@ }); | ||
| try { | ||
| conn.connect(config); | ||
| } catch (err) { | ||
| console.error(`[SFTP] Connect exception: ${err.message}`); | ||
| reject(err); | ||
| try { | ||
| conn.connect(config); | ||
| } catch (err) { | ||
| console.error(`[SFTP] Connect exception: ${err.message}`); | ||
| reject(err); | ||
| } | ||
| }); | ||
| } | ||
| home(sessionId) { | ||
| const session = this.sessions.get(sessionId); | ||
| if (!session) return null; | ||
| return session.homeDir || null; | ||
| } | ||
| realpath(sessionId, path) { | ||
| return new Promise((resolve, reject) => { | ||
| const session = this.sessions.get(sessionId); | ||
| if (!session) { | ||
| reject(new Error('Session not found')); | ||
| return; | ||
| } | ||
| session.sftp.realpath(path, (err, resolvedPath) => { | ||
| if (err) { | ||
| reject(err); | ||
| return; | ||
| } | ||
| resolve(resolvedPath); | ||
| }); | ||
| }); | ||
@@ -126,0 +164,0 @@ } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
AI-detected potential code anomaly
Supply chain riskAI has identified unusual behaviors that may pose a security risk.
Found 2 instances in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
4130113
0.17%28490
0.54%27
-3.57%