Let the identity check tell a write from a read and from its own repair
Widening the scope group to --[a-z-]+ was wrong in a way the previous
commit's matrix could not see, because it had no repair row: --unset is
matched as a scope flag, and user. follows it, so
git config --local --unset user.name
is flagged as a violation. That is the remedy this check recommends in its
own failure message. A build that fails on its own repair is how a correct
check gets deleted — the failure accuses the check, and the next person
believes it. The same widening flags a read, echo "$(git config user.name)".
Two halves, both needed. Enumerate the three real scopes, and require a
VALUE after the key: user\.[a-zA-Z]+[[:space:]]+[^[:space:])]. The trailing
class is what makes a read legal — $(git config user.name) has ) where a
write has a value. Sixteen rows measured one at a time, 16/16, and eleven of
them now run in ci/selftest.sh (44 assertions).
Reported by the Nolimit Security session; three of us reached for
--[a-z-]+ independently, so the comment above the step says explicitly not
to, with the reason, rather than leaving it to be re-derived. The read row
came from nolimit-seo.
Two things found while proving it, both about the probe rather than the
check:
ci/selftest.sh is itself a ci/*.sh file, so its fixtures trip the check they
test. Spelled out in full, the first version of this block reported six
false positives and no count, while the same rows measured standalone were
16/16 correct — a failing probe is evidence about the probe until you have
looked at it. Fixed the way the credential fixtures three hundred lines up
in the same file already were: assemble the value through $gc so the source
matches nothing while the written file matches fully. The labels need it
too, so they name the shape instead of quoting the line.
And a plant that lands in a SOURCED file is not inert text. nolimit-seo
planted into ci/lib.sh, which ci/static.sh sources, so the line ran and set
the clone's identity for real. The target here is ci/build-zip.sh and only
ci/lib.sh is sourced — verified rather than assumed — and a final assertion
checks the clone still has no local identity, because if a fixture had
executed then every verdict above it was measured against a tree nobody
intended.
static=0 selftest=0 site=0; site still reports 69 assertions on WordPress
7.1.2 with WooCommerce 11.1.2. ci/ ships nothing, so no version bump.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
7b1d667b5a
commit
97818ec4db
+74
-32
@@ -206,44 +206,77 @@ ann=$(ci_php ci/check-textdomain.php "$TEXT_DOMAIN" selftest-dotted 2>&1 \
|
|||||||
|| bad "the annotated path is not a file: '$ann'"
|
|| bad "the annotated path is not a file: '$ann'"
|
||||||
|
|
||||||
# ------------------------------------------------------- the clone's git identity
|
# ------------------------------------------------------- the clone's git identity
|
||||||
# 🔴 Grade 2 evidence: the exact line ci/bump-tested-up-to.sh carried until
|
# 🔴 Grade 2 evidence: row 1 is the exact line ci/bump-tested-up-to.sh carried
|
||||||
# 2026-09-25, not an arbitrary `git config`. That line writes .git/config, so
|
# until 2026-09-25, not an arbitrary `git config`. That line writes .git/config,
|
||||||
# every later commit in the clone — a release, by a person — is authored by the
|
# so every later commit in the clone — a release, by a person — is authored by
|
||||||
# robot with no trailer. Six such commits in one sibling plugin, two in
|
# the robot with no trailer. Six such commits in one sibling plugin, two in
|
||||||
# another. git warns about an identity it cannot FIND and never about a wrong
|
# another. git warns about an identity it cannot FIND and never about a wrong
|
||||||
# one, so nothing in the repository ever says so.
|
# one, so nothing in the repository ever says so.
|
||||||
|
#
|
||||||
|
# The controls are the larger half here, and three of them were live faults in
|
||||||
|
# earlier versions of this check: `--[a-z-]+` as the scope group reads `--unset`
|
||||||
|
# as a scope and flags the REPAIR, and without a trailing-value requirement a
|
||||||
|
# `$(git config user.name)` READ is a violation. A build that fails on its own
|
||||||
|
# remedy is how a correct check gets deleted — the failure accuses the check,
|
||||||
|
# and the next person believes it.
|
||||||
|
#
|
||||||
|
# 🔴 The fixtures are assembled through $gc rather than written out, because
|
||||||
|
# THIS file is a ci/*.sh file and the check reads every one of them. Spelled in
|
||||||
|
# full, its own rows match: the first version of this block reported six false
|
||||||
|
# positives and no count, while the same rows measured standalone were 16/16
|
||||||
|
# correct. A failing probe is evidence about the probe until you have looked at
|
||||||
|
# it — and the labels have to dodge it too, which is why they name the shape
|
||||||
|
# instead of quoting the line. Identical to the credential fixtures three
|
||||||
|
# hundred lines up, one file over.
|
||||||
|
#
|
||||||
|
# The plant goes into ci/build-zip.sh because nothing here SOURCES it: static.sh
|
||||||
|
# and this script source ci/lib.sh only. A plant in a sourced file is not inert
|
||||||
|
# text — it runs, sets the clone's identity for real, and the commit fixing the
|
||||||
|
# fault is authored by the plant. nolimit-seo lost a run to exactly that. So the
|
||||||
|
# last assertion checks the clone rather than the output.
|
||||||
echo
|
echo
|
||||||
echo "→ ci/static.sh must catch a ci/ script that configures the clone"
|
echo "→ ci/static.sh must catch a written identity and ignore reads and repairs"
|
||||||
cd "$work/repo"
|
cd "$work/repo"
|
||||||
cp ci/build-zip.sh "$work/build-zip.orig"
|
cp ci/build-zip.sh "$work/build-zip.orig"
|
||||||
for scope in '' '--local' '--global'; do
|
|
||||||
cp "$work/build-zip.orig" ci/build-zip.sh
|
|
||||||
line="git config ${scope:+$scope }user.name \"compatibility check\""
|
|
||||||
printf '%s\n' "$line" >> ci/build-zip.sh
|
|
||||||
# The plant must BE there before any verdict is believed. A `printf` that
|
|
||||||
# silently went somewhere else leaves the control state, and every assertion
|
|
||||||
# below then passes while proving nothing — a hollow probe wearing the
|
|
||||||
# costume of the most rigorous thing in the suite.
|
|
||||||
if [ "$(grep -cF "$line" ci/build-zip.sh)" -lt 1 ]; then
|
|
||||||
bad "could not stage the git-identity fixture for '${scope:-no flag}'"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
id_out=$(./ci/static.sh 2>&1 || true)
|
|
||||||
if printf '%s' "$id_out" | grep -q 'sets a git identity that outlives it'; then
|
|
||||||
ok "flagged git config ${scope:-(no flag)} user.name"
|
|
||||||
else
|
|
||||||
bad "NOT FLAGGED: git config ${scope:-(no flag)} user.name — the clone keeps the robot's name"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
# The correct form must NOT fire, or the fix trips the check that exists to
|
identity_row() { # $1 = RED|green (want), $2 = label, $3 = line to plant
|
||||||
# protect it and the next person deletes one of the two.
|
cp "$work/build-zip.orig" ci/build-zip.sh
|
||||||
cp "$work/build-zip.orig" ci/build-zip.sh
|
printf '%s\n' "$3" >> ci/build-zip.sh
|
||||||
printf '%s\n' 'git -c user.name="compatibility check" -c user.email=x commit -m y' >> ci/build-zip.sh
|
# The plant must BE there before any verdict is believed. A printf that went
|
||||||
scoped_out=$(./ci/static.sh 2>&1 || true)
|
# somewhere else leaves the control state, and then every row passes while
|
||||||
printf '%s' "$scoped_out" | grep -q 'sets a git identity that outlives it' \
|
# proving nothing — and the all-green rows are exactly the ones whose zeroes
|
||||||
&& bad "FALSE POSITIVE on 'git -c user.name=… commit' — that is the fix, not the fault" \
|
# you want to believe.
|
||||||
|| ok "ignored the scoped 'git -c … commit' form"
|
if [ "$(grep -cF "$3" ci/build-zip.sh)" -lt 1 ]; then
|
||||||
|
bad "could not stage the git-identity fixture: $2"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
row_out=$(./ci/static.sh 2>&1 || true)
|
||||||
|
if printf '%s' "$row_out" | grep -q 'sets a git identity that outlives it'; then
|
||||||
|
got=RED
|
||||||
|
else
|
||||||
|
got=green
|
||||||
|
fi
|
||||||
|
if [ "$got" = "$1" ]; then
|
||||||
|
ok "$2 -> $got"
|
||||||
|
elif [ "$1" = RED ]; then
|
||||||
|
bad "NOT FLAGGED: $2 — the clone keeps the robot's name"
|
||||||
|
else
|
||||||
|
bad "FALSE POSITIVE: $2 — the check goes red on something it must allow"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
gc="git config" # never spelled out below: see the note above
|
||||||
|
identity_row RED 'WRITE no flag, quoted value' "$gc user.name \"compatibility check\""
|
||||||
|
identity_row RED 'WRITE --local scope' "$gc --local user.name \"x\""
|
||||||
|
identity_row RED 'WRITE --global scope' "$gc --global user.email \"x@y\""
|
||||||
|
identity_row RED 'WRITE --system scope' "$gc --system user.name \"x\""
|
||||||
|
identity_row RED 'WRITE unquoted value' "$gc user.email noreply@localhost"
|
||||||
|
identity_row green 'REPAIR --local --unset' "$gc --local --unset user.name"
|
||||||
|
identity_row green 'REPAIR --unset, no scope' "$gc --unset user.email"
|
||||||
|
identity_row green 'READ inside a command substitution' "echo \"\$($gc user.name)\""
|
||||||
|
identity_row green 'FIX git -c ... commit' 'git -c user.name="x" -c user.email=y commit -m z'
|
||||||
|
identity_row green 'other a non-identity key' "$gc core.autocrlf false"
|
||||||
|
identity_row green 'prose a commented-out mention' "# $gc user.name \"x\" is the fault"
|
||||||
cp "$work/build-zip.orig" ci/build-zip.sh
|
cp "$work/build-zip.orig" ci/build-zip.sh
|
||||||
|
|
||||||
# And the passing line must carry a number: "every script is clean" reads the
|
# And the passing line must carry a number: "every script is clean" reads the
|
||||||
@@ -253,6 +286,15 @@ printf '%s' "$clean_out" | grep -qE "^ [0-9]+ scripts in ci/ leave the clone's
|
|||||||
&& ok "the git-identity check reports how many scripts it read" \
|
&& ok "the git-identity check reports how many scripts it read" \
|
||||||
|| bad "the git-identity check printed no count — it cannot tell 11 scripts from 0"
|
|| bad "the git-identity check printed no count — it cannot tell 11 scripts from 0"
|
||||||
|
|
||||||
|
# The fixtures are lines that SET an identity. If any of them executed rather
|
||||||
|
# than being read, this clone now has one — which would also mean every verdict
|
||||||
|
# above was measured against a tree nobody intended.
|
||||||
|
if git config --local --get-regexp '^user\.' >/dev/null 2>&1; then
|
||||||
|
bad "a git-identity fixture RAN — the clone has a local identity and the rows above are void"
|
||||||
|
else
|
||||||
|
ok "no fixture executed: the clone still has no local identity"
|
||||||
|
fi
|
||||||
|
|
||||||
# ------------------------------------------------------------------- plugin.conf
|
# ------------------------------------------------------------------- plugin.conf
|
||||||
echo
|
echo
|
||||||
echo "→ the floors must be required, not defaulted"
|
echo "→ the floors must be required, not defaulted"
|
||||||
|
|||||||
+28
-3
@@ -322,8 +322,33 @@ ci_step "no ci/ script configures the clone it was handed"
|
|||||||
# misattributed hand commits in one sibling, two in another, including
|
# misattributed hand commits in one sibling, two in another, including
|
||||||
# releases. This tree was clean and one run away.
|
# releases. This tree was clean and one run away.
|
||||||
#
|
#
|
||||||
# Any flag, not just --local: --global sets the whole machine's identity, which
|
# The pattern has to separate a WRITE from a read and from the repair, and the
|
||||||
# is the same fault one scope wider and reaches every repository on the box.
|
# obvious widening gets all three wrong. Measured row by row:
|
||||||
|
#
|
||||||
|
# git config user.name "compatibility check" WRITE caught
|
||||||
|
# git config --local user.name "x" WRITE caught
|
||||||
|
# git config --global user.email "x@y" WRITE caught <- --local only: missed
|
||||||
|
# git config --system user.name "x" WRITE caught
|
||||||
|
# git config --local --unset user.name REPAIR ignored <- --[a-z-]+: flagged
|
||||||
|
# git config --unset user.email REPAIR ignored <- --[a-z-]+: flagged
|
||||||
|
# echo "$(git config user.name)" READ ignored <- --[a-z-]+: flagged
|
||||||
|
# git -c user.name="x" … commit -m y FIX ignored
|
||||||
|
# git config core.autocrlf false other ignored
|
||||||
|
# git config --global core.pager cat other ignored
|
||||||
|
# # git config user.name "x" (a comment) prose ignored
|
||||||
|
# (nothing planted) clean ignored
|
||||||
|
#
|
||||||
|
# ⚠️ Do NOT widen the scope group to `--[a-z-]+`. It reads `--unset` as a scope
|
||||||
|
# and then finds `user.` right after it, so the check goes red on the very
|
||||||
|
# repair this message recommends — and a build that fails on the remedy is how
|
||||||
|
# a correct check gets deleted: the failure accuses the check, and the next
|
||||||
|
# person believes it. Three sessions reached for `--[a-z-]+` independently on
|
||||||
|
# 2026-09-25, which is why it is written here rather than re-derived.
|
||||||
|
#
|
||||||
|
# Enumerating the three real scopes is one half. The other is requiring a VALUE
|
||||||
|
# after the key — `user\.[a-zA-Z]+[[:space:]]+[^[:space:])]` — which is what
|
||||||
|
# makes a read not a violation: `$(git config user.name)` has `)` where a write
|
||||||
|
# has a value. Found by nolimit-seo, whose first version failed on it.
|
||||||
#
|
#
|
||||||
# The rule: a script may configure ITSELF, never the working copy it was
|
# The rule: a script may configure ITSELF, never the working copy it was
|
||||||
# handed. Reported by the Nolimit Security session 2026-09-25.
|
# handed. Reported by the Nolimit Security session 2026-09-25.
|
||||||
@@ -331,7 +356,7 @@ scripts_scanned=$(git ls-files 'ci/*.sh' | wc -l)
|
|||||||
if [ "$scripts_scanned" -lt 1 ]; then
|
if [ "$scripts_scanned" -lt 1 ]; then
|
||||||
note_fail "The git-identity scan found no ci/*.sh files at all, so it proved nothing."
|
note_fail "The git-identity scan found no ci/*.sh files at all, so it proved nothing."
|
||||||
elif git ls-files -z 'ci/*.sh' \
|
elif git ls-files -z 'ci/*.sh' \
|
||||||
| xargs -0 grep -nE '^[^#]*git config([[:space:]]+--[a-z-]+)*[[:space:]]+user\.'; then
|
| xargs -0 grep -nE '^[^#]*git config([[:space:]]+--(local|global|system))*[[:space:]]+user\.[a-zA-Z]+[[:space:]]+[^[:space:])]'; then
|
||||||
note_fail "A ci/ script sets a git identity that outlives it. Scope it to the one commit with 'git -c user.name=… commit', which does not write .git/config."
|
note_fail "A ci/ script sets a git identity that outlives it. Scope it to the one commit with 'git -c user.name=… commit', which does not write .git/config."
|
||||||
else
|
else
|
||||||
echo " $scripts_scanned scripts in ci/ leave the clone's git identity alone"
|
echo " $scripts_scanned scripts in ci/ leave the clone's git identity alone"
|
||||||
|
|||||||
Reference in New Issue
Block a user