Security advisory: dye template injection

April 3, 2026 at 12:15 AM (UTC)

Impact

Certain dye template expressions would result in execution of arbitrary code.

This issue was discovered and fixed by dye's author, and is not known to be exploited.

Patches

The issue has been patched in version 1.1.1.

Workarounds

Do not insert untrusted content into dye template expressions, or do not use dye template expressions.

Discussion

dye's template support, which debuted in version 1.1.0, is implemented by parsing the template, then taking the contents between double curly braces ("{{" and "}}") and re-executing them as if they were prefixed with the dye command.

For example, this:

dye write "{{red}}text{{reset}}"

effectively executes:

dye red
printf "%s" "text"
dye reset

dye commands output control characters by leveraging tput. Some tput capabilities used have a parameter, such as this to set the color to red:

tput setaf 1

Because of this, and to support automatic shutoff of color and emphases, internally, tput capabilities with a parameter are passed to dye_out as the capability name and a parameter, with a single space separating them. This allows them to occupy three distinct arguments:

dye_out "setaf 1" "sgr0" "text"

dye_out would then use eval to call tput with the two arguments it expected.

Unfortunately, this original implementation detail, combined with the template feature, opened the door for injection of commands in text if they appeared inside double curly braces.

Consider a file named "data {{red ;touch hacked}}", whose name has been read into a dye template string. Printing the file name is enough to trigger the embedded shell command as if your dye script evaluated it:

cd "$(mktemp -d)"
touch 'data {{red ;touch hacked}}'
dye p "{{bold}}Processing $(ls){{end bold}}"
test -f hacked && echo 'oh no'

The issue was resolved by removing all use of eval, and instead using a new routine that explicitly splits arguments, providing two new arguments to pass to tput when necessary.