Skip to content

Commit 0dbb846

Browse files
committed
fix and unify shell escaping for user/group/directory
1 parent baff92c commit 0dbb846

File tree

2 files changed

+33
-17
lines changed

2 files changed

+33
-17
lines changed

lib/sshkit/command.rb

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
require 'digest/sha1'
22
require 'securerandom'
3+
require 'shellwords'
34

45
# @author Lee Hambley
56
module SSHKit
@@ -145,7 +146,7 @@ def should_map?
145146

146147
def within(&_block)
147148
return yield unless options[:in]
148-
sprintf("cd #{options[:in]} && %s", yield)
149+
sprintf("cd #{options[:in].shellescape} && %s", yield)
149150
end
150151

151152
def environment_hash
@@ -167,7 +168,7 @@ def with(&_block)
167168

168169
def user(&_block)
169170
return yield unless options[:user]
170-
"sudo -u #{options[:user]} #{environment_string + " " unless environment_string.empty?}-- sh -c '#{yield}'"
171+
"sudo -u #{options[:user].to_s.shellescape} #{environment_string + " " unless environment_string.empty?}-- sh -c #{yield.shellescape}"
171172
end
172173

173174
def in_background(&_block)
@@ -182,7 +183,7 @@ def umask(&_block)
182183

183184
def group(&_block)
184185
return yield unless options[:group]
185-
%Q(sg #{options[:group]} -c "#{yield}")
186+
"sg #{options[:group].to_s.shellescape} -c #{yield.shellescape}"
186187
# We could also use the so-called heredoc format perhaps:
187188
#"newgrp #{options[:group]} <<EOC \\\"%s\\\" EOC" % %Q{#{yield}}
188189
end

test/unit/test_command.rb

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -33,60 +33,65 @@ def test_leading_and_trailing_space_is_stripped
3333
def test_including_the_env
3434
SSHKit.config = nil
3535
c = Command.new(:rails, 'server', env: {rails_env: :production})
36-
assert_equal %{( export RAILS_ENV="production" ; /usr/bin/env rails server )}, c.to_command
36+
assert_equal %{( export RAILS_ENV=\"production\" ; /usr/bin/env rails server )}, c.to_command
3737
end
3838

3939
def test_including_the_env_with_multiple_keys
4040
SSHKit.config = nil
4141
c = Command.new(:rails, 'server', env: {rails_env: :production, foo: 'bar'})
42-
assert_equal %{( export RAILS_ENV="production" FOO="bar" ; /usr/bin/env rails server )}, c.to_command
42+
assert_equal %{( export RAILS_ENV=\"production\" FOO=\"bar\" ; /usr/bin/env rails server )}, c.to_command
4343
end
4444

4545
def test_including_the_env_with_string_keys
4646
SSHKit.config = nil
4747
c = Command.new(:rails, 'server', env: {'FACTER_env' => :production, foo: 'bar'})
48-
assert_equal %{( export FACTER_env="production" FOO="bar" ; /usr/bin/env rails server )}, c.to_command
48+
assert_equal %{( export FACTER_env=\"production\" FOO=\"bar\" ; /usr/bin/env rails server )}, c.to_command
4949
end
5050

5151
def test_double_quotes_are_escaped_in_env
5252
SSHKit.config = nil
5353
c = Command.new(:rails, 'server', env: {foo: 'asdf"hjkl'})
54-
assert_equal %{( export FOO="asdf\\\"hjkl" ; /usr/bin/env rails server )}, c.to_command
54+
assert_equal %{( export FOO=\"asdf\\\"hjkl\" ; /usr/bin/env rails server )}, c.to_command
5555
end
5656

5757
def test_percentage_symbol_handled_in_env
5858
SSHKit.config = nil
5959
c = Command.new(:rails, 'server', env: {foo: 'asdf%hjkl'}, user: "anotheruser")
60-
assert_equal %{( export FOO="asdf%hjkl" ; sudo -u anotheruser FOO=\"asdf%hjkl\" -- sh -c '/usr/bin/env rails server' )}, c.to_command
60+
assert_equal %{( export FOO=\"asdf%hjkl\" ; sudo -u anotheruser FOO=\"asdf%hjkl\" -- sh -c /usr/bin/env\\ rails\\ server )}, c.to_command
6161
end
6262

6363
def test_including_the_env_doesnt_addressively_escape
6464
SSHKit.config = nil
6565
c = Command.new(:rails, 'server', env: {path: '/example:$PATH'})
66-
assert_equal %{( export PATH="/example:$PATH" ; /usr/bin/env rails server )}, c.to_command
66+
assert_equal %{( export PATH=\"/example:$PATH\" ; /usr/bin/env rails server )}, c.to_command
6767
end
6868

6969
def test_global_env
7070
SSHKit.config = nil
7171
SSHKit.config.default_env = { default: 'env' }
7272
c = Command.new(:rails, 'server', env: {})
73-
assert_equal %{( export DEFAULT="env" ; /usr/bin/env rails server )}, c.to_command
73+
assert_equal %{( export DEFAULT=\"env\" ; /usr/bin/env rails server )}, c.to_command
7474
end
7575

7676
def test_default_env_is_overwritten_with_locally_defined
7777
SSHKit.config.default_env = { foo: 'bar', over: 'under' }
7878
c = Command.new(:rails, 'server', env: { over: 'write'})
79-
assert_equal %{( export FOO="bar" OVER="write" ; /usr/bin/env rails server )}, c.to_command
79+
assert_equal %{( export FOO=\"bar\" OVER=\"write\" ; /usr/bin/env rails server )}, c.to_command
8080
end
8181

8282
def test_working_in_a_given_directory
8383
c = Command.new(:ls, '-l', in: "/opt/sites")
8484
assert_equal "cd /opt/sites && /usr/bin/env ls -l", c.to_command
8585
end
8686

87+
def test_working_in_a_given_weird_directory
88+
c = Command.new(:ls, '-l', in: "/opt/sites and stuff")
89+
assert_equal "cd /opt/sites\\ and\\ stuff && /usr/bin/env ls -l", c.to_command
90+
end
91+
8792
def test_working_in_a_given_directory_with_env
8893
c = Command.new(:ls, '-l', in: "/opt/sites", env: {a: :b})
89-
assert_equal %{cd /opt/sites && ( export A="b" ; /usr/bin/env ls -l )}, c.to_command
94+
assert_equal %{cd /opt/sites && ( export A=\"b\" ; /usr/bin/env ls -l )}, c.to_command
9095
end
9196

9297
def test_having_a_host_passed
@@ -97,17 +102,27 @@ def test_having_a_host_passed
97102

98103
def test_working_as_a_given_user
99104
c = Command.new(:whoami, user: :anotheruser)
100-
assert_equal "sudo -u anotheruser -- sh -c '/usr/bin/env whoami'", c.to_command
105+
assert_equal "sudo -u anotheruser -- sh -c /usr/bin/env\\ whoami", c.to_command
106+
end
107+
108+
def test_working_as_a_given_weird_user
109+
c = Command.new(:whoami, user: "mr space |")
110+
assert_equal "sudo -u mr\\ space\\ \\| -- sh -c /usr/bin/env\\ whoami", c.to_command
101111
end
102112

103113
def test_working_as_a_given_group
104114
c = Command.new(:whoami, group: :devvers)
105-
assert_equal 'sg devvers -c "/usr/bin/env whoami"', c.to_command
115+
assert_equal 'sg devvers -c /usr/bin/env\\ whoami', c.to_command
116+
end
117+
118+
def test_working_as_a_given_weird_group
119+
c = Command.new(:whoami, group: "space | group")
120+
assert_equal "sg space\\ \\|\\ group -c /usr/bin/env\\ whoami", c.to_command
106121
end
107122

108123
def test_working_as_a_given_user_and_group
109124
c = Command.new(:whoami, user: :anotheruser, group: :devvers)
110-
assert_equal %Q(sudo -u anotheruser -- sh -c 'sg devvers -c "/usr/bin/env whoami"'), c.to_command
125+
assert_equal %Q(sudo -u anotheruser -- sh -c sg\\ devvers\\ -c\\ /usr/bin/env\\\\\\ whoami), c.to_command
111126
end
112127

113128
def test_umask
@@ -125,13 +140,13 @@ def test_umask_with_working_directory
125140
def test_umask_with_working_directory_and_user
126141
SSHKit.config.umask = '007'
127142
c = Command.new(:touch, 'somefile', in: '/var', user: 'alice')
128-
assert_equal "cd /var && umask 007 && sudo -u alice -- sh -c '/usr/bin/env touch somefile'", c.to_command
143+
assert_equal "cd /var && umask 007 && sudo -u alice -- sh -c /usr/bin/env\\ touch\\ somefile", c.to_command
129144
end
130145

131146
def test_umask_with_env_and_working_directory_and_user
132147
SSHKit.config.umask = '007'
133148
c = Command.new(:touch, 'somefile', user: 'bob', env: {a: 'b'}, in: '/var')
134-
assert_equal %{cd /var && umask 007 && ( export A="b" ; sudo -u bob A="b" -- sh -c '/usr/bin/env touch somefile' )}, c.to_command
149+
assert_equal %{cd /var && umask 007 && ( export A=\"b\" ; sudo -u bob A=\"b\" -- sh -c /usr/bin/env\\ touch\\ somefile )}, c.to_command
135150
end
136151

137152
def test_verbosity_defaults_to_logger_info

0 commit comments

Comments
 (0)