Skip to content

Commit 80a991d

Browse files
committed
Fix safe interpolation detection
Any variable interpolation is currently reported as unsafe. The stdlib feature a `stdlib::shell_escape()` function (formerly `shell_escape()`) that escape the string passed as parameter. In such a case, an unsafe interpolation should not be detected. Add detection of such escaped string and do not report an error in this case. `stdlib::shell_escape()` must be the last function called in the interpolation for it to not be reported as unsafe.
1 parent 7b7b42d commit 80a991d

File tree

2 files changed

+88
-6
lines changed

2 files changed

+88
-6
lines changed

lib/puppet-lint/plugins/check_unsafe_interpolations.rb

+50-6
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ def check
2222
end
2323
end
2424

25-
# Iterate over the tokens in a title and raise a warning if an interpolated variable is found
25+
# Iterate over the tokens in a title and raise a warning if an unsafe interpolated variable is found
2626
def check_unsafe_title(title)
2727
title.each do |token|
28-
notify_warning(token.next_code_token) if interpolated?(token)
28+
notify_warning(token.next_code_token) if unsafe_interpolated?(token)
2929
end
3030
end
3131

@@ -60,7 +60,7 @@ def check_command(token)
6060
# Iterate through tokens in command
6161
while current_token.type != :NEWLINE
6262
# Check if token is a varibale and if it is parameterised
63-
rule_violations.append(current_token.next_code_token) if interpolated?(current_token)
63+
rule_violations.append(current_token.next_code_token) if unsafe_interpolated?(current_token)
6464
current_token = current_token.next_token
6565
end
6666

@@ -78,7 +78,7 @@ def parameterised?(token)
7878
end
7979

8080
# This function is a replacement for puppet_lint's title_tokens function which assumes titles have single quotes
81-
# This function adds a check for titles in double quotes where there could be interpolated variables
81+
# This function adds a check for titles in double quotes where there could be unsafe interpolated variables
8282
def get_exec_titles
8383
result = []
8484
tokens.each_index do |token_idx|
@@ -114,8 +114,52 @@ def get_exec_titles
114114
result
115115
end
116116

117-
def interpolated?(token)
118-
INTERPOLATED_STRINGS.include?(token.type)
117+
def find_closing_brack(token)
118+
while (token = token.next_code_token)
119+
case token.type
120+
when :RBRACK
121+
return token
122+
when :LBRACK
123+
token = find_closing_brack(token)
124+
end
125+
end
126+
raise 'not reached'
127+
end
128+
129+
def find_closing_paren(token)
130+
while (token = token.next_code_token)
131+
case token.type
132+
when :RPAREN
133+
return token
134+
when :LPAREN
135+
token = find_closing_paren(token)
136+
end
137+
end
138+
raise 'not reached'
139+
end
140+
141+
def unsafe_interpolated?(token)
142+
# XXX: Since stdlib 9.0.0 'shell_escape' is deprecated in favor or 'stdlib::shell_escape'
143+
# When the shell_escape function is removed from stdlib, we want to remove it bellow.
144+
return false unless INTERPOLATED_STRINGS.include?(token.type)
145+
146+
token = token.next_code_token
147+
148+
if token.type == :FUNCTION_NAME && ['shell_escape', 'stdlib::shell_escape'].include?(token.value)
149+
token = token.next_code_token
150+
token = find_closing_paren(token).next_code_token if token.type == :LPAREN
151+
return ![:DQPOST, :DQMID].include?(token.type)
152+
elsif token.type == :VARIABLE
153+
token = token.next_code_token
154+
token = find_closing_brack(token).next_code_token if token.type == :LBRACK
155+
if token.type == :DOT && [:NAME, :FUNCTION_NAME].include?(token.next_code_token.type) && ['shell_escape', 'stdlib::shell_escape'].include?(token.next_code_token.value)
156+
token = token.next_code_token.next_code_token
157+
token = find_closing_paren(token).next_code_token if token.type == :LPAREN
158+
return ![:DQPOST, :DQMID].include?(token.type)
159+
end
160+
end
161+
162+
true
119163
end
120164

121165
# Finds the index offset of the next instance of `value` in `tokens_slice` from the original index

spec/puppet-lint/plugins/check_unsafe_interpolations_spec.rb

+38
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,44 @@ class foo {
9191
end
9292
end
9393

94+
context 'exec with shell escaped string in command' do
95+
let(:code) do
96+
<<-PUPPET
97+
class foo {
98+
99+
exec { 'bar':
100+
command => "echo ${foo.stdlib::shell_escape} ${bar.stdlib::shell_escape()}",
101+
onlyif => "${bar[1].stdlib::shell_escape}",
102+
unless => "[ -x ${stdlib::shell_escape(reticulate($baz))} ]",
103+
}
104+
}
105+
PUPPET
106+
end
107+
108+
it 'detects zero problems' do
109+
expect(problems).to have(0).problems
110+
end
111+
end
112+
113+
context 'exec with post-processed shell escaped string in command' do
114+
let(:code) do
115+
<<-PUPPET
116+
class foo {
117+
118+
exec { 'bar':
119+
command => "echo ${reticulate(foo.stdlib::shell_escape)} ${bar.stdlib::shell_escape().reticulate}",
120+
onlyif => "${bar[1].stdlib::shell_escape.reticulate()}",
121+
unless => "[ -x ${reticulate(stdlib::shell_escape($baz))} ]",
122+
}
123+
}
124+
PUPPET
125+
end
126+
127+
it 'detects zero problems' do
128+
expect(problems).to have(4).problems
129+
end
130+
end
131+
94132
context 'exec that has an array of args in command' do
95133
let(:code) do
96134
<<-PUPPET

0 commit comments

Comments
 (0)