Skip to content

fix(renderer): make copied runner install commands fail closed - #39

Merged
namikmesic merged 2 commits into
mainfrom
fm/make-the-add-runner-copied-commands-safe-d7
Sep 30, 2026
Merged

namikmesic merged 2 commits into
mainfrom
fm/make-the-add-runner-copied-commands-safe-d7

Conversation

@namikmesic

Copy link
Copy Markdown
Owner

Intent

Make the Add runner panel's copied installation commands safe. Today the Download block interpolates the server URL, file name, checksum and token without shell quoting, and a failed checksum does not stop the following tar, so a hostile value runs as shell and a bad download still extracts. Generate a self-contained fail-closed block instead: every dynamic value quoted for a POSIX shell and rejected if it carries control characters, prerequisites checked first, the download and checksum done in a private staging directory next to the destination with the archive extracted there, and the finished directory renamed into a previously absent puck-runner directory only after every step succeeds, with staging removed on any failure. Use sha256sum on Linux and shasum -a 256 on macOS. The Configure and Run blocks each enter the completed puck-runner directory in a guarded subshell and verify the installation is complete before running, instead of assuming the earlier block changed the caller's directory. This works with today's development packages and claims no new authenticity.

What Changed

  • The Add runner Download block is a self-contained script that shell-quotes every dynamic value, rejects control characters and unsafe package names or URLs, and checks that curl, tar, and sha256sum (Linux) or shasum (macOS) are present before downloading.
  • The archive is downloaded, checked with sha256sum or shasum -a 256, and extracted in a private staging directory next to the destination. It is renamed to puck-runner only after the shipped files and version match, and staging is removed if any step fails.
  • Configure and Run each verify that ./puck-runner has the expected version and an executable config.sh, then enter that directory in a subshell. When a package or registration value is not safe to paste, the panel shows an error and no commands.

Risk Assessment

✅ Low: The copied install commands are a bounded fail-closed rewrite: dynamic values are POSIX-quoted and control-character rejected, checksum failure stops before extract, and staging is published to an absent puck-runner only after the checks succeed.

Testing

I generated the Add runner copy-paste blocks with the panel's command generator and ran them in /bin/sh against a local server and a tarball that uses the development package's config.sh, run.sh, and svc.sh. On macOS the Download block published a private puck-runner directory only after shasum -a 256 checked out, left the caller's directory unchanged, and removed staging when the checksum, the download, the redirect, or the package was bad. Configure and Run entered that directory in a subshell and refused an incomplete install; quotes and command substitutions stayed literal. The Linux block does call sha256sum, but this Mac's sha256sum is Darwin 1.0 and rejects the GNU --status flag, so a successful Linux checksum was not observed. The Add runner panel was not opened, because it requires a signed-in Puck session, so there is no screenshot of the settings UI.

  • Live validation: ✅ go - 11 of 13 scenarios driven live against the product
Scenario Result Live Evidence
Pasting the macOS Download block installs a development-layout package into a new private puck-runner directory and leaves the caller where they were ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
Quotes, command substitutions, and semicolons in the URL, file name, version, and token stay literal data and do not run ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/hostile-node-log.txt
A checksum that does not match the download does not extract or publish, and staging is removed ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
A download the server refuses does not publish a directory or leave staging behind ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
An HTTP redirect is not followed and nothing is published ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
An existing puck-runner directory, file, or symlink is left untouched and nothing is downloaded ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
A missing prerequisite is named and nothing is downloaded or staged ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
A package missing a required file, carrying the wrong version, or shipping a non-executable config.sh is not published ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
Configure and both Run choices enter the finished puck-runner directory without moving the caller, and they refuse an incomplete installation ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/configure-and-run.log
An untrusted HTTPS download is not extracted or published ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
Control characters, path-like file names, and a download off this server's origin produce no install script ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
The Linux Download block checks a good package with sha256sum and then publishes it ⏸️ untested no The prior payload marked this scenario untested while live=true. The recorded run invoked Darwin sha256sum at /sbin/sha256sum, which rejects GNU --status, and did not observe a correct Linux checksum…
Opening Add runner shows the Download, Configure, and Run blocks a person can copy ⏸️ untested no The Add runner panel calls runnerRegistrationToken only after a Puck session exists, and this environment has no signed-in session. Sign in through Settings → Providers → GitHub against a development…
Evidence: Live shell transcript of the copied install blocks

