Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 259876f

Browse files
committed
fix: Escape shell arguments
The commands passed to "coder sh" are passed as a single argument to "sh -c", so we need to shell-escape the command we pass. This change escapes spaces, backslash, and quotes.
1 parent b8067c0 commit 259876f

File tree

2 files changed

+83
-5
lines changed

2 files changed

+83
-5
lines changed

internal/cmd/shell.go

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,17 +80,54 @@ coder sh front-end-dev cat ~/config.json`,
8080
}
8181
}
8282

83+
// shellEscape escapes an argument so that we can pass it 'sh -c'
84+
// and have it do the right thing.
85+
//
86+
// Use this to ensure that the result of a command running in
87+
// the development environment behaves the same as the command
88+
// running via "coder sh".
89+
//
90+
// For example:
91+
//
92+
// $ coder sh env
93+
// $ go run ~/test.go 1 2 "3 4" '"abc def" \\abc' 5 6 "7 8 9"
94+
//
95+
// should produce the same output as:
96+
//
97+
// $ coder sh go run ~/test.go 1 2 "3 4" '"abc def" \\abc' 5 6 "7 8 9"
98+
func shellEscape(arg string) string {
99+
r := strings.NewReplacer(`\`, `\\`, `"`, `\"`, `'`, `\'`, ` `, `\ `)
100+
return r.Replace(arg)
101+
}
102+
83103
func shell(cmd *cobra.Command, cmdArgs []string) error {
84104
ctx := cmd.Context()
85-
command := "sh"
86-
args := []string{"-c"}
105+
106+
var command string
107+
var args []string
87108
if len(cmdArgs) > 1 {
88-
args = append(args, strings.Join(cmdArgs[1:], " "))
109+
var escapedArgs strings.Builder
110+
111+
for i, arg := range cmdArgs[1:] {
112+
escapedArgs.WriteString(shellEscape(arg))
113+
114+
// Add spaces between arguments, except the last argument
115+
if i < len(cmdArgs)-2 {
116+
escapedArgs.WriteByte(' ')
117+
}
118+
}
119+
120+
command = "/bin/sh"
121+
args = []string{"-c"}
122+
args = append(args, escapedArgs.String())
89123
} else {
90124
// Bring user into shell if no command is specified.
91125
shell := "$(getent passwd $(id -u) | cut -d: -f 7)"
92-
name := "-$(basename " + shell + ")"
93-
args = append(args, fmt.Sprintf("exec -a %q %q", name, shell))
126+
127+
// force bash for the '-l' flag to the exec built-in
128+
command = "/bin/bash"
129+
args = []string{"-c"}
130+
args = append(args, fmt.Sprintf("exec -l %q", shell))
94131
}
95132

96133
envName := cmdArgs[0]

internal/cmd/shell_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package cmd
2+
3+
import "testing"
4+
5+
func TestShellEscape(t *testing.T) {
6+
t.Parallel()
7+
8+
tests := []struct {
9+
Name string
10+
Input string
11+
Escaped string
12+
}{
13+
{
14+
Name: "single space",
15+
Input: "hello world",
16+
Escaped: `hello\ world`,
17+
},
18+
{
19+
Name: "multiple spaces",
20+
Input: "test message hello world",
21+
Escaped: `test\ message\ hello\ \ world`,
22+
},
23+
{
24+
Name: "mixed quotes",
25+
Input: `"''"`,
26+
Escaped: `\"\'\'\"`,
27+
},
28+
{
29+
Name: "mixed escaped quotes",
30+
Input: `"'\"\"'"`,
31+
Escaped: `\"\'\\\"\\\"\'\"`,
32+
},
33+
}
34+
35+
for _, test := range tests {
36+
if e, a := test.Escaped, shellEscape(test.Input); e != a {
37+
t.Fatalf("test %q failed; expected: %q, got %q (input: %q)",
38+
test.Name, test.Escaped, a, test.Input)
39+
}
40+
}
41+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy