Skip to content

Commit abc8991

Browse files
committed
Enhance command replacer
- Use functions for easier reasoning about the code. - Compare commands to an allow list; easier to read and scalable. - Check for unsafe tokens. - Add default options so we don't have to repeat them in every COMMAND-OUTPUT. - Replace eval with ${EXECUTABLE_COMMAND[@]} to prevent injections. Example errors: `ls -l` ERROR: refusing to run arbitrary command: ls `phpcs && ls -l` ERROR: refusing unsafe token: &&
1 parent c923cac commit abc8991

File tree

1 file changed

+42
-13
lines changed

1 file changed

+42
-13
lines changed

build/wiki-command-replacer.sh

Lines changed: 42 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,45 @@ cd "$(dirname "$0")/.."
66

77
MARKER_START='{{COMMAND-OUTPUT "'
88
MARKER_END='"}}'
9+
ALLOWED_COMMANDS=("phpcs" "phpcbf")
10+
DEFAULT_OPTIONS=("--parallel=1" "--no-cache" "--no-colors" "--report-width=100" "--report=diff")
11+
12+
tokenize_command() {
13+
read -ra TOKENS <<< "$1"
14+
}
15+
16+
check_allowed_commands() {
17+
local cmd="${TOKENS[0]}"
18+
for allowed in "${ALLOWED_COMMANDS[@]}"; do
19+
[[ "$cmd" == "$allowed" ]] && return 0
20+
done
21+
22+
echo >&2 " ERROR: refusing to run arbitrary command: $cmd"
23+
exit 1
24+
}
25+
26+
validate_tokens() {
27+
for token in "${TOKENS[@]}"; do
28+
if [[ "$token" =~ [\;\|\&\$\<\>\`\\] ]]; then
29+
echo >&2 " ERROR: refusing unsafe token: $token"
30+
exit 1
31+
fi
32+
done
33+
}
34+
35+
add_default_options() {
36+
EXECUTABLE_COMMAND=("${TOKENS[0]}" "${DEFAULT_OPTIONS[@]}" "${TOKENS[@]:1}")
37+
}
38+
39+
execute_command() {
40+
tokenize_command "$1"
41+
check_allowed_commands
42+
validate_tokens
43+
add_default_options
44+
45+
echo >&2 " INFO: running: ${EXECUTABLE_COMMAND[@]}"
46+
${EXECUTABLE_COMMAND[@]}
47+
}
948

1049
if [[ -z "${CI:-}" ]]; then
1150
# The `_wiki` directory is created in a previous GitHub Action step.
@@ -19,20 +58,10 @@ grep -lrF "${MARKER_START}" _wiki | while read -r file_to_process; do
1958

2059
while IFS=$'\n' read -r line; do
2160
if [[ ${line} = ${MARKER_START}*${MARKER_END} ]]; then
22-
COMMAND="${line##"${MARKER_START}"}"
23-
COMMAND="${COMMAND%%"${MARKER_END}"}"
24-
25-
if [[ "${COMMAND}" != "phpcs "* ]] && [[ "${COMMAND}" != "phpcbf "* ]]; then
26-
echo >&2 " ERROR: refusing to run arbitrary command: ${COMMAND}"
27-
exit 1
28-
fi
29-
30-
#FIXME refuse to run commands with a semicolon / pipe / ampersand / sub-shell
61+
USER_COMMAND="${line##"${MARKER_START}"}"
62+
USER_COMMAND="${USER_COMMAND%%"${MARKER_END}"}"
3163

32-
echo >&2 " INFO: running: ${COMMAND}"
33-
(
34-
eval "${COMMAND}" </dev/null || true
35-
)
64+
execute_command "$USER_COMMAND" </dev/null || true
3665
else
3766
echo "${line}"
3867
fi

0 commit comments

Comments
 (0)