You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
ci: no Windows runner anywhere — green checks carry no signal for platform-sensitive shell changes #1414 ("no Windows runner anywhere") is adjacent but different. A Windows runner would not catch this class either: Git Bash on Windows ships GNU grep, so a GNU-only construct passes there exactly as it passes on ubuntu-24.04. The exposure is BSD userland — macOS system grep/sed — which no runner in this repo covers.
The concrete near-miss that motivated this
While hardening plugins/markdown-format/hooks/markdown-format.sh on #1097, a predicate deciding whether a markdownlint config's module specifiers can be pinned to an approval was written with \b word boundaries:
grep -Eq '\brequire\b'"$file"
\b is a GNU extension. POSIX leaves \<ordinary char> undefined, and BSD grep may read \b as a literal b — turning \brequire\b into brequireb, which matches nothing. The predicate would then have returned "nothing unpinnable found" for every input.
That is a fail-open in a security predicate, on macOS, that no test on a GNU runner could ever catch. markdown-format.sh's own header explicitly commits to macOS system bash, so the platform is in declared scope.
The fix, now on main, is POSIX-portable and is the reference implementation for what a gate should accept:
word starts/ends spelled as bracket expressions — (^|[^A-Za-z0-9_$])require($|[^A-Za-z0-9_$]) — instead of \b
the residue test uses grep -q rather than grep -o, so no boundary character is consumed (which is what made a count-based approach undercount a nested require(require("x")))
Suggested shape
A changed-file-scoped lane over **/*.sh flagging GNU-only constructs. Non-exhaustive starter set, all of which differ or are undefined under BSD userland:
Direction matters. Flagging a GNU-ism that is actually safe costs a reviewer one line of justification; missing one in a security predicate costs a silent fail-open. The gate should over-flag and allow an inline opt-out with a stated reason, not under-flag.
An alternative or complement worth weighing: run the existing shell test suites once under a BSD-userland container so the behavioral difference surfaces as a test failure rather than a lint finding. That catches constructs no token list anticipated, at the cost of a slower lane.
Provenance
Found on #1097 by adversarially re-probing a predicate I had just written, not by any reviewer. It is the kind of defect that produces no symptom until it is exploited on the one platform CI does not cover.
What is missing
No CI gate checks shell scripts for GNU-only constructs. This is a missing capability, not a defect in an existing gate.
To be explicit about what already exists and why neither covers this:
portability-lint/ ci: portability lint lane — enforce declared agnosticism mechanically (19-issue coupling class + top review-churn source) #531 matches skill-coupling tokens (stack, forge, branch, tracker defaults) fromscripts/skill-portability-tokens.txtagainst changed skill files. That is its job and it does it. It is not a shell-portability gate and was never meant to be.ubuntu-24.04. The exposure is BSD userland — macOS systemgrep/sed— which no runner in this repo covers.The concrete near-miss that motivated this
While hardening
plugins/markdown-format/hooks/markdown-format.shon #1097, a predicate deciding whether a markdownlint config's module specifiers can be pinned to an approval was written with\bword boundaries:\bis a GNU extension. POSIX leaves\<ordinary char>undefined, and BSD grep may read\bas a literalb— turning\brequire\bintobrequireb, which matches nothing. The predicate would then have returned "nothing unpinnable found" for every input.That is a fail-open in a security predicate, on macOS, that no test on a GNU runner could ever catch.
markdown-format.sh's own header explicitly commits to macOS system bash, so the platform is in declared scope.The fix, now on
main, is POSIX-portable and is the reference implementation for what a gate should accept:(^|[^A-Za-z0-9_$])require($|[^A-Za-z0-9_$])— instead of\bgrep -qrather thangrep -o, so no boundary character is consumed (which is what made a count-based approach undercount a nestedrequire(require("x")))Suggested shape
A changed-file-scoped lane over
**/*.shflagging GNU-only constructs. Non-exhaustive starter set, all of which differ or are undefined under BSD userland:\b,\<,\>,\s,\S,\w,\W,\+,\?ingrep/sedpatternsgrep -P(PCRE — absent in BSD grep and in some GNU builds)sed -iwithout a backup suffix (BSD requires an argument)sed\|,\+,\?and GNU-only addressesreadlink -f,date -d,stat -c,mktemp -p,head -con some builds,sort -Vecho -eTwo design notes from the near-miss:
An alternative or complement worth weighing: run the existing shell test suites once under a BSD-userland container so the behavioral difference surfaces as a test failure rather than a lint finding. That catches constructs no token list anticipated, at the cost of a slower lane.
Provenance
Found on #1097 by adversarially re-probing a predicate I had just written, not by any reviewer. It is the kind of defect that produces no symptom until it is exploited on the one platform CI does not cover.