feat: capture SSH exec commands (PLAN.md 4.4)
Bots often send commands via `ssh user@host <command>` (exec request) rather than requesting an interactive shell. These were previously rejected silently. Now exec commands are captured, stored on the session record, and displayed in the web UI session detail page. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -252,6 +252,54 @@ func TestIntegrationSSHConnect(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// Test exec command capture.
|
||||
t.Run("exec_command", func(t *testing.T) {
|
||||
clientCfg := &ssh.ClientConfig{
|
||||
User: "root",
|
||||
Auth: []ssh.AuthMethod{ssh.Password("toor")},
|
||||
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
|
||||
Timeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
client, err := ssh.Dial("tcp", addr, clientCfg)
|
||||
if err != nil {
|
||||
t.Fatalf("SSH dial: %v", err)
|
||||
}
|
||||
defer client.Close()
|
||||
|
||||
session, err := client.NewSession()
|
||||
if err != nil {
|
||||
t.Fatalf("new session: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
// Run a command via exec (no PTY, no shell).
|
||||
if err := session.Run("uname -a"); err != nil {
|
||||
// Run returns an error because the server closes the channel,
|
||||
// but that's expected.
|
||||
_ = err
|
||||
}
|
||||
|
||||
// Give the server a moment to store the command.
|
||||
time.Sleep(200 * time.Millisecond)
|
||||
|
||||
// Verify the exec command was captured.
|
||||
sessions, err := store.GetRecentSessions(context.Background(), 50, false)
|
||||
if err != nil {
|
||||
t.Fatalf("GetRecentSessions: %v", err)
|
||||
}
|
||||
var foundExec bool
|
||||
for _, s := range sessions {
|
||||
if s.ExecCommand != nil && *s.ExecCommand == "uname -a" {
|
||||
foundExec = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !foundExec {
|
||||
t.Error("expected a session with exec_command='uname -a'")
|
||||
}
|
||||
})
|
||||
|
||||
// Test threshold acceptance: after enough failed dials, a subsequent
|
||||
// dial with the same credentials should succeed via threshold or
|
||||
// remembered credential.
|
||||
|
||||
Reference in New Issue
Block a user