-
Notifications
You must be signed in to change notification settings - Fork 8.9k
Terminate ssh/scp options with "--" separator #13697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: trunk
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,13 @@ | ||
| package codespaces | ||
|
|
||
| import ( | ||
| "context" | ||
| "fmt" | ||
| "os" | ||
| "path/filepath" | ||
| "runtime" | ||
| "slices" | ||
| "strings" | ||
| "testing" | ||
| ) | ||
|
|
||
|
|
@@ -65,6 +71,21 @@ func TestParseSSHArgs(t *testing.T) { | |
| ParsedArgs: []string{"-v"}, | ||
| Command: []string{"echo", "-b", "test"}, | ||
| }, | ||
| { | ||
| Args: []string{"-v", "--", "echo", "hi"}, | ||
| ParsedArgs: []string{"-v"}, | ||
| Command: []string{"echo", "hi"}, | ||
| }, | ||
| { | ||
| Args: []string{"--", "-Fconfig", "arg"}, | ||
| ParsedArgs: []string{}, | ||
| Command: []string{"-Fconfig", "arg"}, | ||
| }, | ||
| { | ||
| Args: []string{"-v", "--"}, | ||
| ParsedArgs: []string{"-v"}, | ||
| Command: nil, | ||
| }, | ||
| { | ||
| Args: []string{"-b"}, | ||
| ParsedArgs: nil, | ||
|
|
@@ -108,6 +129,11 @@ func TestParseSCPArgs(t *testing.T) { | |
| ParsedArgs: []string{}, | ||
| Command: []string{"local/file", "remote:file"}, | ||
| }, | ||
| { | ||
| Args: []string{"--", "-Fconfig", "remote:file"}, | ||
| ParsedArgs: []string{}, | ||
| Command: []string{"-Fconfig", "remote:file"}, | ||
| }, | ||
| { | ||
| Args: []string{"-c"}, | ||
| ParsedArgs: nil, | ||
|
|
@@ -151,3 +177,82 @@ func checkParseResult(t *testing.T, tcase parseTestCase, gotArgs, gotCmd []strin | |
| t.Errorf("command does not match parsed command. got: '%s', expected: '%s'", commandStr, parsedCommandStr) | ||
| } | ||
| } | ||
|
|
||
| // TestNewSSHCommandUsesEndOfOptionsSeparator asserts that "--" is placed | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: I think these new tests could just be one table test nit: I know this file doesn't already use it, so this isn't a blocking thing, but it would be ideal if we could modernize the tests by using testify.Assert / testify.Require like the rest of the codebase. Happy to tackle that as a follow-up ourselves if no appetite for it 😁
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To keep this PR focused on the security fix, I'd prefer to leave the table-test consolidation (and the testify migration you mentioned) for a follow-up rather than expand the diff here. Happy to open that follow-up, or I can fold it in now if you'd rather — your call. |
||
| // immediately before the destination in the ssh argv. | ||
| func TestNewSSHCommandUsesEndOfOptionsSeparator(t *testing.T) { | ||
| stubExecutablesOnPath(t, "ssh") | ||
|
|
||
| cmd, _, err := newSSHCommand(context.Background(), 1234, "user@localhost", []string{"-v"}, []string{"echo", "hello"}) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| // cmd.Args[0] is the ssh executable path; the rest are arguments. | ||
| args := cmd.Args[1:] | ||
|
|
||
| dashDashIdx := slices.Index(args, "--") | ||
| if dashDashIdx == -1 { | ||
| t.Fatalf("expected ssh args to contain a \"--\" separator, got: %v", args) | ||
| } | ||
|
|
||
| dstIdx := slices.Index(args, "user@localhost") | ||
| if dstIdx == -1 { | ||
| t.Fatalf("expected destination in ssh args, got: %v", args) | ||
| } | ||
|
|
||
| if dashDashIdx+1 != dstIdx { | ||
| t.Errorf("expected \"--\" to immediately precede destination, got args: %v", args) | ||
| } | ||
| } | ||
|
|
||
| // TestNewSCPCommandUsesEndOfOptionsSeparator asserts that "--" precedes | ||
| // the file arguments in the scp argv. | ||
| func TestNewSCPCommandUsesEndOfOptionsSeparator(t *testing.T) { | ||
| stubExecutablesOnPath(t, "scp") | ||
|
|
||
| cmd, err := newSCPCommand(context.Background(), 1234, "user@localhost", []string{"local/file", "remote:file"}) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error: %v", err) | ||
| } | ||
|
|
||
| args := cmd.Args[1:] | ||
|
|
||
| dashDashIdx := slices.Index(args, "--") | ||
| if dashDashIdx == -1 { | ||
| t.Fatalf("expected scp args to contain a \"--\" separator, got: %v", args) | ||
| } | ||
|
|
||
| localIdx := slices.Index(args, "local/file") | ||
| if localIdx == -1 { | ||
| t.Fatalf("expected file arg in scp args, got: %v", args) | ||
| } | ||
|
|
||
| if dashDashIdx+1 != localIdx { | ||
| t.Errorf("expected \"--\" to immediately precede file arguments, got args: %v", args) | ||
| } | ||
| } | ||
|
|
||
| // stubExecutablesOnPath creates empty executable files for names in a temp | ||
| // dir and prepends it to PATH for the test, so safeexec.LookPath resolves | ||
| // without requiring the real binaries on the host. | ||
| func stubExecutablesOnPath(t *testing.T, names ...string) { | ||
| t.Helper() | ||
|
|
||
| dir := t.TempDir() | ||
| for _, name := range names { | ||
| if runtime.GOOS == "windows" { | ||
| name += ".exe" | ||
| } | ||
| path := filepath.Join(dir, name) | ||
| f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0755) | ||
| if err != nil { | ||
| t.Fatalf("failed to create stub %s: %v", name, err) | ||
| } | ||
| if err := f.Close(); err != nil { | ||
| t.Fatalf("failed to close stub %s: %v", name, err) | ||
| } | ||
| } | ||
|
|
||
| t.Setenv("PATH", strings.Join([]string{dir, os.Getenv("PATH")}, string(os.PathListSeparator))) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think Copilot is right. I think we should respect a user-provided
--. WDYT @anumol-baby?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call — addressed in latest commit. parseArgs now treats a bare -- as the end-of-options marker: it's dropped, and everything after it becomes the command, so a user-supplied -- is respected.