Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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`.
Expand Down
9 changes: 8 additions & 1 deletion book/src/linting/false-positives.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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.**
Expand All @@ -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`
Expand Down
4 changes: 3 additions & 1 deletion docs/release-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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`.
Expand Down
6 changes: 4 additions & 2 deletions docs/roadmaps/releases.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 |
|---|---|
Expand All @@ -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

Expand Down
7 changes: 5 additions & 2 deletions docs/roadmaps/roadmap.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -2915,6 +2916,7 @@ roadmap:
estimated_effort: null
labels:
- kind:code
- release:v7.4.2
notes: null
epic: 426
- id: PMAT-432
Expand All @@ -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
Expand All @@ -2955,5 +2957,6 @@ roadmap:
estimated_effort: null
labels:
- kind:code
- release:v7.4.2
notes: null
epic: 426
Loading