=== Pasting the macOS Download block installs today's package layout into a new puck-runner directory and leaves the caller where they were
PASS published puck-runner mode 700, caller stayed in /private/var/folders/bf/_d_b7c6n5xv2xk9wdhqpz_bm0000gn/T/puck-runner-work-P8Yw6r

=== Quotes, command substitutions, and background operators in the URL, file name, version, and token stay literal data
PASS no injected file; config.sh received the hostile URL and token as single arguments inside puck-runner

=== A checksum that does not match the download does not extract or publish, and staging is removed
PASS exit 1, nothing published, download did happen

=== A download the server refuses does not publish a directory or leave staging behind
PASS exit 22; curl stderr: curl: (22) The requested URL returned error: 404

=== An HTTP redirect is not followed, and nothing is published
PASS exit 1, one response, nothing published

=== An existing puck-runner directory, file, or symlink is left untouched and nothing is downloaded
PASS refused directory, file, symlink before curl

=== A missing prerequisite is named and nothing is downloaded or staged
PASS refused curl, shasum, tar, mktemp, mv, rm, cat

=== A package missing a required file, carrying the wrong version, or shipping a non-executable config.sh is not published
PASS refused config.sh, run.sh, svc.sh, VERSION, bin/node, bin/puck-runner.cjs, wrong-version, not-executable

=== Configure and both Run choices enter the finished puck-runner directory without moving the caller, and they refuse an incomplete installation
PASS macOS configure, run.sh, and svc.sh ran inside puck-runner; incomplete installs were refused; linux service block reached sudo (sudo: a password is required)

=== An untrusted HTTPS download is not extracted or published
PASS curl rejected the certificate and the block published nothing

=== Control characters, path-like file names, and a download off this server's origin produce no install script
PASS rejected control characters in file, url, sha256, version, serverUrl, token; rejected paths, foreign origins, and a non-checksum

=== The Linux Download block calls sha256sum and does not publish when that check cannot succeed
UNTESTED UNTESTED host sha256sum is Darwin and rejects --status (usage: sha256sum [-bctwz] [files ...]). The Linux block invoked it and published nothing, but a correct GNU sha256sum check was not observed.
Evidence: macOS Download block that was executed
sh -eu <<'PUCK_RUNNER_INSTALL'
fail() { printf '%s\n' "$1" >&2; exit 1; }
# Check prerequisites and refuse an existing destination.
for tool in curl shasum tar mktemp mv rm cat; do
  command -v "$tool" >/dev/null 2>&1 || fail "Missing prerequisite: $tool"
done
[ ! -e ./puck-runner ] && [ ! -L ./puck-runner ] || fail 'puck-runner already exists.'
# Create private staging beside the destination; clean up on failure.
parent=$(pwd -P)
stage=''
trap 'status=$?; trap - 0; [ -z "$stage" ] || rm -rf "$stage"; exit "$status"' 0
trap 'exit 1' HUP INT TERM
umask 077
stage=$(mktemp -d "$parent/.puck-runner.XXXXXXXXXX")
cd "$stage"
file='puck-runner-macos-arm64-0.1.0.tar.gz'
# Download to a partial file with bounded timeouts.
curl --fail --max-redirs 0 --globoff --connect-timeout 30 --max-time 900 --output "./$file.partial" -- 'http://127.0.0.1:54747/runner/0.1.0/puck-runner-macos-arm64-0.1.0.tar.gz'
# Verify the checksum before renaming or extracting.
printf '%s  %s\n' '9c2471905559f46ee60fd5a149a0fb8f164b79cf4fd9faa5309c0f26c4103339' "./$file.partial" | shasum -a 256 -c --status
mv "./$file.partial" "./$file"
# Extract in staging and require the shipped files and version.
tar -xzf "./$file"
for required in config.sh run.sh svc.sh VERSION bin/node bin/puck-runner.cjs; do
  [ -f "$required" ] || fail "Runner package is missing $required."
done
for executable in config.sh run.sh svc.sh bin/node; do
  [ -x "$executable" ] || fail "Runner package cannot execute $executable."
done
[ "$(cat VERSION)" = '0.1.0' ] || fail 'Runner package version does not match.'
# Publish only after every check succeeds; never replace a destination.
cd "$parent"
[ ! -e ./puck-runner ] && [ ! -L ./puck-runner ] || fail 'puck-runner already exists.'
mv -n "$stage" ./puck-runner
[ ! -d "$stage" ] || fail 'Could not publish puck-runner.'
if [ -d "./puck-runner/${stage##*/}" ]; then
  stage="$parent/puck-runner/${stage##*/}"
  fail 'puck-runner appeared during publication.'
fi
stage=''
PUCK_RUNNER_INSTALL
Evidence: config.sh received the hostile URL and token as single arguments inside puck-runner

cwd=.../puck-runner arg=.../bin/puck-runner.cjs arg=config arg=--url arg=http://127.0.0.1:54703/a&#39;;touch&#10;arg=--token&#10;arg=PRT_&#39; "; touch .../INJECTED-ce741ceb; # PUCK_RUNNER_INSTALL

cwd=/private/var/folders/bf/_d_b7c6n5xv2xk9wdhqpz_bm0000gn/T/puck-runner-work-WEKisI/puck-runner
arg=/private/var/folders/bf/_d_b7c6n5xv2xk9wdhqpz_bm0000gn/T/puck-runner-work-WEKisI/puck-runner/bin/puck-runner.cjs
arg=config
arg=--url
arg=http://127.0.0.1:54747/a';touch
arg=--token
arg=PRT_' "; touch /var/folders/bf/_d_b7c6n5xv2xk9wdhqpz_bm0000gn/T/INJECTED-3abf592a; # PUCK_RUNNER_INSTALL
Evidence: Configure and Run, including the Linux sudo attempt

configure, run.sh, and svc.sh ran from puck-runner via the development shell scripts. linux sudo attempt stderr: sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper sudo: a password is required

configure, run.sh, and svc.sh ran from puck-runner via the development shell scripts.
linux sudo attempt stderr: sudo: a terminal is required to read the password; either use the -S option to read from standard input or configure an askpass helper
sudo: a password is required
Evidence: Linux block invoked Darwin sha256sum, which rejected --status, and published nothing
script exit 1
stderr:   % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed

  0     0    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
100  1636    0  1636    0     0  3307k      0 --:--:-- --:--:-- --:--:-- 1597k
usage: sha256sum [-bctwz] [files ...]

host sha256sum exit 1
host sha256sum stderr: usage: sha256sum [-bctwz] [files ...]

published: false

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 11 of 13 scenarios driven live against the product
Scenario Result Live Evidence
Pasting the macOS Download block installs a development-layout package into a new private puck-runner directory and leaves the caller where they were ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
Quotes, command substitutions, and semicolons in the URL, file name, version, and token stay literal data and do not run ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/hostile-node-log.txt
A checksum that does not match the download does not extract or publish, and staging is removed ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
A download the server refuses does not publish a directory or leave staging behind ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
An HTTP redirect is not followed and nothing is published ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
An existing puck-runner directory, file, or symlink is left untouched and nothing is downloaded ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
A missing prerequisite is named and nothing is downloaded or staged ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
A package missing a required file, carrying the wrong version, or shipping a non-executable config.sh is not published ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
Configure and both Run choices enter the finished puck-runner directory without moving the caller, and they refuse an incomplete installation ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/configure-and-run.log
An untrusted HTTPS download is not extracted or published ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
Control characters, path-like file names, and a download off this server's origin produce no install script ✅ pass live ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/runner-commands-transcript.txt
The Linux Download block checks a good package with sha256sum and then publishes it ⏸️ untested no The prior payload marked this scenario untested while live=true. The recorded run invoked Darwin sha256sum at /sbin/sha256sum, which rejects GNU --status, and did not observe a correct Linux checksum…
Opening Add runner shows the Download, Configure, and Run blocks a person can copy ⏸️ untested no The Add runner panel calls runnerRegistrationToken only after a Puck session exists, and this environment has no signed-in session. Sign in through Settings → Providers → GitHub against a development…
  • node --experimental-strip-types --disable-warning=MODULE_TYPELESS_PACKAGE_JSON ~/.no-mistakes/evidence/01M3SMZMNR4YN7MJEHQ82YK9H0/drive-runner-commands.mjs
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@namikmesic
namikmesic merged commit b027c9d into main Sep 30, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant