fix(cli): refuse host filesystem access from remote TUI clients (#14677)

This commit is contained in:
Justin Tahara authored and GitHub committed 2026-09-11 22:19:29 +00:00
1 parent c10954ad7a
commit e565d19c6c
5 files changed
+56 -1

No files matched your search

+3
View File
@@ -100,6 +100,9 @@ environment variable (the --host-key flag takes precedence).`,
onyx-cli serve --host 0.0.0.0 --port 2222
onyx-cli serve --idle-timeout 30m --max-session-timeout 2h`,
RunE: func(cmd *cobra.Command, args []string) error {
// Sessions here belong to remote clients; keep them off the host filesystem.
tui.RemoteMode = true
serverCfg := config.Load()
if serverCfg.ServerURL == "" {
return exitcodes.New(exitcodes.NotConfigured, "server URL is not configured\n Run: onyx-cli chat to complete first-time setup")
+4
View File
@@ -267,6 +267,10 @@ func cmdSelectModel(m Model, idxStr string) (Model, tea.Cmd) {
}
func cmdAttach(m Model, pathStr string) (Model, tea.Cmd) {
if RemoteMode {
m.viewport.addWarning("/attach is disabled over SSH: paths resolve on the server host, not yours.")
return m, nil
}
if pathStr == "" {
m.viewport.addWarning("Usage: /attach <file_path>")
return m, nil
+39
View File
@@ -1,6 +1,8 @@
package tui
import (
"os"
"path/filepath"
"strings"
"testing"
@@ -279,6 +281,43 @@ func TestSelectAgentFromPickerUsesIDWhenNameMatches(t *testing.T) {
}
}
func TestAttachRefusedInRemoteMode(t *testing.T) {
RemoteMode = true
t.Cleanup(func() { RemoteMode = false })
m := NewModel(config.DefaultConfig(), nil)
m, cmd := cmdAttach(m, "/etc/passwd")
if cmd != nil {
t.Fatal("expected no upload command in remote mode")
}
if len(m.viewport.entries) == 0 {
t.Fatal("expected a warning entry")
}
got := m.viewport.entries[len(m.viewport.entries)-1].content
if !strings.Contains(got, "disabled over SSH") {
t.Errorf("warning = %q, want a refusal mentioning SSH", got)
}
}
func TestDetectFileDropIgnoredInRemoteMode(t *testing.T) {
path := filepath.Join(t.TempDir(), "secret.txt")
if err := os.WriteFile(path, []byte("secret"), 0o600); err != nil {
t.Fatalf("write temp file: %v", err)
}
if got := detectFileDrop(path); got != path {
t.Fatalf("local detectFileDrop = %q, want %q", got, path)
}
RemoteMode = true
t.Cleanup(func() { RemoteMode = false })
if got := detectFileDrop(path); got != "" {
t.Errorf("remote detectFileDrop = %q, want no match", got)
}
}
func TestSelectModelInvalidIndex(t *testing.T) {
m := NewModel(config.DefaultConfig(), nil)
m, _ = cmdSelectModel(m, "5")
+5 -1
View File
@@ -204,8 +204,12 @@ type fileDropMsg struct {
path string
}
// detectFileDrop checks if the text looks like a file path.
// detectFileDrop checks if the text looks like a file path. It never matches
// in remote mode, where the path would resolve on the server host.
func detectFileDrop(text string) string {
if RemoteMode {
return ""
}
cleaned := strings.Trim(text, "'\"")
if cleaned == "" {
return ""
+5
View File
@@ -14,6 +14,11 @@ const (
MaxAPIKeyRetries = 5
)
// RemoteMode marks this process as serving the TUI to remote clients.
// Commands that touch the host filesystem are refused while it is set,
// because those paths resolve on the operator's machine, not the client's.
var RemoteMode bool
// --- auth prompt (bubbletea model) ---
type authState int