Skip to content

Commit 731b792

Browse files
[SECURITY] Improved security while fixing regression (- WIP # 485 -)
Changes in file .github/actions/check-control/action.yml: * refactored how sha is normalized and sanitized Changes in file .github/actions/run-minimal-acceptance-tests/action.yml: * refactored how sha is normalized and sanitized Changes in file .github/actions/setup-py-reqs/action.yml: * refactored how sha is normalized and sanitized
1 parent bdff510 commit 731b792

File tree

3 files changed

+220
-59
lines changed

3 files changed

+220
-59
lines changed

.github/actions/check-control/action.yml

Lines changed: 84 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -110,19 +110,76 @@ runs:
110110
if: ${{ !cancelled() }}
111111
shell: bash
112112
run: |
113-
set -e # Exit immediately if any command fails
114-
sha_input='${{ inputs.sha }}'
115-
if [[ ! "$sha_input" =~ ^[0-9a-f]{40}$ ]]; then
116-
# check if value is non-sha valid
117-
output=$(git rev-parse --verify $sha_input)
118-
if [[ -n "$output" ]]; then
119-
printf "::debug:: %s\n" "Valid branch name or sha provided: ${output}" ;
113+
set -euo pipefail
114+
115+
raw_input='${{ inputs.sha }}'
116+
117+
# Reject NUL or newline immediately
118+
if printf '%s' "$raw_input" | grep -q $'[\\x00\\n\\r]'; then
119+
printf "::error title='Invalid':: %s\n" "Error: input contains disallowed control characters" >&2
120+
exit 1
121+
fi
122+
123+
# Strip one level of surrounding quotes and trim whitespace
124+
normalize() {
125+
local s="$1"
126+
s="${s#"${s%%[![:space:]]*}"}"
127+
s="${s%"${s##*[![:space:]]}"}"
128+
if [[ (${s:0:1} == "'" && ${s: -1} == "'") || (${s:0:1} == '"' && ${s: -1} == '"') ]]; then
129+
s="${s:1:-1}"
130+
fi
131+
printf '%s' "$s"
132+
}
133+
input="$(normalize "$raw_input")"
134+
135+
# Reject inputs starting with '-' (options)
136+
if [[ "${input:0:1}" == "-" ]]; then
137+
printf "::error title='Invalid':: %s\n" "Error: input may not start with '-'" >&2
138+
exit 1
139+
fi
140+
141+
# Reject any characters that enable ref modifiers or ambiguity
142+
if [[ "$input" =~ [[:space:]~^\{\}:@\*\?$$$$] ]]; then
143+
printf "::error title='Invalid':: %s\n" "Error: input contains disallowed characters" >&2
144+
exit 1
145+
fi
146+
147+
# If it's a 40-char SHA, accept directly
148+
if [[ "$input" =~ ^[0-9a-f]{40}$ ]]; then
149+
resolved_sha="$input"
150+
else
151+
# Try explicit namespaces in order: full refs, refs/heads/, refs/tags/, then bare branch/tag
152+
resolved_sha=""
153+
# 1) If input is a full ref path starting with refs/, resolve only that
154+
if [[ "$input" == refs/* ]]; then
155+
if git rev-parse --verify "$input" >/dev/null 2>&1; then
156+
resolved_sha="$(git rev-parse --verify "$input")"
157+
else
158+
printf "::error title='Invalid':: %s\n" "Error: ref not found: $input" >&2
159+
exit 1
160+
fi
120161
else
121-
printf "::error title='Invalid':: %s\n" "Error: Invalid SHA format" >&2 ;
122-
exit 1 ;
162+
# 2) Try refs/heads/<input>
163+
if git rev-parse --verify "refs/heads/$input" >/dev/null 2>&1; then
164+
resolved_sha="$(git rev-parse --verify "refs/heads/$input")"
165+
# 3) Try refs/tags/<input>
166+
elif git rev-parse --verify "refs/tags/$input" >/dev/null 2>&1; then
167+
resolved_sha="$(git rev-parse --verify "refs/tags/$input")"
168+
else
169+
printf "::error title='Invalid':: %s\n" "Error: no matching branch or tag found for: $input" >&2
170+
exit 1
171+
fi
123172
fi
124-
fi ;
125-
printf "sha=%s\n" $(git rev-parse --verify "$output") >> "$GITHUB_OUTPUT" ;
173+
fi
174+
175+
# Ensure final resolved value is a full 40-char commit SHA
176+
if [[ ! "$resolved_sha" =~ ^[0-9a-f]{40}$ ]]; then
177+
printf "::error title='Invalid':: %s\n" "Error: resolved value is not a full commit SHA" >&2
178+
exit 1
179+
fi
180+
181+
printf "sha=%s\n" "$resolved_sha" >> "$GITHUB_OUTPUT"
182+
126183
- id: output_uuid
127184
if: ${{ !cancelled() && (inputs.check-id == '') }}
128185
shell: bash
@@ -167,21 +224,19 @@ runs:
167224
run: |
168225
printf "%s\n" "::group::validate-name"
169226
name_input='${{ inputs.name }}'
170-
sanitized_input_name=$(echo "$name_input" | tr -cd '[:alnum:] _')
171-
printf "::debug:: %s\n" "Will use name $sanitized_input_name" ;
227+
printf "::debug:: %s\n" "Will use name $name_input" ;
172228
printf "%s\n" "::endgroup::"
173229
printf "%s\n" "::group::validate-title"
174230
title_input='${{ inputs.name }}'
175-
sanitized_input_title=$(echo "$title_input" | tr -cd '[:alnum:] _')
176-
printf "::debug:: %s\n" "Will use name $sanitized_input_title" ;
177-
sanitized_input_title_field=$(printf "%s%s" 'output[title]=' "$sanitized_input_title_field" ;)
231+
printf "::debug:: %s\n" "Will use name $title_input" ;
232+
sanitized_input_title_field=$(printf "%s%s" 'output[title]=' "$title_input" ;)
178233
printf "%s\n" "::endgroup::"
179234
printf "%s\n" "::group::create-new-check"
180235
# GitHub CLI api
181236
# https://cli.github.com/manual/gh_api
182237
CHECK_ID=$(gh api --method POST -H "Accept: application/vnd.github+json" \
183238
/repos/reactive-firewall-org/multicast/check-runs \
184-
-f "name=$sanitized_input_name" -f "head_sha=${{ steps.output_sha.outputs.sha }}" \
239+
-f "name=$name_input" -f "head_sha=${{ steps.output_sha.outputs.sha }}" \
185240
-f 'status=${{ inputs.status }}' -f "external_id=${{ steps.output_uuid.outputs.uuid }}" \
186241
-f "started_at=${{ steps.output_date.outputs.check_date }}Z" \
187242
-f "details_url=${{ steps.output_check_details_url.outputs.details_url }}" \
@@ -198,21 +253,19 @@ runs:
198253
run: |
199254
printf "%s\n" "::group::validate-name"
200255
name_input='${{ inputs.name }}'
201-
sanitized_input_name=$(echo "$name_input" | tr -cd '[:alnum:] _')
202-
printf "::debug:: %s\n" "Will use name $sanitized_input_name" ;
256+
printf "::debug:: %s\n" "Will use name $name_input" ;
203257
printf "%s\n" "::endgroup::"
204258
printf "%s\n" "::group::validate-title"
205259
title_input='${{ inputs.name }}'
206-
sanitized_input_title=$(echo "$title_input" | tr -cd '[:alnum:] _')
207-
printf "::debug:: %s\n" "Will use name $sanitized_input_title" ;
208-
sanitized_input_title_field=$(printf "%s%s" 'output[title]=' "$sanitized_input_title_field" ;)
260+
printf "::debug:: %s\n" "Will use name $title_input" ;
261+
sanitized_input_title_field=$(printf "%s%s" 'output[title]=' "$title_input" ;)
209262
printf "%s\n" "::endgroup::"
210263
printf "%s\n" "::group::update-new-check"
211264
# GitHub CLI api
212265
# https://cli.github.com/manual/gh_api
213266
CHECK_ID=$(gh api --method POST -H "Accept: application/vnd.github+json" \
214267
/repos/reactive-firewall-org/multicast/check-runs \
215-
-f "name=$sanitized_input_name" -f "head_sha=${{ steps.output_sha.outputs.sha }}" \
268+
-f "name=$input_name" -f "head_sha=${{ steps.output_sha.outputs.sha }}" \
216269
-f "status=in_progress" -f "external_id=${{ steps.output_uuid.outputs.uuid }}" \
217270
-f "started_at=${{ steps.output_date.outputs.check_date }}Z" \
218271
-f "details_url=${{ steps.output_check_details_url.outputs.details_url }}" \
@@ -253,21 +306,19 @@ runs:
253306
run: |
254307
printf "%s\n" "::group::validate-name"
255308
name_input='${{ inputs.name }}'
256-
sanitized_input_name=$(echo "$name_input" | tr -cd '[:alnum:] _')
257-
printf "::debug:: %s\n" "Will use name $sanitized_input_name" ;
309+
printf "::debug:: %s\n" "Will use name $name_input" ;
258310
printf "%s\n" "::endgroup::"
259311
printf "%s\n" "::group::validate-title"
260312
title_input='${{ inputs.name }}'
261-
sanitized_input_title=$(echo "$title_input" | tr -cd '[:alnum:] _')
262-
printf "::debug:: %s\n" "Will use name $sanitized_input_title" ;
263-
sanitized_input_title_field=$(printf "%s%s" 'output[title]=' "$sanitized_input_title_field" ;)
313+
printf "::debug:: %s\n" "Will use name $title_input" ;
314+
sanitized_input_title_field=$(printf "%s%s" 'output[title]=' "$title_input" ;)
264315
printf "%s\n" "::endgroup::"
265316
printf "%s\n" "::group::update-check"
266317
# GitHub CLI api
267318
# https://cli.github.com/manual/gh_api
268319
gh api --method PATCH -H "Accept: application/vnd.github+json" \
269320
/repos/reactive-firewall-org/multicast/check-runs/${{ steps.output_check_id.outputs.check_id }} \
270-
-f "name=$sanitized_input_name" -f "head_sha=${{ steps.output_sha.outputs.sha }}" \
321+
-f "name=$input_name" -f "head_sha=${{ steps.output_sha.outputs.sha }}" \
271322
-f "status=${{ inputs.status }}" \
272323
-f "details_url=${{ steps.output_check_details_url.outputs.details_url }}" \
273324
-f "$sanitized_input_title_field" \
@@ -282,21 +333,19 @@ runs:
282333
run: |
283334
printf "%s\n" "::group::validate-name"
284335
name_input='${{ inputs.name }}'
285-
sanitized_input_name=$(echo "$name_input" | tr -cd '[:alnum:] _')
286-
printf "::debug:: %s\n" "Will use name $sanitized_input_name" ;
336+
printf "::debug:: %s\n" "Will use name $name_input" ;
287337
printf "%s\n" "::endgroup::"
288338
printf "%s\n" "::group::validate-title"
289339
title_input='${{ inputs.name }}'
290-
sanitized_input_title=$(echo "$title_input" | tr -cd '[:alnum:] _')
291-
printf "::debug:: %s\n" "Will use name $sanitized_input_title" ;
292-
sanitized_input_title_field=$(printf "%s%s" 'output[title]=' "$sanitized_input_title_field" ;)
340+
printf "::debug:: %s\n" "Will use name $title_input" ;
341+
sanitized_input_title_field=$(printf "%s%s" 'output[title]=' "$title_input" ;)
293342
printf "%s\n" "::endgroup::"
294343
printf "%s\n" "::group::complete-check"
295344
# GitHub CLI api
296345
# https://cli.github.com/manual/gh_api
297346
gh api --method PATCH -H "Accept: application/vnd.github+json" \
298347
/repos/reactive-firewall-org/multicast/check-runs/${{ steps.output_check_id.outputs.check_id }} \
299-
-f "name=$sanitized_input_name" -f "head_sha=${{ steps.output_sha.outputs.sha }}" \
348+
-f "name=$input_name" -f "head_sha=${{ steps.output_sha.outputs.sha }}" \
300349
-f "status=completed" -f "conclusion=${{ inputs.conclusion }}" \
301350
-f "completed_at=${{ steps.output_date.outputs.check_date }}Z" \
302351
-f "details_url=${{ steps.output_check_details_url.outputs.details_url }}" \

.github/actions/run-minimal-acceptance-tests/action.yml

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -83,20 +83,76 @@ runs:
8383
if: ${{ !cancelled() }}
8484
shell: bash
8585
run: |
86-
set -e # Exit immediately if any command fails
87-
sha_input='${{ inputs.sha }}'
88-
if [[ ! "$sha_input" =~ ^[0-9a-f]{40}$ ]]; then
89-
# check if value is non-sha valid
90-
output=$(git rev-parse --verify $sha_input 2>/dev/null)
91-
if [[ -n "$output" ]]; then
92-
printf "::debug:: %s\n" "Valid branch name or sha provided: ${output}" ;
86+
set -euo pipefail
87+
88+
raw_input='${{ inputs.sha }}'
89+
90+
# Reject NUL or newline immediately
91+
if printf '%s' "$raw_input" | grep -q $'[\\x00\\n\\r]'; then
92+
printf "::error title='Invalid':: %s\n" "Error: input contains disallowed control characters" >&2
93+
exit 1
94+
fi
95+
96+
# Strip one level of surrounding quotes and trim whitespace
97+
normalize() {
98+
local s="$1"
99+
s="${s#"${s%%[![:space:]]*}"}"
100+
s="${s%"${s##*[![:space:]]}"}"
101+
if [[ (${s:0:1} == "'" && ${s: -1} == "'") || (${s:0:1} == '"' && ${s: -1} == '"') ]]; then
102+
s="${s:1:-1}"
103+
fi
104+
printf '%s' "$s"
105+
}
106+
input="$(normalize "$raw_input")"
107+
108+
# Reject inputs starting with '-' (options)
109+
if [[ "${input:0:1}" == "-" ]]; then
110+
printf "::error title='Invalid':: %s\n" "Error: input may not start with '-'" >&2
111+
exit 1
112+
fi
113+
114+
# Reject any characters that enable ref modifiers or ambiguity
115+
if [[ "$input" =~ [[:space:]~^\{\}:@\*\?$$$$] ]]; then
116+
printf "::error title='Invalid':: %s\n" "Error: input contains disallowed characters" >&2
117+
exit 1
118+
fi
119+
120+
# If it's a 40-char SHA, accept directly
121+
if [[ "$input" =~ ^[0-9a-f]{40}$ ]]; then
122+
resolved_sha="$input"
123+
else
124+
# Try explicit namespaces in order: full refs, refs/heads/, refs/tags/, then bare branch/tag
125+
resolved_sha=""
126+
# 1) If input is a full ref path starting with refs/, resolve only that
127+
if [[ "$input" == refs/* ]]; then
128+
if git rev-parse --verify "$input" >/dev/null 2>&1; then
129+
resolved_sha="$(git rev-parse --verify "$input")"
130+
else
131+
printf "::error title='Invalid':: %s\n" "Error: ref not found: $input" >&2
132+
exit 1
133+
fi
93134
else
94-
printf "::error title='Invalid':: %s\n" "Error: Invalid SHA format" >&2 ;
95-
exit 1 ;
135+
# 2) Try refs/heads/<input>
136+
if git rev-parse --verify "refs/heads/$input" >/dev/null 2>&1; then
137+
resolved_sha="$(git rev-parse --verify "refs/heads/$input")"
138+
# 3) Try refs/tags/<input>
139+
elif git rev-parse --verify "refs/tags/$input" >/dev/null 2>&1; then
140+
resolved_sha="$(git rev-parse --verify "refs/tags/$input")"
141+
else
142+
printf "::error title='Invalid':: %s\n" "Error: no matching branch or tag found for: $input" >&2
143+
exit 1
144+
fi
96145
fi
97-
fi ;
98-
printf "sha=%s\n" $(git rev-parse --verify "$output") >> "$GITHUB_OUTPUT" ;
99-
printf "BUILD_SHA=%s\n" $(git rev-parse --verify "$output") >> "$GITHUB_ENV" ;
146+
fi
147+
148+
# Ensure final resolved value is a full 40-char commit SHA
149+
if [[ ! "$resolved_sha" =~ ^[0-9a-f]{40}$ ]]; then
150+
printf "::error title='Invalid':: %s\n" "Error: resolved value is not a full commit SHA" >&2
151+
exit 1
152+
fi
153+
154+
printf "sha=%s\n" "$resolved_sha" >> "$GITHUB_OUTPUT"
155+
printf "BUILD_SHA=%s\n" "$resolved_sha" >> "$GITHUB_ENV" ;
100156
- name: "Setup Python"
101157
id: output_python
102158
if: ${{ !cancelled() }}

.github/actions/setup-py-reqs/action.yml

Lines changed: 68 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -68,20 +68,76 @@ runs:
6868
if: ${{ !cancelled() }}
6969
shell: bash
7070
run: |
71-
set -e # Exit immediately if any command fails
72-
sha_input='${{ inputs.sha }}'
73-
if [[ ! "$sha_input" =~ ^[0-9a-f]{40}$ ]]; then
74-
# check if value is non-sha valid
75-
output=$(git rev-parse --verify $sha_input 2>/dev/null)
76-
if [[ -n "$output" ]]; then
77-
printf "::debug:: %s\n" "Valid branch name or sha provided: ${output}" ;
71+
set -euo pipefail
72+
73+
raw_input='${{ inputs.sha }}'
74+
75+
# Reject NUL or newline immediately
76+
if printf '%s' "$raw_input" | grep -q $'[\\x00\\n\\r]'; then
77+
printf "::error title='Invalid':: %s\n" "Error: input contains disallowed control characters" >&2
78+
exit 1
79+
fi
80+
81+
# Strip one level of surrounding quotes and trim whitespace
82+
normalize() {
83+
local s="$1"
84+
s="${s#"${s%%[![:space:]]*}"}"
85+
s="${s%"${s##*[![:space:]]}"}"
86+
if [[ (${s:0:1} == "'" && ${s: -1} == "'") || (${s:0:1} == '"' && ${s: -1} == '"') ]]; then
87+
s="${s:1:-1}"
88+
fi
89+
printf '%s' "$s"
90+
}
91+
input="$(normalize "$raw_input")"
92+
93+
# Reject inputs starting with '-' (options)
94+
if [[ "${input:0:1}" == "-" ]]; then
95+
printf "::error title='Invalid':: %s\n" "Error: input may not start with '-'" >&2
96+
exit 1
97+
fi
98+
99+
# Reject any characters that enable ref modifiers or ambiguity
100+
if [[ "$input" =~ [[:space:]~^\{\}:@\*\?$$$$] ]]; then
101+
printf "::error title='Invalid':: %s\n" "Error: input contains disallowed characters" >&2
102+
exit 1
103+
fi
104+
105+
# If it's a 40-char SHA, accept directly
106+
if [[ "$input" =~ ^[0-9a-f]{40}$ ]]; then
107+
resolved_sha="$input"
108+
else
109+
# Try explicit namespaces in order: full refs, refs/heads/, refs/tags/, then bare branch/tag
110+
resolved_sha=""
111+
# 1) If input is a full ref path starting with refs/, resolve only that
112+
if [[ "$input" == refs/* ]]; then
113+
if git rev-parse --verify "$input" >/dev/null 2>&1; then
114+
resolved_sha="$(git rev-parse --verify "$input")"
115+
else
116+
printf "::error title='Invalid':: %s\n" "Error: ref not found: $input" >&2
117+
exit 1
118+
fi
78119
else
79-
printf "::error title='Invalid':: %s\n" "Error: Invalid SHA format" >&2 ;
80-
exit 1 ;
120+
# 2) Try refs/heads/<input>
121+
if git rev-parse --verify "refs/heads/$input" >/dev/null 2>&1; then
122+
resolved_sha="$(git rev-parse --verify "refs/heads/$input")"
123+
# 3) Try refs/tags/<input>
124+
elif git rev-parse --verify "refs/tags/$input" >/dev/null 2>&1; then
125+
resolved_sha="$(git rev-parse --verify "refs/tags/$input")"
126+
else
127+
printf "::error title='Invalid':: %s\n" "Error: no matching branch or tag found for: $input" >&2
128+
exit 1
129+
fi
81130
fi
82-
fi ;
83-
printf "sha=%s\n" $(git rev-parse --verify "$output") >> "$GITHUB_OUTPUT" ;
84-
printf "BUILD_SHA=%s\n" $(git rev-parse --verify "$output") >> "$GITHUB_ENV" ;
131+
fi
132+
133+
# Ensure final resolved value is a full 40-char commit SHA
134+
if [[ ! "$resolved_sha" =~ ^[0-9a-f]{40}$ ]]; then
135+
printf "::error title='Invalid':: %s\n" "Error: resolved value is not a full commit SHA" >&2
136+
exit 1
137+
fi
138+
139+
printf "sha=%s\n" "$resolved_sha" >> "$GITHUB_OUTPUT"
140+
printf "BUILD_SHA=%s\n" "$resolved_sha" >> "$GITHUB_ENV" ;
85141
- name: "Setup Python"
86142
id: output_python
87143
if: ${{ !cancelled() }}

0 commit comments

Comments
 (0)