fix: prevent command injection via docker options eval

Values supplied through docker options, `--ttl-seconds`, and `-e` flowed into a Bash `eval` during build, deploy, and run, letting a low-privileged user execute arbitrary commands on the host as the dokku user. These arguments are now tokenized and passed through to the container verbatim, without shell expansion. A one-time migration repairs stored labels whose backticks were saved with a stray backslash so Traefik-style rules stay valid on the next deploy.
This commit is contained in:
Jose Diaz-Gonzalez
2026-07-18 09:22:37 -04:00
parent 730fa85d03
commit 1a376c3622
25 changed files with 444 additions and 29 deletions

View File

@@ -105,6 +105,11 @@ teardown() {
}
@test "(builder-herouish:build .env)" {
run /bin/bash -c "dokku docker-options:add $TEST_APP build '--label=com.dokku.build-test=safe'"
echo "output: $output"
echo "status: $status"
assert_success
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP
echo "output: $output"
echo "status: $status"

View File

@@ -146,6 +146,11 @@ teardown() {
echo "status: $status"
assert_success
run /bin/bash -c "dokku docker-options:add $TEST_APP build '--label=com.dokku.build-test=safe'"
echo "output: $output"
echo "status: $status"
assert_success
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt
echo "output: $output"
echo "status: $status"

View File

@@ -178,6 +178,11 @@ teardown() {
echo "status: $status"
assert_success
run /bin/bash -c "dokku docker-options:add $TEST_APP build '--label=com.dokku.build-test=safe'"
echo "output: $output"
echo "status: $status"
assert_success
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt_cnb
echo "output: $output"
echo "status: $status"

View File

@@ -157,6 +157,11 @@ teardown() {
echo "status: $status"
assert_success
run /bin/bash -c "dokku docker-options:add $TEST_APP build '--label=com.dokku.build-test=safe'"
echo "output: $output"
echo "status: $status"
assert_success
run deploy_app python dokku@$DOKKU_DOMAIN:$TEST_APP add_requirements_txt
echo "output: $output"
echo "status: $status"

View File

@@ -91,3 +91,32 @@ teardown() {
echo "status: $status"
assert_output "false"
}
@test "(common) fn-docker-args-split does not expand injected commands" {
export DOKKU_TEST_PAYLOAD='--label x=$(id)'
run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-docker-args-split \"\$DOKKU_TEST_PAYLOAD\" | tr '\\0' '\\n'"
echo "output: $output"
echo "status: $status"
assert_success
assert_line 1 'x=$(id)'
[[ "$output" != *"uid="* ]] || flunk "id command output leaked - value was expanded"
export DOKKU_TEST_PAYLOAD='--label x=`id`'
run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-docker-args-split \"\$DOKKU_TEST_PAYLOAD\" | tr '\\0' '\\n'"
echo "output: $output"
echo "status: $status"
assert_success
assert_line 1 'x=`id`'
[[ "$output" != *"uid="* ]] || flunk "id command output leaked - value was expanded"
unset DOKKU_TEST_PAYLOAD
}
@test "(common) fn-docker-args-split preserves quoted tokens" {
run /bin/bash -c "source '$PLUGIN_CORE_AVAILABLE_PATH/common/functions'; fn-docker-args-split \"--label 'a b'\" | tr '\\0' '\\n'"
echo "output: $output"
echo "status: $status"
assert_success
assert_line 0 "--label"
assert_line 1 "a b"
}

View File

@@ -285,6 +285,25 @@ teardown() {
assert_output "/tmp:{} /var/tmp:{}"
}
@test "(docker-options) deploy does not expand command substitution in option values [buildpacks]" {
run /bin/bash -c "dokku docker-options:add $TEST_APP deploy '--label=com.dokku.test=\$(id)'"
echo "output: $output"
echo "status: $status"
assert_success
run deploy_app
echo "output: $output"
echo "status: $status"
assert_success
CID=$(<$DOKKU_ROOT/$TEST_APP/CONTAINER.web.1)
run /bin/bash -c "docker inspect $CID --format '{{ index .Config.Labels \"com.dokku.test\" }}'"
echo "output: $output"
echo "status: $status"
assert_output '$(id)'
[[ "$output" != *"uid="* ]] || flunk "id command output leaked - option value was expanded"
}
@test "(docker-options) deploy with options [dockerfile]" {
run /bin/bash -c "dokku docker-options:add $TEST_APP deploy \"-v /var/tmp\""
echo "output: $output"

View File

@@ -153,3 +153,46 @@ teardown() {
assert_success
assert_output "2"
}
@test "(run) docker-options and -e flags are not eval-injected" {
run deploy_app
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku docker-options:add $TEST_APP run '--label=com.dokku.test=safe'"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku run --ttl-seconds=60 -e FOO=bar $TEST_APP env | grep -E '^FOO=bar'"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku run -e 'X=\$(id)' $TEST_APP env"
echo "output: $output"
echo "status: $status"
assert_success
[[ "$output" == *'X=$(id)'* ]] || flunk "expected literal X=\$(id) in container env"
[[ "$output" != *"uid="* ]] || flunk "id command output leaked - env value was expanded"
}
@test "(run) --ttl-seconds rejects non-numeric input; docker-options stores it verbatim" {
run /bin/bash -c "dokku run --ttl-seconds '\$(id)' $TEST_APP echo hi"
echo "output: $output"
echo "status: $status"
assert_failure
[[ "$output" != *"uid="* ]] || flunk "id command output leaked - ttl value was expanded"
run /bin/bash -c "dokku docker-options:add $TEST_APP run '--label=x=\$(id)'"
echo "output: $output"
echo "status: $status"
assert_success
run /bin/bash -c "dokku docker-options:report $TEST_APP --docker-options-run"
echo "output: $output"
echo "status: $status"
[[ "$output" == *'x=$(id)'* ]] || flunk "expected literal x=\$(id) stored in docker options"
[[ "$output" != *"uid="* ]] || flunk "id command output leaked - docker option was expanded"
}