Skip to content

fix(install): put ant on PATH from install.sh, as install.ps1 does (#194) - #223

Open
Nic-dorman wants to merge 7 commits into
mainfrom
fix/install-sh-path
Open

Nic-dorman wants to merge 7 commits into
mainfrom
fix/install-sh-path

Conversation

@Nic-dorman

@Nic-dorman Nic-dorman commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Fixes #194: after a successful install.sh run, ant --version failed with command-not-found in a new terminal. The install directory wasn't on PATH, and on macOS the default /usr/local/bin is root-owned without Homebrew, so the install failed without sudo. install.ps1 already adds its directory to the Windows user PATH. This brings install.sh to parity.

What changes

  • Default dir: ~/.local/bin on macOS as well as Linux, so the install never needs sudo.

  • PATH: when INSTALL_DIR isn't already on PATH, one guarded line (case ":${PATH}:" in *":$HOME/.local/bin:"*) ;; *) export PATH=… ;; esac) is appended to the same set of files rustup uses, and each changed file is listed:

    • ~/.profile (every POSIX login shell)
    • whichever of ~/.bash_profile / ~/.bash_login / ~/.bashrc already exist (~/.bashrc is created if bash is the login shell)
    • ~/.zshenv when zsh is installed (read by every zsh, interactive or not, so this covers every Mac)
    • conf.d/ant.fish when fish is installed

    Re-runs add nothing. An unwritable file is reported, not fatal.

  • Opt-out: --no-modify-path (curl … | bash -s -- --no-modify-path) or ANT_NO_MODIFY_PATH=1 gives the old print-the-line behaviour. install.ps1 honours the env var too.

  • Shadowing note: if another ant is already on PATH, the installer says so. It could be an earlier macOS install in /usr/local/bin, or an unrelated program: Apache Ant is also called ant, and both GitHub runner images ship it. macOS zsh login shells run path_helper and brew shellenv after ~/.zshenv, so /usr/local/bin and /opt/homebrew/bin end up ahead of ~/.local/bin. I deliberately didn't force ~/.local/bin to the front, since that would change precedence for every tool in it.

The issue proposed modifying PATH opt-in. I made it default-on with an opt-out instead, like install.ps1 and rustup in non-interactive mode. A curl | bash script can't prompt (stdin is the script), and an opt-in would leave the agent flow (install, then ant --version) broken by default.

Linear issue

Closes V2-1506

Risk tier

  • T0 — docs / tooling / CI / pure UX-output. Repo CI only.
  • T1 — client-only, no network-facing behavior change. CI + prod compat smoke.
  • T2 — node/client logic with behavioral surface, no protocol/format/economics change. Dev testnet + ADR.
  • T3 — protocol / storage format / payments / routing. T2 evidence + adversarial testing.

Compatibility

  • Wire: none
  • Storage: none
  • API: none. Installer only: the macOS default INSTALL_DIR moves from /usr/local/bin to ~/.local/bin, and install.sh now writes shell startup files unless --no-modify-path / ANT_NO_MODIFY_PATH=1 is set. No binary or ant-core change.

Semver impact

  • breaking
  • feature
  • fix

Test evidence

Review fix 65a1f87 (2026-10-28): a newline in the effective install path. A relative INSTALL_DIR is checked as "$PWD/$INSTALL_DIR" before anything is downloaded or created, so a newline in the current directory's name is rejected too. The conversion to an absolute path is also lossless now, using pwd && printf x and then cutting exactly x.

  • Dispatched install-test.yml on 65a1f87 with 0.3.12: run 40659873563. The three installer jobs pass. The Linux step rejects all three newline cases: one in INSTALL_DIR, a cwd ending in a newline with INSTALL_DIR=., and a cwd with an embedded newline with INSTALL_DIR=bin.
  • On dev2, in containers, for e1648ce → 65a1f87: Ubuntu 24.04 went from 7 of 52 checks failing to 52/52 passing, and bash:3.2 from 7 of 48 failing to 48/48 passing. The 7 are the new cwd cases, including the reported install into the sibling directory.
  • PR CI on 65a1f87: everything passes except Security Audit, which is the inherited RUSTSEC-2026-0387 (V2-1743).

Review fix e1648ce (2026-10-27): colon and newline. A directory with : in its name can't be a PATH entry. It is installed, but kept out of every startup file, and no export PATH= line is printed, with or without --no-modify-path. An INSTALL_DIR containing a newline is rejected before anything is downloaded, so $(… pwd) can no longer strip a trailing one and install somewhere else.

  • Dispatched install-test.yml on e1648ce with 0.3.12: run 40531438944. The three installer jobs pass, including the new Linux step: three colon names are refused, and the newline case is rejected. The npm (Linux) jobs fail as below.
  • On dev2, in containers, for c8feea5 → e1648ce:
    • Ubuntu 24.04: 13 of 45 checks failed before, 45/45 pass after.
    • bash:3.2: 13 of 41 failed before, 41/41 pass after.
    • The 13 are the new colon and newline cases, including the reported trailing-newline install into the stripped directory.
  • PR CI on e1648ce: everything passes except Security Audit, which is the inherited RUSTSEC-2026-0387 (V2-1743).

Review fixes 3211965 + c8feea5 (2026-10-27). A directory whose name contains $, a backtick, ", \ or a newline is no longer written into startup files. macOS login shells re-read PATH through path_helper unescaped, so escaping alone wasn't enough; 3211965's macOS run showed the substitutions running in zsh -lc. A relative INSTALL_DIR works with CDPATH set.

  • Dispatched install-test.yml on c8feea5 with 0.3.12: run 40514011527. The three installer jobs pass, including the new steps:
    • an odd name is refused, writes no startup file and runs nothing, including through macOS login shells;
    • a name with spaces, ', ( ), &, # and * is added and resolves in bash, dash, zsh and fish on Linux, and through path_helper on macOS;
    • CDPATH=. with a relative INSTALL_DIR.
      The two npm (Linux) jobs fail as before, for the reasons below; ci(install-test): make the npm jobs test npm's ant, not Apache Ant's #228 fixes both.
  • On dev2, in containers, for e4aaa87 → c8feea5:
    • Ubuntu 24.04 (bash 5.2 runs the installer; dash, zsh 5.9, fish 3.7): 13 of 32 checks failed before, 32/32 pass after.
    • bash:3.2 (bash 3.2.57; busybox sh and sed): 13 of 28 failed before, 28/28 pass after.
    • The new Linux steps' run: scripts fail on e4aaa87 and pass on c8feea5.
  • shellcheck is clean, and so is actionlint with shellcheck.

Re-run on the current head e4aaa87 (2026-10-27). The evidence below the line predates the 10-23 rebase. The rebase didn't change this PR's patch in any of its five files, and main hasn't touched install.sh, install.ps1 or install-test.yml since. Dispatched install-test.yml on e4aaa87 with version 0.3.12, the current latest: run 40507506042

  • install.sh (Linux), install.sh (macOS) and install.ps1 (Windows) all pass.
  • Linux: bash -lc and bash -ic resolve /home/runner/.local/bin/ant, and ant --version prints ant 0.3.12. The shadowing note names the image's /usr/bin/ant, and the ANT_NO_MODIFY_PATH=1 step prints the old not-in-PATH warning.
  • macOS (stock /bin/bash runs the installer): the block lands in .profile and .zshenv, and zsh -c resolves /Users/runner/.local/bin/ant. ~/.local/bin is on PATH in zsh -lc, zsh -ic and /bin/bash -lc, and ant --version prints ant 0.3.12. The note names Homebrew's /opt/homebrew/bin/ant.
  • The two npm (Linux) jobs still fail for the reasons below. ci(install-test): make the npm jobs test npm's ant, not Apache Ant's #228 fixes both:
    • test npm install (Linux): "ant is still on PATH after uninstall", because of the image's Apache Ant.
    • --ignore-scripts: the binary logs using 7 bundled bootstrap seed(s), but the job greps for embedded bootstrap peer.
  • The other npm jobs, macOS and Windows, pass.

  • shellcheck (koalaman/shellcheck:stable) is clean.

  • Ubuntu 24.04 container matrix, 37/37 pass. The installer runs as fresh users, and each check starts from a pristine env -i PATH without ~/.local/bin:

    • bash user: fresh bash -i, bash -l, zsh -c, fish -c and sh -l all resolve ~/.local/bin/ant, and ant --version prints ant 0.3.10. Exactly one block lands in .profile, .bashrc, .zshenv and ant.fish, and no .bash_profile is created.
    • Re-run: reports the line is already present, and still one block per file.
    • zsh user with no startup files (macOS-like): zsh -c, zsh -ilc and bash -l resolve. .zshenv and .profile are created, .bashrc is not, and new files have no leading blank line.
    • --no-modify-path and ANT_NO_MODIFY_PATH=1: files untouched, old warning printed. ANT_NO_MODIFY_PATH=0 still modifies.
    • An unknown option exits non-zero. A root-owned 444 .bashrc is reported under "Could not write to:" and the install still succeeds.
    • A relative INSTALL_DIR is written as an absolute path. A pre-existing /usr/local/bin/ant triggers the shadowing note, and there's no note otherwise. Already on PATH means nothing is changed or printed.
  • bash 3.2.57 (bash:3.2, the version stock macOS curl | bash runs): fresh, re-run, all-writes-failed and --no-modify-path paths all run cleanly under set -u with empty arrays.

  • install-test.yml now checks fresh shells after a default install:

    • Linux: bash -lc and bash -ic.
    • macOS: stock /bin/bash runs the installer, then zsh -c, zsh -lc (runs path_helper), zsh -ic and /bin/bash -lc.

    The existing INSTALL_DIR step now sets ANT_NO_MODIFY_PATH=1, which exercises the opt-out and keeps the new check meaningful. Dispatched against this branch with version 0.3.10: run 38253772727

    • install.sh (Linux), install.sh (macOS) and install.ps1 (Windows) all pass.
    • Linux: bash -lc and bash -ic resolve ~/.local/bin/ant, ahead of the image's Apache Ant at /usr/bin/ant, which the note flagged.
    • macOS: zsh -c resolves it, ~/.local/bin is on PATH in zsh -lc, zsh -ic and /bin/bash -lc, and ant --version prints ant 0.3.10.
    • The first run found zsh login shells resolving ant to the runner's Homebrew Apache Ant. That's what prompted the second commit.
    • The two npm (Linux) job failures in both runs predate this PR and are unrelated. Those jobs don't use install.sh, and this is the first time install-test has been dispatched since they were added (V2-1152).
      • test npm install (Linux) fails "ant is still on PATH after uninstall" because of /usr/bin/ant (Apache Ant).
      • --ignore-scripts fails its embedded bootstrap peer log grep.

New dependency

none

ADR

n/a

Mitigation / rollback

Revert the commit. Users who already ran the new installer keep one harmless guarded PATH line, marked # Added by the ant installer, in their startup files.

🤖 Generated with Claude Code

Nic-dorman and others added 2 commits October 23, 2026 20:07
)

After a successful install.sh run, `ant --version` failed with command
not found on a fresh shell: the install dir was not on PATH (macOS's
default /usr/local/bin is root-owned without Homebrew, and the
~/.local/bin workaround is not on macOS's default PATH), and the script
only printed the export line. install.ps1 already adds its dir to the
Windows user PATH.

install.sh now:
- defaults to ~/.local/bin on macOS as well as Linux, so it never needs
  sudo;
- when INSTALL_DIR is not already on PATH, appends one guarded line to
  ~/.profile, the user's existing bash startup files (creating ~/.bashrc
  when bash is the login shell), ~/.zshenv when zsh is installed, and
  fish's conf.d when fish is (the rustup set), listing each file it
  changed. Re-runs add nothing; unwritable files are reported, not fatal;
- honours --no-modify-path / ANT_NO_MODIFY_PATH=1 to keep the old
  print-the-line behaviour (install.ps1 honours the env var too);
- notes when another ant is already on PATH, e.g. an earlier macOS
  install in /usr/local/bin that path_helper would put first.

install-test.yml gains a fresh-shell check on Linux and macOS (stock
/bin/bash 3.2, zsh login and non-login).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cOS login shells

The macOS install-test run showed zsh login shells resolving `ant` to the
runner image's Homebrew Apache Ant: /etc/zprofile's path_helper and
`brew shellenv` run after ~/.zshenv and put /opt/homebrew/bin ahead of
~/.local/bin. Forcing ~/.local/bin to the front would change precedence
for every tool in it, so the installer keeps appending and instead says,
when another `ant` is on PATH, that it may be an unrelated program and
how to get past it. The macOS check asserts ~/.local/bin is on PATH in
login shells, and that `zsh -c` resolves this ant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@MaidSafe-Hermes MaidSafe-Hermes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2/2 independent seats reviewed this head: sol-1 (gpt-6.1-sol, xhigh) returned CHANGES_REQUIRED; glm-1 (z-ai/glm-5.3) returned APPROVE. I adjudicated the disagreement against the source and reproduced both issues below, so this is REQUEST_CHANGES. GitHub checks: 18/18 successful. Local verification: bash -n passes; isolated probes reproduce the startup-file command execution and the relative INSTALL_DIR failure with CDPATH=.; the latter also fails in a mocked end-to-end installer run. These are installer regressions not covered by the ordinary-path CI matrix.

Comment thread install.sh
# Guarded, so a shell that reads several of these files (Ubuntu's ~/.profile sources ~/.bashrc)
# still ends up with one entry.
# shellcheck disable=SC2016 # $PATH is meant to reach the startup file unexpanded.
posix_line="$(printf 'case ":${PATH}:" in *":%s:"*) ;; *) export PATH="%s:$PATH" ;; esac' "$dir" "$dir")"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P2] Escape INSTALL_DIR before writing shell startup code. This formats the value directly into POSIX and fish source. If a valid override contains shell syntax, e.g. $(touch${IFS}$HOME/pwned), that text is executed when the generated .profile is sourced; I reproduced the file creation. Quote/escape the directory as a literal separately for POSIX and fish (or reject unsupported characters), and add a regression test for dollar signs, quotes, backticks, and backslashes.

Comment thread install.sh Outdated
# Install binary
mkdir -p "$INSTALL_DIR"
# Absolute, so the PATH entry written below does not depend on where the installer was run from.
INSTALL_DIR="$(cd "$INSTALL_DIR" && pwd)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P2] Clear CDPATH when resolving a relative INSTALL_DIR. With CDPATH=. and INSTALL_DIR=bin, this cd prints ./bin to stdout; command substitution captures that plus pwd, so INSTALL_DIR contains two newline-separated paths and the following cp fails. I reproduced this in a mocked installer run. Use a CDPATH-cleared cd and add a test for a relative override with CDPATH set.

Nic-dorman and others added 2 commits October 27, 2026 13:22
… relative one

The PATH lines written to the shell startup files formatted INSTALL_DIR
straight into shell code, so a directory name containing $(...),
backticks or quotes ran as code whenever a new shell started. Everything
after the literal $HOME prefix is now escaped for double quotes: \ " $
and ` for POSIX shells, and \ " and $ for fish. The escaping uses sed,
because bash's ${var//...} replacement quoting differs between 3.2 and
5.2. The `export PATH=...` line printed for the current shell is escaped
the same way. A directory name with a newline leaves the startup files
alone and says so.

Making a relative INSTALL_DIR absolute ran a plain `cd`, which with
CDPATH set prints the directory too, so INSTALL_DIR held two lines and
the copy failed. It now clears CDPATH, and `cd` and `mkdir` take `--`.

install-test gains regression steps:
- Linux and macOS: an install directory named with $(...), backticks,
  quotes and a backslash must resolve literally in each new shell,
  including dash, zsh and fish on Linux, and from the printed line,
  without running anything.
- Linux: a relative INSTALL_DIR with CDPATH=. must install to the right
  absolute path.
The shell lookups in these steps report a failure instead of exiting
silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… on PATH

Escaping INSTALL_DIR in the startup lines (3211965) isn't enough on
macOS. Login shells there run `eval $(path_helper -s)` from /etc/zprofile
and /etc/profile, and path_helper prints every PATH entry inside double
quotes without escaping. So a correctly written entry containing $(...)
still runs, or is mangled, in every new login shell. The macOS
install-test run showed it: zsh -lc had
`.../odd   quoted back\slash` on PATH, with the substitutions run and
the quotes stripped.

A directory whose name contains $, `, ", \ or a newline is now left
out of every startup file. The installer prints a warning, plus an
escaped `export PATH=` line for the current shell only. Every remaining
character is literal inside the double quotes of the POSIX and fish
lines, so the per-shell escaping is gone. The printed lines stay
escaped.

install-test now checks, on Linux and macOS:
- such a name is refused, no startup file is written, the printed line
  resolves, and nothing runs, including through a macOS login shell;
- a name with spaces, ', ( ), &, # and * is still added and resolves in
  every shell, through path_helper on macOS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nic-dorman

Copy link
Copy Markdown
Member Author

Both findings are addressed, in 3211965 and c8feea5.

P2: escape INSTALL_DIR before writing shell startup code (install.sh:143). I went with your alternative: such names are refused rather than escaped.

The first fix, 3211965, escaped the directory per syntax: \ " $ and the backtick for POSIX, and \ " $ for fish. On Linux that held in bash, dash, zsh and fish, but the macOS install-test run showed it isn't enough there.

  • macOS login shells run eval `/usr/libexec/path_helper -s` from /etc/zprofile and /etc/profile.
  • path_helper prints PATH="<every entry>"; export PATH; without escaping, so a correctly written entry is re-parsed as code in every login shell.
  • zsh -lc had .../odd quoted back\slash on PATH: both substitutions had run and the quotes were stripped (run 40511569910, macOS job).
  • The same happens to a user who adds such a directory by hand.

So c8feea5 refuses to add any directory whose name contains $, a backtick, ", \ or a newline. In that case:

  • No startup file is written.
  • The installer prints a warning saying why, and an export PATH=... line for the current shell only, escaped so it is safe to paste.
  • The printed line on the existing --no-modify-path path is escaped the same way.

Every other character is literal inside the double quotes of the POSIX and fish lines, so the per-syntax escaping was removed.

P2: clear CDPATH when resolving a relative INSTALL_DIR (install.sh:331). Fixed: INSTALL_DIR="$(CDPATH='' cd -- "$INSTALL_DIR" && pwd)". mkdir and cd also take --, so a directory name starting with - works.

Regression tests in install-test.yml:

  • Linux and macOS: shell syntax is refused. INSTALL_DIR="$HOME"'/odd $(touch $HOME/ran-subst) `touch $HOME/ran-backtick` "quoted" back\slash' must produce the warning and write no startup file. The printed line must resolve ant in exactly that directory, and no marker file may appear.
    • Linux checks .profile, .bashrc, .zshenv and fish's conf.d, with zsh and fish installed for the step.
    • On macOS, stock /bin/bash and BSD sed run the installer, and a zsh -lc and a /bin/bash -lc run before the marker check.
  • Linux and macOS: other punctuation is still added. "$HOME/my apps (x86) & 'quoted' #1 *" must resolve in each new shell:
    • Linux: bash -lc, bash -ic, sh -lc (dash), zsh -c, fish -c;
    • macOS: zsh -c, plus the directory on PATH through path_helper in zsh -lc, zsh -ic and /bin/bash -lc.
  • Linux: a relative INSTALL_DIR=bin with CDPATH=. must report and create $work/bin/ant.
  • The command -v lookups, including those in the existing default-install steps, now report which shell failed instead of exiting silently under bash -e.

Verified on dev2 in containers, before and after:

e4aaa87 (before) c8feea5 (after)
Ubuntu 24.04, bash 5.2 runs the installer; dash, zsh 5.9, fish 3.7 13 of 32 checks fail: the odd name is written into all four startup files, both marker files are created, none of $, a backtick, " or \ on its own is refused, the CDPATH install fails in cp on the two-line path, and a newline is written across two lines of .profile 32/32 pass
bash:3.2, bash 3.2.57 runs the installer; busybox sh and sed 13 of 28 fail, the same way 28/28 pass

Checks that pass on both: a default install, a re-run leaving one block per file, a literal $HOME in the line, each shell resolving ~/.local/bin/ant, and the punctuation name added and resolving in every shell.

The new Linux steps' own run: scripts were executed in the Ubuntu container too. Before the fix, the refusal step fails ("expected the installer to refuse this directory") and the CDPATH step fails. After it, all four install.sh steps pass.

shellcheck is clean on install.sh, and on the workflow through actionlint. The intentional word splitting of $shell and the unexpanded $PATH are annotated.

install-test on c8feea5 (npm 0.3.12): run 40514011527. install.sh (Linux), install.sh (macOS) and install.ps1 (Windows) pass, including every new step:

  • Linux: the odd name is refused and its printed line resolves. The punctuation name resolves in bash -lc, bash -ic, sh -lc, zsh -c and fish -c. The CDPATH=. install lands in .../relative/bin/ant.
  • macOS: the odd name is refused, and no marker file appears after a zsh -lc and a /bin/bash -lc. The punctuation name resolves in zsh -c. In zsh -lc, zsh -ic and /bin/bash -lc it appears on PATH intact, as .../my apps (x86) & 'quoted' #1 *, so it survives path_helper.
  • The two npm (Linux) jobs still fail for the reasons ci(install-test): make the npm jobs test npm's ant, not Apache Ant's #228 fixes: Apache Ant on PATH after uninstall, and the stale embedded bootstrap peer grep. The macOS and Windows npm jobs pass.

CI on c8feea5: all jobs pass except Security Audit. It fails only on RUSTSEC-2026-0387 (crypto-bigint 0.5.5), which is inherited from main and tracked as V2-1743. This PR changes no Cargo.toml or Cargo.lock.

@MaidSafe-Hermes MaidSafe-Hermes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Focused re-review of the two prior installer blockers (not a fresh full review). 2/2 seats reviewed this delta: sol-1 (gpt-6.1-sol, xhigh) and glm-1 (z-ai/glm-5.3); both found the prior command-execution and CDPATH issues fixed, and both found the remaining colon-in-PATH defect. I also reproduced the trailing-newline normalization defect below. I am requesting changes for these two remaining installer bugs. The Security Audit is failing at this head: cargo-audit reports crypto-bigint 0.5.5 / RUSTSEC-2026-0387 (timing variability in intended constant-time operations; advisory says upgrade to >=0.7.6). Cargo.lock is byte-identical to the PR base, so this failure is inherited, not introduced by this installer delta. Per the request, do not merge until Security Audit is green. Validation: bash -n passes; mocked installer probes confirm relative INSTALL_DIR works with CDPATH=. and confirm a trailing-newline INSTALL_DIR installs into the stripped, unintended directory. The focused install.sh Linux/macOS and install.ps1 Windows jobs pass; the separate npm Linux jobs fail on the existing Apache Ant uninstall check and bootstrap-peer log assertion.

Comment thread install.sh Outdated
# the line, which is checked for line by line.
unsafe_on_path() {
case "$1" in
*'$'* | *'`'* | *'"'* | *\\* | *$'\n'*) return 0 ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P2] Reject colon-containing INSTALL_DIR values before adding them to PATH. PATH is colon-delimited, but unsafe_on_path accepts colon. A value ending in colon writes an empty PATH component (which searches the current directory); an embedded colon adds an unintended second component. I reproduced the generated startup line. Refuse this value for both startup edits and the printed export, and add a regression test for a colon-containing path (including fish, where it can be re-added on each startup).

Comment thread install.sh Outdated
mkdir -p -- "$INSTALL_DIR"
# Absolute, so the PATH entry written below does not depend on where the installer was run from.
# CDPATH cleared, or `cd` into a relative directory found through it prints that directory too.
INSTALL_DIR="$(CDPATH='' cd -- "$INSTALL_DIR" && pwd)"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P2] Check for newline before capturing pwd, or preserve it through normalization. Bash command substitution strips trailing newline bytes. With INSTALL_DIR ending in a newline, mkdir creates that requested directory, but this assignment loses the newline; if the stripped path already exists, the installer exits successfully after copying ant into that different directory. I reproduced this with a mocked installer run. Reject the raw input before normalization (or use a sentinel and then reject), and add a trailing-newline regression test.

…e up front

PATH is colon-separated, so a directory with `:` in its name can't be
one of its entries. A trailing `:` added an empty entry, which searches
the current directory, and one in the middle added a second, different
directory; in fish the line could re-add itself on every start. Such a
directory is now left off PATH, with or without --no-modify-path. The
installer says why and suggests running ant by its full path, and
prints no `export PATH=` line.

An INSTALL_DIR containing a newline is now rejected before anything is
downloaded or created. Making the path absolute with `$(... pwd)`
stripped a trailing newline, so ant could be copied into the directory
without it and the install still reported success.

install-test's Linux job gains a step covering a colon in the middle, a
trailing colon and a colon with ANT_NO_MODIFY_PATH=1. Each must be
refused, with no startup file written (fish's included) and no export
line printed. The step also checks that an INSTALL_DIR ending in a
newline, whose stripped directory exists, fails and installs nothing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nic-dorman

Copy link
Copy Markdown
Member Author

Both findings are addressed in e1648ce.

P2: reject colon-containing INSTALL_DIR values before adding them to PATH (install.sh:95). Fixed. A directory with : in its name can't be a PATH entry, so the installer no longer tries to add one:

  • No startup file is written, fish's conf.d included.
  • No export PATH= line is printed, on either the default path or the --no-modify-path/ANT_NO_MODIFY_PATH=1 path.
  • The installer prints why (':' in its name separates PATH entries) and suggests installing elsewhere or running <dir>/ant by its full path.
  • The binary is still installed where it was asked to go.

P2: check for a newline before capturing pwd (install.sh:360). Fixed. An INSTALL_DIR containing a newline anywhere is now rejected right after it's read, before anything is downloaded or created. The installer exits 1 with INSTALL_DIR must not contain a newline. Nothing reaches the $(… pwd) normalization, so a trailing newline can no longer redirect the install. The refusal check no longer lists the newline, since such a name can't get that far.

Regression tests: a new step in install-test.yml's Linux job covers three colon cases: one in the middle, a trailing one, and one with ANT_NO_MODIFY_PATH=1.

  • Each must print the refusal and no export PATH= line.
  • None of .profile, .bashrc, .zshenv or conf.d/ant.fish may be written. fish and zsh are installed by the job's earlier step.
  • An INSTALL_DIR ending in a newline, whose stripped directory already exists, must fail and install nothing in either directory.

Verified on dev2 in containers (the same harness as last round, with the new cases added):

c8feea5 (before) e1648ce (after)
Ubuntu 24.04, bash 5.2; dash, zsh, fish 13 of 45 fail, all of them the new cases. Both newline names exit 0. The trailing-newline install lands in the existing stripped dir and writes .profile, which is your reproduction. All three colon names are added to .profile, .bashrc, .zshenv and ant.fish, with an export line printed. 45/45 pass
bash:3.2, bash 3.2.57; busybox sh and sed 13 of 41 fail, the same way 41/41 pass

All of last round's checks still pass on both: odd names refused, punctuation names added, CDPATH, default install, re-run.

I also ran the new Linux step's own run: script in the Ubuntu container. On c8feea5 it fails with ERROR: expected the installer to refuse …/with:colon, and on e1648ce it passes. shellcheck is clean on install.sh, and actionlint with shellcheck is clean on the workflow.

install-test on e1648ce (npm 0.3.12): run 40531438944. install.sh (Linux and macOS) and install.ps1 (Windows) pass. In the new step, the three colon names install and print can't be added to your PATH: ':' in its name separates PATH entries, and the newline case exits with INSTALL_DIR must not contain a newline. The two npm (Linux) jobs fail only for the reasons #228 fixes. The macOS and Windows npm jobs pass.

CI on e1648ce: all jobs pass except Security Audit. It fails only on RUSTSEC-2026-0387 (crypto-bigint 0.5.5), which is inherited from main and tracked as V2-1743. This PR changes no Cargo.toml or Cargo.lock.

@MaidSafe-Hermes MaidSafe-Hermes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Focused re-review of PR #223 at e1648ce25f0e37d81969d0874bb5343bdc5cc510. 2/2 seats reviewed this exact delta: sol-1 (gpt-6.1-sol, xhigh) and glm-1 (z-ai/glm-5.3). Both found the remaining newline-path defect below. The colon-in-PATH findings from the prior review are fixed in both PATH-modification modes.

P2 — reject newline in the effective install path before normalizing it (install.sh:359-360, install.sh:385). The new guard only checks the supplied INSTALL_DIR, before relative paths are resolved. A relative value such as INSTALL_DIR=. passes when the current working directory's name ends in a newline. The $(... pwd) command substitution at line 385 strips that trailing newline and silently changes the destination. I reproduced this: from /.../work , the exact normalization resolves to /.../work; if that sibling exists, the installer can overwrite its ant binary. Relative paths from a working directory containing an embedded newline also bypass the guard and can write a malformed multi-line PATH entry. Validate the effective absolute path without losing newline characters before installing or writing startup files, and add regressions for both cwd cases.

Checks: 17/18 current checks pass; Security Audit fails. The PR's Cargo.lock is byte-identical to its base (ccd4106...), so that audit failure is inherited, not introduced here. Local bash -n passes. ShellCheck was not run because it is not installed in this environment.

…t INSTALL_DIR

The newline check looked only at INSTALL_DIR as given. A relative one
was resolved later against the current directory, whose own name could
hold the newline:
- With INSTALL_DIR=. run from a directory whose name ends in a newline,
  `$(cd ... && pwd)` stripped it, and ant went into the sibling without
  it, overwriting any ant there.
- A newline in the middle of the current directory's path reached the
  PATH lines.

The check now runs on the effective path before anything is downloaded
or created: INSTALL_DIR itself if absolute, otherwise "$PWD/$INSTALL_DIR".
$PWD keeps trailing newlines, where `$(pwd)` doesn't. Making the path
absolute is also lossless now: `pwd && printf x`, then exactly "\nx" is
cut. Even an unforeseen route can't change the destination silently.

install-test's colon/newline step gains two cases. INSTALL_DIR=. is run
from a directory ending in a newline with its sibling present, and
INSTALL_DIR=bin from one with a newline in the middle. Each must fail
and install nothing in either place.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Nic-dorman

Copy link
Copy Markdown
Member Author

Addressed in 65a1f87.

P2: reject a newline in the effective install path before normalizing it (install.sh:359-360, 385). You're right: the check only covered INSTALL_DIR as given, and a relative one picked up the current directory's name later. Two changes:

  1. The check now runs on the effective path, before anything is downloaded or created. That's INSTALL_DIR itself if it's absolute, and "$PWD/$INSTALL_DIR" otherwise. $PWD is a variable, so unlike $(pwd) it keeps a trailing newline. A newline in either part exits 1 with the install directory must not contain a newline (in INSTALL_DIR, or in the current directory for a relative one).
  2. Making the path absolute is now lossless: $(CDPATH='' cd -- "$INSTALL_DIR" && pwd && printf x), then exactly \nx is cut. Even if a newline reached it by some route the check doesn't foresee, the destination can't change silently any more.

Regression tests: the Linux install-test colon/newline step gains both of your cwd cases:

  • INSTALL_DIR=. run from …/work<newline>, with the sibling …/work present;
  • INSTALL_DIR=bin run from …/a<newline>b.

Each must fail, and nothing may be installed in …/work, …/work<newline> or …/a<newline>b/bin.

Verified on dev2 in containers, with the same harness plus these two cases:

e1648ce (before) 65a1f87 (after)
Ubuntu 24.04, bash 5.2; dash, zsh, fish 7 of 52 fail, all of them the new cwd cases. Both runs exit 0. ant is installed into the sibling …/work, and .profile is written, which is your reproduction. 52/52 pass
bash:3.2, bash 3.2.57; busybox sh and sed 7 of 48 fail, the same way 48/48 pass

Every earlier check still passes on both, including the default install and re-run, which go through the new printf x normalization.

The new step's own run: script, executed in the Ubuntu container, fails on e1648ce with ERROR: a relative INSTALL_DIR under a directory with a newline in its name was accepted, and passes on 65a1f87. shellcheck is clean on install.sh, and actionlint with shellcheck is clean on the workflow.

install-test on 65a1f87 (npm 0.3.12): run 40659873563. install.sh (Linux and macOS) and install.ps1 (Windows) pass. The Linux step refuses the three colon names, and all three newline cases, INSTALL_DIR and the two cwd cases, exit with the install directory must not contain a newline …. The two npm (Linux) jobs fail only for the reasons #228 fixes. The macOS and Windows npm jobs pass.

CI on 65a1f87: all jobs pass except Security Audit. It fails only on RUSTSEC-2026-0387 (crypto-bigint 0.5.5), which is inherited from main and tracked as V2-1743. This PR changes no Cargo.toml or Cargo.lock.

@MaidSafe-Hermes MaidSafe-Hermes left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

APPROVE — focused re-review of the changes since e1648ce, at head 65a1f87. The newline-path fix rejects relative install paths from cwd names containing newlines before download or creation; I verified both trailing-newline and embedded-newline cases against this head, and bash -n passes. No new defect found in this delta.

2/2 independent seats reviewed this same evidence and head (sol-1 gpt-6.1-sol xhigh; glm-1 z-ai/glm-5.3); both recommend APPROVE.

Merge condition: DO NOT MERGE until the Security Audit passes on the current PR head. It is currently failing (crypto-bigint 0.5.5 / RUSTSEC-2026-0387). Cargo.lock is unchanged from base, so this is inherited and not introduced by this installer delta. This approval is not a waiver of that audit gate.

#238 rotated the old [Unreleased] into the 0.3.7 to 0.3.12 sections, so
this PR's entry moves to the new, empty [Unreleased], under the same
heading it had before.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

install.sh doesn't put ant on PATH — ant --version fails on a fresh shell after a successful install (install.ps1 already adds to PATH)

2 participants