From 0c969ffcfb3addbd4020c92fe584da34184e70ff Mon Sep 17 00:00:00 2001 From: Myasnikov Daniil Date: Thu, 30 Jul 2026 12:26:57 +0500 Subject: [PATCH] fix(e2e): refuse a target naming only a major version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `cut -f2` prints the whole line when that line carries no delimiter, so a single-component target like "v1" came back with min=1 and was resolved as if it were v1.1 — walking down to the v1.0 line and printing a real-looking baseline with exit 0. The lane would then install that baseline and report on an upgrade path nobody asked for, which is worse than failing. Pass -s so the delimiter-less line is suppressed, min comes back empty, and the existing parse guard rejects it. Two-component targets are unaffected: the guard rejects a missing minor, not a missing patch, and v1.6 still resolves. In practice the lane only ever passes "" or a target derived from a release-X.Y.Z branch, so this was latent rather than live. Signed-off-by: Myasnikov Daniil --- hack/upgrade-prev-version.sh | 7 ++++++- hack/upgrade-prev-version_test.bats | 25 +++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/hack/upgrade-prev-version.sh b/hack/upgrade-prev-version.sh index 2f7adfd10..3bc5bb4c8 100755 --- a/hack/upgrade-prev-version.sh +++ b/hack/upgrade-prev-version.sh @@ -53,7 +53,12 @@ fi ver="${target#v}" ver="${ver%%-*}" # strip the -rc.N / -beta.N suffix maj=$(printf '%s' "$ver" | cut -d. -f1) -min=$(printf '%s' "$ver" | cut -d. -f2) +# -s is load-bearing: without it `cut -f2` on a line carrying no delimiter +# prints the WHOLE line, so a single-component target like "v1" yielded min=1 +# and was silently resolved as if it were v1.1 — printing a plausible baseline +# with exit 0 instead of rejecting an unparseable target. -s suppresses the +# delimiter-less line, min comes back empty, and the guard below catches it. +min=$(printf '%s' "$ver" | cut -s -d. -f2) case "${maj}:${min}" in *[!0-9:]*|:*|*:) echo "ERROR: cannot parse target version '$target'" >&2; exit 1 ;; esac diff --git a/hack/upgrade-prev-version_test.bats b/hack/upgrade-prev-version_test.bats index 86b2d435d..1c7025cb5 100755 --- a/hack/upgrade-prev-version_test.bats +++ b/hack/upgrade-prev-version_test.bats @@ -125,3 +125,28 @@ EOF false fi } + +# A target naming only a major ("v1") is not a version this resolver can act on, +# and must be refused rather than guessed at. It used to resolve: `cut -f2` +# without -s prints the whole line when it carries no delimiter, so min came +# back as "1" and v1 was silently treated as v1.1 — emitting a real-looking +# baseline from the v1.0 line with exit 0. A wrong-but-plausible baseline is +# worse than an error here, because the lane would install it and report on an +# upgrade path nobody asked for. +@test "a target naming only a major version is an error, not a guess" { + tmp=$(mktemp -d) + trap 'rm -rf "$tmp"' EXIT + _fixture > "$tmp/tags" + + for bad in v1 1; do + if out=$(hack/upgrade-prev-version.sh "$bad" "$tmp/tags" 2>/dev/null); then + echo "FAIL: target '$bad' should be refused, got '$out'" >&2 + false + fi + done + + # The two-component form is legitimate and must keep resolving — the guard + # rejects a missing minor, not a missing patch. + out=$(hack/upgrade-prev-version.sh "v1.6" "$tmp/tags") + [ -n "$out" ] +}