mirror of
https://github.com/saymrwulf/betrusted-ed25519-verified.git
synced 2026-09-04 20:24:08 +00:00
lift-guard: eleven more classes, two of them regressions I introduced
Round-9 review (Claude, N1). The brief said "assume there are more"; there
were eleven, and two were introduced by the round-8 fix itself.
INTRODUCED BY THE ARITHMETIC TOKENISATION — the round-8 fix for a false
NEGATIVE created two false POSITIVES. The interior of `$(( ))` was tokenised
with `[A-Za-z_][A-Za-z0-9_]*`, which starts matching at the letter-bearing tail
of a numeric literal:
echo $((0x1F)) -> FATAL: reads x1F
echo $((1e3)) -> FATAL: reads e3
Now anchored so a match cannot begin after a digit or word character.
INTRODUCED BY THE INDIRECT-EXPANSION REFUSAL, and this is the one that matters.
`${!...}` has three meanings and `re.search(r'\$\{!')` cannot tell them apart:
${!name} indirect expansion — genuinely unanalysable
${!arr[@]} ${!arr[*]} array KEY expansion — ordinary
${!prefix*} ${!prefix@} variable-NAME listing — ordinary
A refusal is the most expensive verdict this tool has — it hard-fails the lift
— and it was firing on two ordinary constructs with a diagnostic naming a
feature they do not use. The reviewer found it LIVE: ltl-accumulator
check.sh:274 is `for cert in "${!CONES[@]}"`, so the day lift-guard is ported
there, any lift covering that line would have refused. The four forks carry
five arrays each, so it was one ordinary edit away from firing there too.
Now matched only for genuine `${!name}`.
SEVEN MORE BINDING FORMS the driver uses and the guard demanded anyway:
let FOO=1 · (( FOO = 1 )) · BAR+=b · FOO[0]=x
for (( i=0; i<3; i++ )) · select FOO in · getopts "o" FOO
Arithmetic contexts bind as well as read, so `(( i++ ))` and the C-style `for`
now contribute to assigns — without that, the reads-extraction added by the
round-8 fix demanded the very names those expressions assign.
Verified: all eleven silent, genuine `${!n}` still refuses, `$((X+1))` and
`((Y>0))` still caught by name, the whole round-8 matrix unchanged, a genuine
missing variable still fails, and the four lifting self-tests green in all four
forks.
The reviewer also discarded one candidate rather than report it — `i=0;
(( i++ ))` looks like a demand but is silent under driver ⊇ payload, which is
how the self-tests invoke it. That restraint is worth recording: a guard edited
twice for false alarms should not be handed a false alarm by its reviewer.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
32a21f7d9f
commit
3910e7c35b
2 changed files with 38 additions and 3 deletions
|
|
@ -8,7 +8,7 @@ b9c907f94572c06a59135d29377d51ad8efc3f74402ac61e8644a15786d4b0ed inventory-allo
|
||||||
86ee83b703d17c1f04af654657219b344b0076bc994c0b791ca6b6c5a0090d4f inventory-allowlist.txt
|
86ee83b703d17c1f04af654657219b344b0076bc994c0b791ca6b6c5a0090d4f inventory-allowlist.txt
|
||||||
3ebc8027f14c9e037f36322ef4119183c33214658efcc1a7bc985a98a9c32e4e inventory_gate.sh
|
3ebc8027f14c9e037f36322ef4119183c33214658efcc1a7bc985a98a9c32e4e inventory_gate.sh
|
||||||
736ea4be712e1b5bcda10ecb466f0dec7008a2a36eabdfd77563976299c43cce lean-guard
|
736ea4be712e1b5bcda10ecb466f0dec7008a2a36eabdfd77563976299c43cce lean-guard
|
||||||
2b78361105984aa8e859758849a0886b5b768766fe9bb39dd04aa8ee24d38b6c lift-guard.sh
|
cad0ae17ce506e1defeab3b9799993b3a2251c055247695832ecae2c2617772f lift-guard.sh
|
||||||
1942177f13d6ae229d87a3b0b33f7fbb4b2ae20fe1059cc83010e73f6a156427 model-correspondence.py
|
1942177f13d6ae229d87a3b0b33f7fbb4b2ae20fe1059cc83010e73f6a156427 model-correspondence.py
|
||||||
77e356f607c01ff597193f28f60b48a701ca2b9597cc9a991ac5b27b00aeaf81 MODEL-CORRESPONDENCE.txt
|
77e356f607c01ff597193f28f60b48a701ca2b9597cc9a991ac5b27b00aeaf81 MODEL-CORRESPONDENCE.txt
|
||||||
772ca6dd22443c83dc35d5428598c8d17a01c69db5be008474d06476fa66f7f8 Proofs/Audit.lean
|
772ca6dd22443c83dc35d5428598c8d17a01c69db5be008474d06476fa66f7f8 Proofs/Audit.lean
|
||||||
|
|
|
||||||
|
|
@ -90,7 +90,13 @@ reads = set(re.findall(r'\$\{?([A-Za-z_][A-Za-z0-9_]*)', payload))
|
||||||
# check.sh's Phase 1b. Not lifted today, which made it latent, not absent.
|
# check.sh's Phase 1b. Not lifted today, which made it latent, not absent.
|
||||||
for expr in (re.findall(r'\$\(\((.*?)\)\)', payload, re.S)
|
for expr in (re.findall(r'\$\(\((.*?)\)\)', payload, re.S)
|
||||||
+ re.findall(r'(?<!\$)\(\((.*?)\)\)', payload, re.S)):
|
+ re.findall(r'(?<!\$)\(\((.*?)\)\)', payload, re.S)):
|
||||||
for tok in re.findall(r'[A-Za-z_][A-Za-z0-9_]*', expr):
|
# NOT PRECEDED BY A DIGIT OR WORD CHARACTER. Round-9 review (Claude, N1):
|
||||||
|
# `$((0x1F))` was read as a variable `x1F`, and `$((1e3))` as `e3`, because
|
||||||
|
# the pattern happily starts matching at the letter-bearing tail of a
|
||||||
|
# numeric literal. Two false alarms introduced by the round-8 fix for a
|
||||||
|
# false NEGATIVE — the guard was made to see more and started seeing things
|
||||||
|
# that are not there, which is the failure mode that gets a guard deleted.
|
||||||
|
for tok in re.findall(r'(?<![0-9A-Za-z_])[A-Za-z_][A-Za-z0-9_]*', expr):
|
||||||
reads.add(tok)
|
reads.add(tok)
|
||||||
|
|
||||||
# What the DRIVER defines, in every form these scripts actually use.
|
# What the DRIVER defines, in every form these scripts actually use.
|
||||||
|
|
@ -108,6 +114,23 @@ assigns |= set(re.findall(
|
||||||
r'\b(?:mapfile|readarray)\b(?:\s+-[A-Za-z]\s*\S*)*\s+([A-Za-z_][A-Za-z0-9_]*)',
|
r'\b(?:mapfile|readarray)\b(?:\s+-[A-Za-z]\s*\S*)*\s+([A-Za-z_][A-Za-z0-9_]*)',
|
||||||
driver))
|
driver))
|
||||||
assigns |= set(re.findall(r'\bprintf\b[^\n]*?\s-v\s+([A-Za-z_][A-Za-z0-9_]*)', driver))
|
assigns |= set(re.findall(r'\bprintf\b[^\n]*?\s-v\s+([A-Za-z_][A-Za-z0-9_]*)', driver))
|
||||||
|
# SEVEN MORE BINDING FORMS — round-9 review (Claude, N1). Each was a false
|
||||||
|
# alarm: the driver binds the name and the guard demanded it anyway. Listed in
|
||||||
|
# the order reported, so the next reader can check the list against that report.
|
||||||
|
assigns |= set(re.findall(r'\blet\s+([A-Za-z_][A-Za-z0-9_]*)\s*=', driver))
|
||||||
|
assigns |= set(re.findall(r'\bselect\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\b', driver))
|
||||||
|
assigns |= set(re.findall(r'\bgetopts\b\s+\S+\s+([A-Za-z_][A-Za-z0-9_]*)', driver))
|
||||||
|
assigns |= set(re.findall(r'(?:^|;|&&|\|\||\)|!|\bthen\b|\bdo\b|\belse\b|\{)\s*'
|
||||||
|
r'([A-Za-z_][A-Za-z0-9_]*)\+=', driver, re.M)) # BAR+=b
|
||||||
|
assigns |= set(re.findall(r'(?:^|;|&&|\|\||\)|!|\bthen\b|\bdo\b|\belse\b|\{)\s*'
|
||||||
|
r'([A-Za-z_][A-Za-z0-9_]*)\[[^]]*\]=', driver, re.M)) # FOO[0]=x
|
||||||
|
# Arithmetic CONTEXTS BIND TOO — `(( FOO = 1 ))`, `(( i++ ))`, and the C-style
|
||||||
|
# `for (( i=0; i<3; i++ ))`. The reads-extraction above adds every identifier it
|
||||||
|
# finds inside `(( ))`, so without this the guard demands the very names those
|
||||||
|
# expressions assign.
|
||||||
|
for expr in (re.findall(r'\$\(\((.*?)\)\)', driver, re.S)
|
||||||
|
+ re.findall(r'(?<!\$)\(\((.*?)\)\)', driver, re.S)):
|
||||||
|
assigns |= set(re.findall(r'(?<![0-9A-Za-z_])([A-Za-z_][A-Za-z0-9_]*)\s*(?:=[^=]|\+\+|--)', expr))
|
||||||
assigns |= set(re.findall(r'\b(?:export|declare|local|readonly)\s+(?:-\w+\s+)*'
|
assigns |= set(re.findall(r'\b(?:export|declare|local|readonly)\s+(?:-\w+\s+)*'
|
||||||
r'([A-Za-z_][A-Za-z0-9_]*)', driver))
|
r'([A-Za-z_][A-Za-z0-9_]*)', driver))
|
||||||
assigns |= set(re.findall(r'\bfor\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\b', driver))
|
assigns |= set(re.findall(r'\bfor\s+([A-Za-z_][A-Za-z0-9_]*)\s+in\b', driver))
|
||||||
|
|
@ -131,7 +154,19 @@ ENV = {'PWD', 'HOME', 'PATH', 'IFS', 'PIPESTATUS', 'BASH_SOURCE', 'FUNCNAME',
|
||||||
# silent. `n=Q; echo "${!n}"` reads Q, and no amount of pattern-matching
|
# silent. `n=Q; echo "${!n}"` reads Q, and no amount of pattern-matching
|
||||||
# recovers that from the source. The guard's contract is that it does not miss
|
# recovers that from the source. The guard's contract is that it does not miss
|
||||||
# a dependency; where it cannot honour that it must refuse, not shrug.
|
# a dependency; where it cannot honour that it must refuse, not shrug.
|
||||||
if re.search(r'\$\{!', payload):
|
# `${!...}` HAS THREE MEANINGS IN BASH and only one of them is indirection:
|
||||||
|
# ${!name} indirect expansion — genuinely unanalysable
|
||||||
|
# ${!arr[@]} ${!arr[*]} array KEY expansion — ordinary, and LIVE at
|
||||||
|
# ltl-accumulator check.sh:274, `for cert in
|
||||||
|
# "${!CONES[@]}"`
|
||||||
|
# ${!prefix*} ${!prefix@} variable-NAME listing — ordinary
|
||||||
|
# Round-9 review (Claude, N1). The round-8 refusal tested for `${!` and could
|
||||||
|
# not tell them apart, so a legitimate construct would have hard-failed a lift
|
||||||
|
# with a diagnostic naming a feature it does not use. A refusal is the most
|
||||||
|
# expensive verdict this tool has; it must be reserved for the case it is
|
||||||
|
# actually about.
|
||||||
|
INDIRECT = re.compile(r'\$\{!\s*[A-Za-z_][A-Za-z0-9_]*\s*\}')
|
||||||
|
if INDIRECT.search(payload):
|
||||||
print('INDIRECT-EXPANSION')
|
print('INDIRECT-EXPANSION')
|
||||||
else:
|
else:
|
||||||
print(' '.join(sorted(n for n in reads - assigns - ENV if not n.isdigit())))
|
print(' '.join(sorted(n for n in reads - assigns - ENV if not n.isdigit())))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue