From 12677a928dd494baf2f6fb7679ff63b69943a77b Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 9 Aug 2026 00:28:57 -0500 Subject: [PATCH] fix(mosaic): prevent init prompt code execution --- .../framework/tools/_scripts/mosaic-init | 8 +++---- .../tools/_scripts/test-mosaic-init-rce.sh | 21 +++++++++++++++++++ packages/mosaic/package.json | 2 +- 3 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 packages/mosaic/framework/tools/_scripts/test-mosaic-init-rce.sh diff --git a/packages/mosaic/framework/tools/_scripts/mosaic-init b/packages/mosaic/framework/tools/_scripts/mosaic-init index 08830f9a..7dc20748 100755 --- a/packages/mosaic/framework/tools/_scripts/mosaic-init +++ b/packages/mosaic/framework/tools/_scripts/mosaic-init @@ -99,7 +99,7 @@ prompt_if_empty() { if [[ $NON_INTERACTIVE -eq 1 ]]; then if [[ -n "$default_value" ]]; then - eval "$var_name=\"$default_value\"" + printf -v "$var_name" %s "$default_value" return fi echo "[mosaic-init] ERROR: --$var_name is required in non-interactive mode" >&2 @@ -115,7 +115,7 @@ prompt_if_empty() { if [[ -z "$value" && -n "$default_value" ]]; then value="$default_value" fi - eval "$var_name=\"$value\"" + printf -v "$var_name" %s "$value" } prompt_multiline() { @@ -129,7 +129,7 @@ prompt_multiline() { fi if [[ $NON_INTERACTIVE -eq 1 ]]; then - eval "$var_name=\"$default_value\"" + printf -v "$var_name" %s "$default_value" return fi @@ -139,7 +139,7 @@ prompt_multiline() { if [[ -z "$value" ]]; then value="$default_value" fi - eval "$var_name=\"$value\"" + printf -v "$var_name" %s "$value" } # ── Existing file detection ──────────────────────────────────── diff --git a/packages/mosaic/framework/tools/_scripts/test-mosaic-init-rce.sh b/packages/mosaic/framework/tools/_scripts/test-mosaic-init-rce.sh new file mode 100644 index 00000000..ed40f3b3 --- /dev/null +++ b/packages/mosaic/framework/tools/_scripts/test-mosaic-init-rce.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Source only the prompt helpers; executing mosaic-init itself requires templates. +source <(head -n 144 "$(dirname "$0")/mosaic-init") + +rm -f /tmp/pwned +payload='literal "$(touch /tmp/pwned)"' +AGENT_NAME="" +prompt_if_empty AGENT_NAME "Agent name" <<<"$payload" + +[[ "$AGENT_NAME" == "$payload" ]] || { + echo "FAIL: prompt answer did not round-trip literally" >&2 + exit 1 +} +[[ ! -e /tmp/pwned ]] || { + echo "FAIL: prompt answer executed code" >&2 + rm -f /tmp/pwned + exit 1 +} +echo "mosaic-init RCE regression: PASS" diff --git a/packages/mosaic/package.json b/packages/mosaic/package.json index 5daa5d06..b94e865e 100644 --- a/packages/mosaic/package.json +++ b/packages/mosaic/package.json @@ -25,7 +25,7 @@ "lint": "eslint src", "typecheck": "tsc --noEmit", "test": "vitest run --passWithNoTests && pnpm run test:framework-shell", - "test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh" + "test:framework-shell": "bash framework/tools/quality/scripts/check-test-enumeration.sh && bash framework/tools/quality/scripts/test-check-test-enumeration.sh && python3 src/lease-broker/daemon_deadline_unittest.py && python3 src/lease-broker/normative_fragments_unittest.py && python3 src/lease-broker/receipt_challenge_unittest.py && python3 src/lease-broker/context_recovery_unittest.py && python3 src/lease-broker/recovery_runtime_unittest.py && python3 src/lease-broker/recovery_b1_adversarial_unittest.py && python3 src/lease-broker/framework_skill_portability_unittest.py && python3 src/mutator-gate/runtime_tools_unittest.py && python3 src/mutator-gate/runtime_launch_guard_unittest.py && python3 src/mutator-gate/version_coupling_unittest.py && python3 framework/tools/lease-broker/check-runtime-launches.py --root ../.. && bash framework/tools/codex/test-pr-diff-context.sh && bash framework/tools/qa/test-deps-preflight.sh && bash framework/tools/git/test-pr-review-gitea-comment.sh && bash framework/tools/git/test-pr-review-repo-host-override.sh && bash framework/tools/git/test-ci-queue-wait-branch-absent.sh && bash framework/tools/git/test-ci-queue-wait-tristate.sh && bash framework/tools/git/test-ci-queue-wait-github-checks.sh && bash framework/tools/git/test-pr-merge-queue-branch.sh && bash framework/tools/git/test-pr-merge-head-pin.sh && bash framework/tools/git/test-git-credential-mosaic.sh && bash framework/tools/git/test-gitea-token-identity.sh && bash framework/tools/woodpecker/test-terminal-green-contract.sh && bash framework/tools/_scripts/test-install-ordering-guard.sh && bash framework/tools/_scripts/test-mosaic-init-rce.sh && bash framework/tools/tmux/agent-send.test.sh && bash framework/tools/wake/test-wake-store-ack.sh && bash framework/tools/wake/test-wake-store-enqueue-race.sh && bash framework/tools/wake/test-wake-digest-hmac.sh && bash framework/tools/wake/test-wake-digest-quarantine.sh && bash framework/tools/wake/test-wake-detector.sh && bash framework/tools/wake/test-wake-fn-oracle.sh && bash framework/tools/wake/test-wake-reconcile.sh && bash framework/tools/wake/test-wake-beacon.sh && bash framework/tools/wake/test-wake-preimage.sh && bash framework/tools/wake/test-wake-install.sh" }, "dependencies": { "@mosaicstack/brain": "workspace:*",