From 9d4984787b786f813ef279ca1846599cdcfe35e4 Mon Sep 17 00:00:00 2001 From: Noah Gift Date: Sat, 10 Oct 2026 11:56:53 +0200 Subject: [PATCH] Release v7.4.2, third cut: the paperwork names the fifteen fixes a tag from main carries, including the SC2075 fix forjar's I8 gate is waiting on The second cut (#416) was not tagged, and two fixes merged after it: #430 (PMAT-428, SEC012 follows an eval payload through a variable) and #441 (PMAT-439, SC2075 false positive on '\\' followed by another quoted string). A tag cut from main carries both, so CHANGELOG, release-notes, releases.md and the book name fifteen fixes, both entries are completed with release:v7.4.2, and PMAT-354 gains the third-cut criterion. Docs-only. Measured on e25dbeef27 (debug build): the completion check of fw16-wired-10g-nm-owner from infra origin/main lints 0 errors (7.4.1: 1 error, SC2075 at 6:99-116), and `echo 'don\'t'` still fires SC2075. Refs PMAT-354 Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 8 ++++++-- book/src/linting/false-positives.md | 9 ++++++++- docs/release-notes.md | 4 +++- docs/roadmaps/releases.md | 6 ++++-- docs/roadmaps/roadmap.yaml | 7 +++++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7dc760cd..90792bae9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [7.4.2] - 2026-10-04 +## [7.4.2] - 2026-10-10 -A patch release with thirteen fixes. Twelve of them fix linter rules that reported correct scripts: most read text the shell never runs as shell (a single-quoted awk or jq program, a comment, an English sentence), or found a word by substring or line regex instead of by its place in the command. The thirteenth drops a dependency that broke every aarch64 build. The release was first cut on 2026-09-20 with four of these fixes and was not tagged before the other nine merged, so the tag carries all thirteen. The nightly build changed too: its Linux assets run on glibc 2.35, an aarch64-linux asset ships, and it builds whenever the `nightly` tag is not at HEAD. +A patch release with fifteen fixes. Thirteen of them fix linter rules that reported correct scripts: most read text the shell never runs as shell (a single-quoted awk or jq program, a comment, an English sentence), or found a word by substring or line regex instead of by its place in the command. One fixes a rule that missed a real `eval` of downloaded text when the payload sat one line above the `eval`. The fifteenth drops a dependency that broke every aarch64 build. The release was first cut on 2026-09-20 with four of these fixes and was not tagged before the other eleven merged, so the tag carries all fifteen. The nightly build changed too: its Linux assets run on glibc 2.35, an aarch64-linux asset ships, and it builds whenever the `nightly` tag is not at HEAD. ### Fixed @@ -39,6 +39,10 @@ A patch release with thirteen fixes. Twelve of them fix linter rules that report - **SC2242 then reported an English sentence as an error** (PMAT-355, #355). Counting was right for code and made the literal case worse: `echo "could not break the matcher — this case discriminates nothing"` opens a case depth on the word "case" and finds `break` in "break the matcher". The rule joins `QUOTE_SENSITIVE_RULES` so it receives the masked copy, and is wired into the allowlist's `(code, check_fn)` pair list — without that it was declared quote-sensitive while nothing ever ran it over a literal, which is bashrs#266's root cause again and is what `test_GH226_quoting_allowlist_names_only_rules_that_exist` caught. Found by `make release-gate` on this cut, so it is in the release rather than after it. +- **SC2075 read `'\\'` followed by another quoted string as one escaped string** (#439, #441). Inside `'...'` a backslash is literal and the next quote closes the string, but the rule matched `'[^']*\\'[^']*'` on the raw line, so `tr -d '\\' | grep -x 'ab'` paired the quote after `\\` with the next quoted word and reported an Error on a script bash runs correctly. At Severity::Error it made forjar's I8 gate refuse a correct completion check. A backslash string followed by a space, `;`, `]`, `"`, `$`, another quote or a comment is now silent, and `echo 'don\'t'` still fires. + +- **SEC012 missed `eval "$X"` when `X` was assigned from `$(yq ...)`, `$(jq ...)` or `$(curl ...)` on an earlier line** (PMAT-428, #430). The rule is line-based, so `eval "$(yq -r .env f.yaml)"` fired and the same payload moved into a variable one line up did not. It now remembers variables assigned from a command substitution carrying a payload (`$(..)`, `"$(..)"`, backticks, `local`/`export` forms) and fires on a later `eval` of `$NAME` or `${NAME}`, naming the assignment line. Any other assignment clears the name, a literal string is not a substitution, and an `eval` that already fired directly is not reported twice. + ### Changed - **The nightly's Linux assets run on glibc 2.35, and an aarch64-linux asset ships** (PMAT-380, #380, #385). The x86_64-linux nightly was built on `ubuntu-latest` (24.04) and needed `GLIBC_2.39`, so it did not start on a glibc 2.35 host, and there was no aarch64-linux leg at all. Both Linux legs now build inside `rust:1.93.0-bullseye` (glibc 2.31), and two steps check the artifact itself: its highest `GLIBC_` symbol must be at most 2.35, and it must run `--version` inside `ubuntu:22.04`. diff --git a/book/src/linting/false-positives.md b/book/src/linting/false-positives.md index d5d6fd6ee..73fc0b04f 100644 --- a/book/src/linting/false-positives.md +++ b/book/src/linting/false-positives.md @@ -150,7 +150,7 @@ and `F-SC1009-GH238-*` falsification entries under `contracts/`. ## What 7.4.2 stopped guessing -7.4.2 carries thirteen fixes, and twelve of them are linter rules that reported +7.4.2 carries fifteen fixes, and thirteen of them are linter rules that reported correct scripts. They fall into three groups, and each group also tells you what the linter will now *not* report. Every fix keeps its true positive firing. @@ -169,6 +169,10 @@ comment and an English sentence are text the shell never runs as shell. `s="$s $h=UP"` as an assignment (#388), and SC2242 no longer reads the words "case" and "break" in a quoted sentence as a `case` and a `break` (#355). Both join `QUOTE_SENSITIVE_RULES`. `$VAR=hello` as a command is still SC1066. +- SC2075 no longer reads `'\\'` followed by another single-quoted string as + one string with an escaped quote: inside `'...'` a backslash is literal and + the next quote closes the string, so `tr -d '\\' | grep -ix '…'` is clean + (#441). `echo 'don\'t'` is still reported. **A word is found by its place in the command, not by a substring or a line regex.** @@ -180,6 +184,9 @@ regex.** - SEC012, which reports `eval` of jq output, needs `eval` as a whole shell word, so `.eval_count` in a jq filter is not `eval` (#375). `eval "$(jq …)"` behind `sudo`, `env X=1`, `command` or `timeout` still fires. +- SEC012 also follows the payload through a variable: `X=$(yq …)` on one line + and `eval "$X"` on a later one fires, naming the assignment line (#430). A + literal string or a reassignment clears the name. - SC2210 and SC2225 find an assignment by word position, so the `pid=` of `ps -o pid= --ppid "$$"` is not an assignment (#370). SC2058 takes `test` or `[` only as the name of a simple command, so `cargo test -q` is not the `test` diff --git a/docs/release-notes.md b/docs/release-notes.md index 8e8bfb2a4..5256c161b 100644 --- a/docs/release-notes.md +++ b/docs/release-notes.md @@ -10,7 +10,7 @@ Planned. The forjar-parity phases that keep being carried (PMAT-253: 1a–1c, 2, ## v7.4.2 -A patch release with thirteen fixes. It was first cut on 2026-09-20 with the four below and was not tagged before nine more merged, so the tag carries all thirteen. +A patch release with fifteen fixes. It was first cut on 2026-09-20 with the four below and was not tagged before eleven more merged, so the tag carries all fifteen. SEC005 read `sk-` inside a word as a hardcoded OpenAI key (#351). The provider prefixes were matched as bare substrings, so `TIMER="ci-disk-watch.timer"` was reported as a secret and forjar's `ci-disk-watch-timer-enable` failed its apply-gate on every intel and gx10 converge — bashrs refusing a script bashrs ships. A prefix now counts only at a token start; property tests assert both directions and `F-SEC005-BOUNDARY` makes the boundary a contract row. @@ -22,6 +22,8 @@ SC2242 then reported an English sentence as an error (#355). Counting was right The nine that merged after the first cut are all linter rules that reported correct scripts. SC2086 and SC2154 fired on `$name` inside `'...'` and after a trailing `#` (#362). Eight rules, among them SC2204, SC1075 and SC2297, read a single-quoted awk program as shell (#364). SC1087 read `$s[0]` in a single-quoted jq program as an array expansion, and SEC012 read `.eval_count` as `eval` (#375). SC2107 read `[ "$(a || b)" = x ]` as `[ a || b ]` (#366). SC2210 and SC2225 found assignments, and SC2058 found the `test` builtin, by line regex instead of by word position, so `ps -o pid= --ppid` read as an assignment and `cargo test -q` as the `test` builtin (#370, #371). SC1066 read `$h=` inside a double-quoted string as an assignment (#388). DET002 lost the destination of a `\`-continued command (#376), and reported `date -d`, `date -r` and `date -f`, which convert a time they are given and read no clock (#386). +Two more merged after the second cut. SC2075 read `'\\'` followed by another single-quoted string as one string with an escaped quote and reported an Error on a correct script, so forjar's I8 gate refused a completion check that strips the backslashes nmcli prints in a MAC (`tr -d '\\' | grep -ix '…'`); `echo 'don\'t'` still fires (#439, #441). And SEC012 missed `eval "$X"` when `X` was assigned from `$(yq …)`, `$(jq …)` or `$(curl …)` a line earlier; it now remembers the assignment and fires on the later `eval`, naming the line (#428, #430). + The nightly build changed too. Its Linux assets were built on Ubuntu 24.04 and needed glibc 2.39; both Linux legs now build on glibc 2.31 and are checked to run on Ubuntu 22.04's 2.35, and an aarch64-linux asset ships (#380, #385). It builds whenever the `nightly` tag is not at HEAD, instead of when a commit landed in the last 24 hours, a window that never retried a failed build on a quiet day (#397). Full notes: the `[7.4.2]` section of `CHANGELOG.md`. diff --git a/docs/roadmaps/releases.md b/docs/roadmaps/releases.md index 8d7a488fd..1e42727c4 100644 --- a/docs/roadmaps/releases.md +++ b/docs/roadmaps/releases.md @@ -67,7 +67,7 @@ Released: tag `v7.4.0` on 4aa24ca271, `bashrs 7.4.0` on crates.io, `cargo instal ## v7.4.2 -Goal: the defects found after v7.4.1 released as a patch, so forjar's bashrs pin can move off 6.68.0 and `ci-disk-watch-timer-enable` converges. The first cut (2026-09-20, #356) carried four fixes and was not tagged; nine more merged to main before the tag, so the release carries thirteen fixes and two changes to the nightly build. +Goal: the defects found after v7.4.1 released as a patch, so forjar's bashrs pin can move off 6.68.0 and `ci-disk-watch-timer-enable` converges. The first cut (2026-09-20, #356) carried four fixes and was not tagged; nine more merged before the second cut (2026-10-04) and two after it, so the release carries fifteen fixes and two changes to the nightly build. | entry | what | |---|---| @@ -85,10 +85,12 @@ Goal: the defects found after v7.4.1 released as a patch, so forjar's bashrs pin | PMAT-380 | #380, #385: the nightly's Linux assets needed glibc 2.39 and there was no aarch64-linux leg; both legs build on glibc 2.31 and are checked to run on 2.35 | | PMAT-386 | #386: DET002 reported `date -d`/`-r`/`-f`, which convert a time they are given and read no clock | | PMAT-388 | #388: SC1066 read `$h=` inside a double-quoted string as an assignment; it joins `QUOTE_SENSITIVE_RULES` | +| PMAT-428 | #430: SEC012 missed `eval "$X"` when `X` was assigned from `$(yq ...)`/`$(jq ...)`/`$(curl ...)` on an earlier line; the assignment is remembered and the later `eval` fires, naming its line | +| PMAT-439 | #441: SC2075 read `'\\'` followed by another single-quoted string as an escaped quote and reported an Error on a correct script, which made forjar's I8 gate refuse fw16's `fw16-wired-10g-nm-owner` completion check; `echo 'don\'t'` still fires | Also on this release, with no roadmap entry: #375 (#399), where SC1087 read `$s[0]` in a single-quoted jq program as an array expansion and SEC012 read `.eval_count` as `eval`; and #397, which builds the nightly whenever the `nightly` tag is not at HEAD. -Issues on this release: #331, #350, #352, #354, #355, #362, #364, #366, #370, #371, #375, #376, #380, #385, #386, #388. +Issues on this release: #331, #350, #352, #354, #355, #362, #364, #366, #370, #371, #375, #376, #380, #385, #386, #388, #428, #439. ## v7.4.1 diff --git a/docs/roadmaps/roadmap.yaml b/docs/roadmaps/roadmap.yaml index d44506833..a0613ff76 100644 --- a/docs/roadmaps/roadmap.yaml +++ b/docs/roadmaps/roadmap.yaml @@ -2652,6 +2652,7 @@ roadmap: - 'AND PMAT-355 (#355), which is IN this cut rather than a follow-up because the release gate is red without it: #332 made SC2242 count case depth in RAW source, so `echo "could not break the matcher — this case discriminates nothing"` is reported as a break outside a case. The rule joins QUOTE_SENSITIVE_RULES and is wired into the allowlist''s (code, check_fn) pair list, which is what makes the property test actually run it. This criterion was widened from the diff after a three-engine quorum refused the PR for carrying work the criterion did not name — the criterion was wrong, not the diff, but a release whose paperwork does not describe it cannot be audited.' - 'AND the R4 CLOSEOUT, which is the release ticket''s tail and lands in a SECOND PR (#358) after the cut PR (#356) merges. R4 reads "a tag is cut only when its release has no open entry", and CB-2113 leaves a ticket open in its own PR for the next PR to complete — so PMAT-355 is necessarily still open at the moment #356 merges, and closing it is work only this ticket can own. Done when release-lint reports v7.4.2 with ONE open entry, PMAT-354 itself, which cannot close before the release is cut. Criterion added from the diff after a three-engine quorum refused #358 3/3 for not performing the release cut: the lanes were right that the diff did not match the ticket text, and the ticket text was what was incomplete — a release ticket that does not own its own closeout leaves the closeout ownerless.' - 'AND the SECOND CUT, because the first was not tagged: nine entries merged to main after #356 and before any v7.4.2 tag existed (PMAT-362, PMAT-364, PMAT-366, PMAT-370, PMAT-371, PMAT-376, PMAT-380, PMAT-386, PMAT-388), plus #375 (SC1087 and SEC012, fixed by #399) and #397 (the nightly builds whenever the `nightly` tag is not at HEAD), neither of which has a roadmap entry. A tag cut from main carries all of them whether the paperwork names them or not, so the paperwork moves to the tree: the CHANGELOG''s `[Unreleased]` section becomes `[7.4.2] - 2026-10-04` with the #375 entry it was missing, `docs/release-notes.md` and `docs/roadmaps/releases.md` describe thirteen fixes and two nightly changes instead of four fixes, the nine entries are marked completed with `release:v7.4.2`, and the book carries the release, which `check-book-updated.sh` requires of a release commit. Docs-only: no path a cargo build reads changes after the gate tree 1241570740. Done when release-lint reports v7.4.2 with ONE open entry, PMAT-354 itself.' + - 'AND the THIRD CUT, because the second was not tagged either: two entries merged to main after #416 and before any v7.4.2 tag existed, PMAT-428 (#430, SEC012 follows an eval payload through a variable assigned a line earlier) and PMAT-439 (#441, SC2075 no longer reads `''\\''` followed by another quoted string as an escaped quote, the false positive that made forjar''s I8 gate refuse fw16''s `fw16-wired-10g-nm-owner` completion check). The paperwork names fifteen fixes: CHANGELOG `[7.4.2] - 2026-10-10`, release-notes, releases.md, and both entries completed with `release:v7.4.2`. Docs-only, so the code the tag carries is `e25dbeef27`, and the release gate must be re-measured on that tree before the tag: the second cut''s gates measured `1241570740`, which carries neither fix.' phases: [] subtasks: [] estimated_effort: null @@ -2902,7 +2903,7 @@ roadmap: github_issue: 428 item_type: task title: 'SEC012: eval of a variable assigned from a payload substitution (bashrs#428)' - status: planned + status: completed priority: medium assigned_to: null created: 2026-10-06T01:39:42Z @@ -2915,6 +2916,7 @@ roadmap: estimated_effort: null labels: - kind:code + - release:v7.4.2 notes: null epic: 426 - id: PMAT-432 @@ -2940,7 +2942,7 @@ roadmap: github_issue: 439 item_type: task title: 'SC2075: a single-quoted backslash followed by another single-quoted string is not an escaped quote (bashrs#439)' - status: inprogress + status: completed priority: high assigned_to: null created: 2026-10-10T07:13:54Z @@ -2955,5 +2957,6 @@ roadmap: estimated_effort: null labels: - kind:code + - release:v7.4.2 notes: null epic: 426