@@ -28,12 +28,19 @@ type SSHProxyConfig struct {
2828
2929// SSHProxy handles proxying SSH connections with credential injection
3030type SSHProxy struct {
31- config SSHProxyConfig
31+ config SSHProxyConfig
32+ mutex sync.Mutex
33+ sessionData []byte // Store session data for logging
34+ inputBuffer []byte // Buffer for input data to batch keystrokes
35+ inputChannelType session.TerminalChannelType // Channel type for buffered input
36+ }
37+
38+ // channelState holds per-channel state for tracking session type
39+ type channelState struct {
3240 mutex sync.Mutex
33- sessionData []byte // Store session data for logging
34- inputBuffer []byte // Buffer for input data to batch keystrokes
35- isBinarySession bool // True if this is SFTP/SCP binary protocol (don't log raw data)
36- sftpParser * SFTPParser // Parser for SFTP protocol to extract file operations
41+ channelType session.TerminalChannelType // Type of channel (terminal, exec, sftp)
42+ isBinarySession bool // True if this channel is SFTP/SCP binary protocol
43+ sftpParser * SFTPParser // Parser for SFTP protocol to extract file operations
3744}
3845
3946// NewSSHProxy creates a new SSH proxy instance
@@ -222,22 +229,25 @@ func (p *SSHProxy) handleChannel(ctx context.Context, newChannel ssh.NewChannel,
222229 Str ("channelType" , channelType ).
223230 Msg ("SSH channel established" )
224231
232+ // Create per-channel state for tracking binary sessions (SFTP/SCP)
233+ chState := & channelState {}
234+
225235 // Handle requests for this channel (pty-req, shell, exec, etc.)
226- go p .handleChannelRequests (clientRequests , serverChannel , sessionID , channelType )
227- go p .handleChannelRequests (serverRequests , clientChannel , sessionID , channelType )
236+ go p .handleChannelRequests (clientRequests , serverChannel , sessionID , channelType , chState )
237+ go p .handleChannelRequests (serverRequests , clientChannel , sessionID , channelType , chState )
228238
229239 // Proxy data bidirectionally with logging
230240 errChan := make (chan error , 2 )
231241
232242 // Client to Server
233243 go func () {
234- err := p .proxyData (clientChannel , serverChannel , "client→server" , sessionID , true )
244+ err := p .proxyData (clientChannel , serverChannel , "client→server" , sessionID , true , chState )
235245 errChan <- err
236246 }()
237247
238248 // Server to Client
239249 go func () {
240- err := p .proxyData (serverChannel , clientChannel , "server→client" , sessionID , false )
250+ err := p .proxyData (serverChannel , clientChannel , "server→client" , sessionID , false , chState )
241251 errChan <- err
242252 }()
243253
@@ -258,7 +268,7 @@ func (p *SSHProxy) handleChannel(ctx context.Context, newChannel ssh.NewChannel,
258268}
259269
260270// handleChannelRequests handles channel-specific requests (pty, shell, exec, etc.)
261- func (p * SSHProxy ) handleChannelRequests (requests <- chan * ssh.Request , targetChannel ssh.Channel , sessionID string , channelType string ) {
271+ func (p * SSHProxy ) handleChannelRequests (requests <- chan * ssh.Request , targetChannel ssh.Channel , sessionID string , channelType string , chState * channelState ) {
262272 for req := range requests {
263273 log .Debug ().
264274 Str ("sessionID" , sessionID ).
@@ -278,15 +288,17 @@ func (p *SSHProxy) handleChannelRequests(requests <-chan *ssh.Request, targetCha
278288
279289 // Determine the type of operation for better logging
280290 opType := "exec"
291+ chState .mutex .Lock ()
292+ chState .channelType = session .TerminalChannelExec
281293 if strings .HasPrefix (command , "scp " ) {
282294 opType = "scp"
283- // Mark as binary session so we don't log the raw file data
284- p .mutex .Lock ()
285- p .isBinarySession = true
286- p .mutex .Unlock ()
295+ // Mark this channel as binary so we don't log the raw file data
296+ chState .isBinarySession = true
297+ chState .channelType = session .TerminalChannelSFTP // SCP is file transfer
287298 } else if strings .Contains (command , "sftp" ) {
288299 opType = "sftp"
289300 }
301+ chState .mutex .Unlock ()
290302
291303 log .Info ().
292304 Str ("sessionID" , sessionID ).
@@ -296,27 +308,31 @@ func (p *SSHProxy) handleChannelRequests(requests <-chan *ssh.Request, targetCha
296308
297309 // Log the exec command to the session recording
298310 var logMessage string
311+ var channelType session.TerminalChannelType
299312 if opType == "scp" {
313+ channelType = session .TerminalChannelSFTP
300314 // Parse SCP command for more readable logging
301315 // scp -t /path = receiving file TO server
302316 // scp -f /path = sending file FROM server
303317 if strings .Contains (command , " -t " ) {
304318 path := extractSCPPath (command )
305- logMessage = fmt .Sprintf ("[SCP UPLOAD] Uploading file to : %s\n " , path )
319+ logMessage = fmt .Sprintf ("Uploaded file: %s\n " , path )
306320 } else if strings .Contains (command , " -f " ) {
307321 path := extractSCPPath (command )
308- logMessage = fmt .Sprintf ("[SCP DOWNLOAD] Downloading file from : %s\n " , path )
322+ logMessage = fmt .Sprintf ("Downloaded file: %s\n " , path )
309323 } else {
310324 logMessage = fmt .Sprintf ("$ %s\n " , command )
311325 }
312326 } else {
327+ channelType = session .TerminalChannelExec
313328 logMessage = fmt .Sprintf ("$ %s\n " , command )
314329 }
315330
316331 event := session.TerminalEvent {
317- Timestamp : time .Now (),
318- EventType : session .TerminalEventInput ,
319- Data : []byte (logMessage ),
332+ Timestamp : time .Now (),
333+ EventType : session .TerminalEventInput ,
334+ ChannelType : channelType ,
335+ Data : []byte (logMessage ),
320336 }
321337 if err := p .config .SessionLogger .LogTerminalEvent (event ); err != nil {
322338 log .Error ().Err (err ).
@@ -327,6 +343,9 @@ func (p *SSHProxy) handleChannelRequests(requests <-chan *ssh.Request, targetCha
327343 }
328344 }
329345 case "shell" :
346+ chState .mutex .Lock ()
347+ chState .channelType = session .TerminalChannelShell
348+ chState .mutex .Unlock ()
330349 log .Info ().
331350 Str ("sessionID" , sessionID ).
332351 Msg ("SSH interactive shell requested" )
@@ -344,15 +363,17 @@ func (p *SSHProxy) handleChannelRequests(requests <-chan *ssh.Request, targetCha
344363
345364 // Log SFTP sessions and set up SFTP parser for file operation logging
346365 if subsystem == "sftp" {
347- p .mutex .Lock ()
348- p .isBinarySession = true
349- p .sftpParser = NewSFTPParser ()
350- p .mutex .Unlock ()
366+ chState .mutex .Lock ()
367+ chState .channelType = session .TerminalChannelSFTP
368+ chState .isBinarySession = true
369+ chState .sftpParser = NewSFTPParser ()
370+ chState .mutex .Unlock ()
351371
352372 event := session.TerminalEvent {
353- Timestamp : time .Now (),
354- EventType : session .TerminalEventInput ,
355- Data : []byte ("File transfer session started\n " ),
373+ Timestamp : time .Now (),
374+ EventType : session .TerminalEventInput ,
375+ ChannelType : session .TerminalChannelSFTP ,
376+ Data : []byte ("File transfer session started\n " ),
356377 }
357378 if err := p .config .SessionLogger .LogTerminalEvent (event ); err != nil {
358379 log .Error ().Err (err ).
@@ -401,7 +422,7 @@ func (p *SSHProxy) handleChannelRequests(requests <-chan *ssh.Request, targetCha
401422}
402423
403424// proxyData proxies data between channels with optional logging
404- func (p * SSHProxy ) proxyData (src io.Reader , dst io.Writer , direction string , sessionID string , logInput bool ) error {
425+ func (p * SSHProxy ) proxyData (src io.Reader , dst io.Writer , direction string , sessionID string , logInput bool , chState * channelState ) error {
405426 buf := make ([]byte , 32 * 1024 ) // 32KB buffer
406427
407428 // Flush any remaining input buffer on exit
@@ -414,11 +435,12 @@ func (p *SSHProxy) proxyData(src io.Reader, dst io.Writer, direction string, ses
414435 for {
415436 n , err := src .Read (buf )
416437 if n > 0 {
417- // Check if this is a binary session (SFTP/SCP)
418- p .mutex .Lock ()
419- isBinary := p .isBinarySession
420- sftpParser := p .sftpParser
421- p .mutex .Unlock ()
438+ // Check if this channel is a binary session (SFTP/SCP)
439+ chState .mutex .Lock ()
440+ isBinary := chState .isBinarySession
441+ sftpParser := chState .sftpParser
442+ channelType := chState .channelType
443+ chState .mutex .Unlock ()
422444
423445 if isBinary && sftpParser != nil && logInput {
424446 // Parse SFTP packets from client->server direction to extract file operations
@@ -427,9 +449,10 @@ func (p *SSHProxy) proxyData(src io.Reader, dst io.Writer, direction string, ses
427449 // Log each SFTP operation
428450 logMsg := FormatOperation (op ) + "\n "
429451 event := session.TerminalEvent {
430- Timestamp : time .Now (),
431- EventType : session .TerminalEventInput ,
432- Data : []byte (logMsg ),
452+ Timestamp : time .Now (),
453+ EventType : session .TerminalEventInput ,
454+ ChannelType : session .TerminalChannelSFTP ,
455+ Data : []byte (logMsg ),
433456 }
434457 if err := p .config .SessionLogger .LogTerminalEvent (event ); err != nil {
435458 log .Error ().Err (err ).
@@ -448,13 +471,14 @@ func (p *SSHProxy) proxyData(src io.Reader, dst io.Writer, direction string, ses
448471 } else if ! isBinary {
449472 // Regular terminal session logging
450473 if logInput {
451- p .bufferInput (buf [:n ], sessionID )
474+ p .bufferInput (buf [:n ], sessionID , channelType )
452475 } else {
453476 // For output, log immediately as before
454477 event := session.TerminalEvent {
455- Timestamp : time .Now (),
456- EventType : session .TerminalEventOutput ,
457- Data : make ([]byte , n ),
478+ Timestamp : time .Now (),
479+ EventType : session .TerminalEventOutput ,
480+ ChannelType : channelType ,
481+ Data : make ([]byte , n ),
458482 }
459483 copy (event .Data , buf [:n ])
460484
@@ -487,10 +511,12 @@ func (p *SSHProxy) proxyData(src io.Reader, dst io.Writer, direction string, ses
487511}
488512
489513// bufferInput accumulates input data and logs only when newline or control chars are encountered
490- func (p * SSHProxy ) bufferInput (data []byte , sessionID string ) {
514+ func (p * SSHProxy ) bufferInput (data []byte , sessionID string , channelType session. TerminalChannelType ) {
491515 p .mutex .Lock ()
492516 defer p .mutex .Unlock ()
493517
518+ p .inputChannelType = channelType
519+
494520 for _ , b := range data {
495521 p .inputBuffer = append (p .inputBuffer , b )
496522
@@ -516,9 +542,10 @@ func (p *SSHProxy) flushInputBufferUnsafe(sessionID string) {
516542 }
517543
518544 event := session.TerminalEvent {
519- Timestamp : time .Now (),
520- EventType : session .TerminalEventInput ,
521- Data : make ([]byte , len (p .inputBuffer )),
545+ Timestamp : time .Now (),
546+ EventType : session .TerminalEventInput ,
547+ ChannelType : p .inputChannelType ,
548+ Data : make ([]byte , len (p .inputBuffer )),
522549 }
523550 copy (event .Data , p .inputBuffer )
524551
0 commit comments