Skip to content

fix(install): Validate ULOOP_VERSION shape before use#1675

Merged
hatayama merged 3 commits into
v3-betafrom
fix/validate-uloop-version-env
Jul 10, 2026
Merged

fix(install): Validate ULOOP_VERSION shape before use#1675
hatayama merged 3 commits into
v3-betafrom
fix/validate-uloop-version-env

Conversation

@hatayama

@hatayama hatayama commented Jul 10, 2026

Copy link
Copy Markdown
Owner

Summary

  • install.sh and install.ps1 reject values of ULOOP_VERSION that could break out of the intended release path (e.g. ../../evil/repo/releases/download/v1), so a hostile ambient env cannot redirect the installer to a different release.

User Impact

  • Legitimate values keep working: latest, latest-beta, bare semver (3.0.0-beta.5), and prefixed release tags (dispatcher-v3.0.0-beta.5, uloop-project-runner-v3.0.0-beta.43, v3.0.0).
  • Malformed values (path traversal, query strings, whitespace, "not-a-version") fail with an actionable message before any network call runs.

Changes

  • scripts/install.sh: add validate_uloop_version POSIX-sh guard, called immediately after $VERSION is derived.
  • scripts/install.ps1: add Test-UloopVersionFormat with the same regex.
  • scripts/test-install-version-format.sh: exercise 15 accepted and rejected shapes against the extracted validate_uloop_version implementation.

Verification

  • sh scripts/test-install-version-format.sh — pass
  • sh scripts/test-install-release-filter.sh — pass (no regression on the existing release-selection flow)
  • sh scripts/test-install-archive-manifest.sh — pass (manifest verification still works)

Review in cubic

…nstall.ps1

Both installer scripts interpolate ULOOP_VERSION into the release download
URL without normalizing dot segments, so a value like
"../../evil/repo/releases/download/v1" would break out of the intended
release path. Fail-close on any value that is not one of the two well-known
channel selectors or a semver-shaped tag (bare, dispatcher-v-prefixed,
uloop-project-runner-v-prefixed, or v-prefixed) so downstream URL builders
and ULOOP_VERSION passthrough only ever see a value they were designed for.

Existing dispatcher self-update flows already emit values that pass this
check via NormalizeTargetVersion + DispatcherReleaseTag; the guard is
defensive coverage for the ambient-env attacker.
@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@hatayama, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 33 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 783dacba-163e-42dc-b5bf-ad817dae64c5

📥 Commits

Reviewing files that changed from the base of the PR and between 900663a and 7bfaaa9.

📒 Files selected for processing (1)
  • cli/dispatcher/shared-inputs-stamp.json
📝 Walkthrough

Walkthrough

The installer validates ULOOP_VERSION against supported channels and semver-like tags before building release URLs. Shell tests cover valid, malformed, traversal-like, and newline-containing inputs. The dispatcher shared-input hash is updated.

Changes

Version validation

Layer / File(s) Summary
Installer validation and input stamp
scripts/install.sh, cli/dispatcher/shared-inputs-stamp.json
ULOOP_VERSION is validated against supported channels and semver-like tags before URL construction, and the shared-input hash is updated.
Validation test matrix
scripts/test-install-version-format.sh
Tests accepted version formats and rejects malformed, traversal-like, URL-like, whitespace, empty, truncated, and newline-containing inputs.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: validating ULOOP_VERSION before use in the install flow.
Description check ✅ Passed The description matches the changeset and explains the validation, impact, and tests in a relevant way.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/validate-uloop-version-env

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

grep -Eq matches per line, so a value like `printf '../evil\n3.0.0'`
was passing the ERE on its second line while smuggling a newline-poisoned
first line into the URL builder downstream. Add a whole-string `case`
guard before the grep so any character outside the semver tag alphabet
(digits, letters, `.`, `+`, `-`) is rejected at the string level.

install.ps1 already anchors ^...$ at string level so this bypass never
existed there.

Add two regression cases with embedded newlines to
test-install-version-format.sh so the whole-string guard cannot regress.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
scripts/test-install-version-format.sh (1)

11-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider adding a guard for missing function extraction.

The extract_function awk pattern (^validate_uloop_version\(\)) assumes the function definition starts at column 0. If the definition is ever indented, extraction silently yields nothing, eval defines no function, and all expect_fail tests pass vacuously (command-not-found returns non-zero). The expect_pass tests would catch this, but only with a less helpful error message.

A one-line guard after the eval would make the failure mode explicit:

 eval "$(extract_function validate_uloop_version)"
+if ! command -v validate_uloop_version >/dev/null 2>&1; then
+  echo "FAIL: could not extract validate_uloop_version from install.sh" >&2
+  exit 1
+fi

The eval pattern itself is acceptable here since the source is a trusted file in the same repository. The static analysis hints (SC1007 for the CDPATH= idiom, SC2034 for LATEST_VERSION/LATEST_BETA_VERSION used by the eval'd function) are false positives in this context.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@scripts/test-install-version-format.sh` around lines 11 - 22, Add a guard
immediately after eval "$(extract_function validate_uloop_version)" to verify
that validate_uloop_version was successfully extracted and defined, failing with
a clear diagnostic if it is missing. Use the existing extract_function and
validate_uloop_version symbols; leave the trusted-file eval and noted
static-analysis suppressions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@scripts/test-install-version-format.sh`:
- Around line 11-22: Add a guard immediately after eval "$(extract_function
validate_uloop_version)" to verify that validate_uloop_version was successfully
extracted and defined, failing with a clear diagnostic if it is missing. Use the
existing extract_function and validate_uloop_version symbols; leave the
trusted-file eval and noted static-analysis suppressions unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ac742e5c-bfa4-4f3c-abcd-52b16dd1eab5

📥 Commits

Reviewing files that changed from the base of the PR and between 1a49534 and 900663a.

⛔ Files ignored due to path filters (1)
  • scripts/install.ps1 is excluded by none and included by none
📒 Files selected for processing (3)
  • cli/dispatcher/shared-inputs-stamp.json
  • scripts/install.sh
  • scripts/test-install-version-format.sh

…guard

commit 900663a re-edited scripts/install.sh (added the case-based
whole-string guard) without re-running scripts/stamp-release-inputs.sh,
leaving the stamp's sharedInputsHash stale for the current install.sh
contents. Restamp so the file matches the input set.
@hatayama
hatayama merged commit c09b683 into v3-beta Jul 10, 2026
5 checks passed
@hatayama
hatayama deleted the fix/validate-uloop-version-env branch July 10, 2026 07:41
@github-actions github-actions Bot mentioned this pull request Jul 11, 2026
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