Properly escape ANSI color codes in shell scripts
What does this MR do?
Fixes ANSI color code handling in shell scripts by properly escaping commands in trace section output. Adds ANSI color formatting to GPG signing disabled messages to improve visibility.
Why was this MR needed?
Fixes gitlab-org/step-runner#414 where ANSI color codes in GPG signing disabled messages were being incorrectly parsed by pilot runners, causing job failures with errors like 31m******: command not found.
The root cause was that commands containing special characters (including ANSI escape sequences) were not being properly escaped for POSIX shell execution in trace section output.
Changes
- Trace Section Writer: Properly escape commands in trace section start/end output to handle special characters correctly and ANSI Color
- Fix related unit tests
What's the best way to test this MR?
config.toml
concurrent = 1
check_interval = 0
shutdown_timeout = 0
[session_server]
session_timeout = 1800
[[runners]]
name = "Local GitLab Runner for tests and debugging"
url = "https://gitlab.com"
id = 0
token = "glrt-REDACTED"
token_obtained_at = "0001-01-01T00:00:00Z"
token_expires_at = "0001-01-01T00:00:00Z"
executor = "docker"
[runners.feature_flags]
FF_USE_FASTZIP = true
FF_USE_NEW_BASH_EVAL_STRATEGY = true
FF_SCRIPT_SECTIONS = true
FF_EXPORT_HIGH_CARDINALITY_METRICS = true
FF_USE_ADAPTIVE_REQUEST_CONCURRENCY = false
FF_SCRIPT_TO_STEP_MIGRATION = true
FF_DEBUG_TRACE = true
[runners.cache]
MaxUploadedArchiveSize = 0
[runners.docker]
host = "unix:///Users/ratchade/.colima/default/docker.sock"
image = "alpine"
pull_policy = "if-not-present"
helper_image = "registry.gitlab.com/gitlab-org/gitlab-runner/gitlab-runner-helper-dev:alpine-latest-arm64-943917bd" #MR generated helper
privileged = false
disable_entrypoint_overwrite = true
oom_kill_disable = false
disable_cache = false
shm_size = 0
volumes = ["/cache"]gitlab-ci.yaml
hello:
variables:
TEST: "$CI_PIPELINE_IID-TEST"
FF_SCRIPT_TO_STEP_MIGRATION: "true"
image: alpine
before_script:
# - sleep 120s
- |
# checking GPG signing support
if [ -f "$GPG_KEY_PATH" ]; then
export GPG_KEY=$(cat ${GPG_KEY_PATH})
export GPG_PASSPHRASE=$(cat ${GPG_PASSPHRASE_PATH})
else
echo -e "\033[0;31m****** GPG signing disabled ******\033[0m"
fi
- |
# --- TestEscapeForAnsiC tests ---
# no escaping needed
echo -e "hello world"
printf '%b\n' "hello world"
# backslash
echo -e "path\to\file"
printf '%b\n' "path\to\file"
# single quote
echo -e "it's working"
printf '%b\n' "it's working"
# newline
echo -e "line1\nline2"
printf '%b\n' "line1\nline2"
# tab
echo -e "col1\tcol2"
printf '%b\n' "col1\tcol2"
# carriage return
echo -e "text\rmore"
printf '%b\n' "text\rmore"
# mixed special chars
echo -e "echo 'hello'\nworld\test"
printf '%b\n' "echo 'hello'\nworld\test"
# command with quotes
echo -e "echo \"hello\" 'world'"
printf '%b\n' "echo \"hello\" 'world'"
# ANSI escape sequence - ESC character
echo -e "\x1b[1;32mGreen\x1b[0m"
printf '%b\n' "\x1b[1;32mGreen\x1b[0m"
# terminal clear screen
echo -e "\x1b[2J\x1b[H"
printf '%b\n' "\x1b[2J\x1b[H"
# null byte
echo -e "text\x00more"
printf '%b\n' "text\x00more"
# DEL character
echo -e "text\x7fmore"
printf '%b\n' "text\x7fmore"
# non-ASCII characters (café - raw UTF-8 bytes)
echo -e "caf\xc3\xa9"
printf '%b\n' "caf\xc3\xa9"
# mixed control and printable
echo -e "Hello\x1b[31mRed\x1b[0mWorld"
printf '%b\n' "Hello\x1b[31mRed\x1b[0mWorld"
# literal backslash-033 red foreground
echo -e "\033[31m"
printf '%b\n' "\033[31m"
# literal backslash-033 reset
echo -e "\033[0m"
printf '%b\n' "\033[0m"
# literal backslash-033 bold blue
echo -e "\033[1;34m"
printf '%b\n' "\033[1;34m"
# literal backslash-033 green background
echo -e "\033[42m"
printf '%b\n' "\033[42m"
# literal backslash-x1b hex escape
echo -e "\x1bHello"
printf '%b\n' "\x1bHello"
# literal backslash-e bash escape
echo -e "\eHello"
printf '%b\n' "\eHello"
# literal backslash-u unicode escape
echo -e "\u001b"
printf '%b\n' "\u001b"
# literal backslash-X uppercase hex
echo -e "\X1b"
printf '%b\n' "\X1b"
# literal backslash-E uppercase bash escape
echo -e "\E"
printf '%b\n' "\E"
# consecutive backslashes four
echo -e "\\\\\\\\"
printf '%b\n' "\\\\\\\\"
# triple backslash
echo -e "\\\\\\"
printf '%b\n' "\\\\\\"
# trailing backslash
echo -e "test\\"
printf '%b\n' "test\\"
# backslash before non-printf char
echo -e "\z"
printf '%b\n' "\z"
# backslash before uppercase non-printf char
echo -e "\Z"
printf '%b\n' "\Z"
# multiple printf-sensitive sequences
echo -e "\n\t\r\a"
printf '%b\n' "\n\t\r\a"
# backslash before b and f and v
echo -e "\b\f\v"
printf '%b\n' "\b\f\v"
# real ESC char with dollar sign
echo -e "\x1b[31m\$HOME\x1b[0m"
printf '%b\n' "\x1b[31m\$HOME\x1b[0m"
# literal escape string with double quotes
echo -e "echo \"\033[31mred\033[0m\""
printf '%b\n' "echo \"\033[31mred\033[0m\""
# dollar and literal backslash-n
echo -e "\$VAR\ntext"
printf '%b\n' "\$VAR\ntext"
# all shell metacharacters together
echo -e "\$\"'\\"
printf '%b\n' "\$\"'\\"
# complex command with literal ANSI
echo -e "printf \"\033[31m%s\033[0m\n\" \"\$msg\""
printf '%b\n' "printf \"\033[31m%s\033[0m\n\" \"\$msg\""
# backtick is escaped for double-quoted context
echo -e "echo \`date\`"
printf '%b\n' "echo \`date\`"
# backticks inside single quotes
echo -e "echo '\`whoami\`'"
printf '%b\n' "echo '\`whoami\`'"
# real ESC bold and dim combined
echo -e "\x1b[1m\x1b[2mtext\x1b[0m"
printf '%b\n' "\x1b[1m\x1b[2mtext\x1b[0m"
# real ESC 256-color foreground
echo -e "\x1b[38;5;196mred\x1b[0m"
printf '%b\n' "\x1b[38;5;196mred\x1b[0m"
# real ESC RGB truecolor
echo -e "\x1b[38;2;255;0;0mred\x1b[0m"
printf '%b\n' "\x1b[38;2;255;0;0mred\x1b[0m"
# mixed real ESC and shell metacharacters
echo -e "\x1b[31m\$USER said \"hello\" at \`date\`\x1b[0m"
printf '%b\n' "\x1b[31m\$USER said \"hello\" at \`date\`\x1b[0m"
# literal octal sequences with surrounding text
echo -e "Status: \033[32mOK\033[0m - done"
printf '%b\n' "Status: \033[32mOK\033[0m - done"
# --- TestEscapeForAnsiC_SecurityFeatures tests ---
# prevent screen clear
echo -e "\x1b[2J"
printf '%b\n' "\x1b[2J"
# prevent cursor positioning
echo -e "\x1b[10;20H"
printf '%b\n' "\x1b[10;20H"
# prevent color manipulation
echo -e "\x1b[31m\x1b[42m"
printf '%b\n' "\x1b[31m\x1b[42m"
# prevent line deletion
echo -e "\x1b[2K"
printf '%b\n' "\x1b[2K"
# prevent scrolling region manipulation
echo -e "\x1b[1;24r"
printf '%b\n' "\x1b[1;24r"
# prevent title change via OSC
echo -e "\x1b]0;pwned\x07"
printf '%b\n' "\x1b]0;pwned\x07"
# prevent 256-color abuse
echo -e "\x1b[38;5;0m\x1b[48;5;0m"
printf '%b\n' "\x1b[38;5;0m\x1b[48;5;0m"
# prevent cursor save and restore
echo -e "\x1b[s\x1b[10;1H\x1b[2Kfake log\x1b[u"
printf '%b\n' "\x1b[s\x1b[10;1H\x1b[2Kfake log\x1b[u"
script:
- echo $TEST > artifact.txt
artifacts:
paths:
- artifact.txt
expire_in: 1 weekJob passes and artifact is uploaded.
What are the relevant issue numbers?
Closes step-runner#414 (closed)
Edited by Romuald Atchadé