I have a GitHub Action that adds a prefix to the PR's title if its branch name has a specific prefix. It throws an error (or otherwise misbehaves) if github.event.pull_request.title
contains unescaped characters like backticks, $
s, quotes (of the same kind that surround the substitution point), etc.
How can I safely substitute GitHub Action variables into my script if I don't control their content?
title="${{ github.event.pull_request.title }}"
if [[ ${{ github.head_ref }} = hotfix/* ]] && [[ $title != hotfix:* ]]; then
echo "Marking as hotfix."
gh pr edit ${{ env.PR_NUMBER }} --title "hotfix: ${title}"
fi
Ideally, use a templating system that performs shell-safe escaping itself; it's a common feature.
Unfortunately, the docs for GitHub actions don't indicate that it supports that (very common) feature -- but it does support JSON, so you can use that:
read_nullsep() {
local arg
for arg; do
IFS= read -r -d '' "$arg" || return
done
}
read_nullsep title head_ref pr_number < <(jq -j '(tostring | gsub("\u0000"; ""), "\u0000")' <<'EOF'
${{ toJSON(github.event.pull_request.title) }}
${{ toJSON(github.head_ref) }}
${{ toJSON(env.PR_NUMBER) }}
EOF
) || { echo "Error decoding metadata" >&2; exit 1; }
if [[ $head_ref = hotfix/* ]] && [[ $title != hotfix:* ]]; then
echo "Marking as hotfix."
gh pr edit "$pr_number" --title "hotfix: ${title}"
fi
Some implementation notes:
jq -j
is an equivalent to jq -r
that doesn't add an automatic newline between items, allowing the user to add a delimiter themselves instead; in this case, the delimiter we're adding is "\u0000"
, a NUL.gsub("\u0000", "")
tells jq to discard any NULs in the JSON content, such that the only NULs present in the output can be the ones we explicitly added as delimiters.read -r
tells read
to treat backslashes as literal data.read -d ''
tells read
to continue until it encounters end-of-file or a NUL literal."$PR_NUMBER"
directly in your shell script and avoid templating it out at all, assuming that it's passed through as an environment variable.