Repository navigation
push check: commit-only range ends, glob ? for astral characters, refuse_paths in NFC and NFD (#966) - #1123
Conversation
#966) globToRegExp read ? as one UTF-16 unit, so keys/?.pem missed a name with one emoji in that slot. ? now matches a surrogate pair or one unit: a superset of the old matches, so no protected, refused or category glob matches less. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
A tree, a blob or a tag of either as a range end is refused since #981's ^{commit} peel; before it a tree head read as a range with no names. judge now takes a CommitId only commitIds makes, so no caller can hand it a name or a tree id, and a test pins the refusal for each kind of end. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
A name committed decomposed (e + combining accent) got past a refuse glob written composed, and the other way round; on Linux these are two files. Each path is matched in its own, NFC and NFD spellings against each spelling of the glob: only ever wider. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
…ws (#966) Round 1 widened ? for every caller, which also widened allow-style globs (secret ! exceptions, docs and test exemptions) and made the money list's comment-cut cover test read narrower than main (R123a); read-denials kept its own matcher with the old ?, so it no longer agreed with the hook (R123b). globRegExp(glob, style) and globMatcher(glob, style), from one parser: deny matches when the one-unit or the one-character reading does (never less than before), allow only when both do (never more). A glob without ? compiles as before. read-denials uses globMatcher; the hook, push check, review levels, money list and the other callers each pick a style. globToRegExp stays the pre-#966 reading for what an earlier version counted (a money glob's before, kept readings) and for engine.ts and the vacuity files, which other open PRs own; a test lists the files that may still call it. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
… per caller (#966) Two mutants survived round 1's tests (R123a, R123b): spellings without NFD, and globs matched as written. A glob in mixed spellings and secrets/cafe* against an NFC name now fail each. The changelog names each caller's style and the matchers still read as before. Signed-off-by: Jayden Vasquez <jaydenvasquez1711@gmail.com>
|
Independent final check (R123a), posted by the coordinator. VERDICT: FAIL @e61eea7fa47e11d4c5948ca75a2037722d39da4e (final check) Reviewer R123a. Head checked at start and end ( Blocking findingThe
The brute probe (
Claims verified (all measured unless marked)
Weakening scan flags
Follow-ups (not blocking)
|
|
Independent adversarial review (R123b), posted by the coordinator. VERDICT: FAIL @e61eea7fa47e11d4c5948ca75a2037722d39da4e (adversarial) R123b, PR #1123 (#966). Head e61eea7 confirmed at start and end ( FAIL reason (bar a/b: overclaim)The changelog says
Other findings (not blocking)
Weakening scan flags
Follow-upsAstral |
|
Independent final check (R123c), posted by the coordinator. VERDICT: PASS @691ed1c8f34d308c5a9f5af3cc23bf133f4d04a2 (final check) Reviewer R123c, PR #1123 (Fixes #966). Head confirmed at start and end ( Findings
Weakening scanRun with main's engine:
Follow-ups (not blocking)
|
|
Independent adversarial review (R123d), posted by the coordinator. VERDICT: PASS @691ed1c8f34d308c5a9f5af3cc23bf133f4d04a2 (adversarial) Reviewer R123d, PR #1123 (#966), round 2. I checked the head with Findings
Mutants (measured,
|
|
Weakening scan decision for head The owner's instruction (verbatim): "Stp asking me quesgtions and just take recommended option each time". This PR is in the critical set (the push check and the guardrails' glob compiler). Both independent reviews of this exact head passed: the final check (R123c) and the adversarial review (R123d). Both classified each of the scan's flags as a strengthening or a false positive; neither found a real weakening. R123d's exhaustive probe of about 130M glob/path pairs found no case where a deny-style glob matches less than main or an allow-style glob matches more (measured). The weakening scan reports 5 reasons (measured, R123c and R123d, main's engine):
Decision: proceed (recommended option). This covers only this head; any new push needs a fresh scan and review. |
Fixes #966
Critical set: yes.
src/push-check.ts(the push guard),src/guardrails/glob.ts(the guardrails) andtest/push-check.test.tsare locked as self-modification insrc/review.ts(SELF_MODIFICATION_PARTSand the locked test list);test/push-check.test.tsis also in.worklane/review.yaml(measured:rg -n "push-check|guardrails/" src/review.ts .worklane/review.yaml). Two independent reviews are needed.What changed
Three commits, one per item of the issue.
ef7681d2). Main already refuses a tree, blob or tag-of-a-tree end since push check: refuse, never throw, when git fails mid-read; read and push by commit id (#981) #1088's^{commit}peel incommitIds(measured, below). This commit makes the rule structural:judgenow takes a brandedCommitIdthat onlycommitIdsproduces, so no later caller can hand it a name or a tree id (type-level; inferred fromnpm run typecheckpassing). A new test pins the refusal for a tree head, a blob head, a tag-of-a-tree head, a tree base and a blob base, and checks thatcheckedPushpushes nothing.?and astral characters (78a93f37). InglobToRegExp,?now compiles to(?:[\uD800-\uDBFF][\uDC00-\uDFFF]|[^/]): one surrogate pair or one UTF-16 unit. Nouflag is added, so other regex semantics stay as they were for every caller. Because the new regex is a superset of the old[^/], no glob matches fewer paths:??still matches a one-emoji name.e61eea7f). The push check matches each touched path through onematcher. It tries the path as written, in NFC and in NFD against each glob in those same three spellings. This only ever widens what is refused, and the issue's pending case-fold handling (Push gate routes on main: log config, crafted tree entries, replace refs in the size check, skill dir symlinks, case folding #951 item 5) can be added in the samespellingsfunction. A changelog fragment is atchangelog.d/966-push-check-range-glob-unicode.md.Review of every
globToRegExpcaller for the?change (inferred, from reading the code)guardrails/engine.ts, the review categories, self-modification and money globs inreview.ts, vacuity test/app globs, weakening, hotspots, owners, chat, browser gate, pr-rules docs/lockfile/generated, and push refuse_paths. Each sees only a superset of its old matches.?meets a character outside the BMP. That is what?is documented to mean. No built-in glob list insrc/contains a?(measured withrgfor quoted glob strings holding?in the guardrails, review, pr-rules, vacuity, weakening, hotspots, chat, browser gate, coordinator, config and push-check sources). So only project-configured globs see the difference, plus the globsreview.tsgenerates from a project's money regexes, where[^/]becomes?. A wider money glob means more money paths, which is stricter, and the money-list suites passed in the full run (measured).review.tscomparesre.source === globToRegExp(re.glob).sourceand keys bysource/flags. Both sides are built by the same function, so both stay consistent.guardrails/access.tssplicesglobToRegExp(s).source.slice(1, -1)intonew RegExp(..., 's'). The flags are the same and the^/$it strips are unchanged.src/confusables.tshas its own glob matcher (globParts), whose?is still one code unit. It is a separate matcher in the review levels and isn't named by this issue, so it is left as a follow-up.Evidence
Fail before, on origin/main
246e3f68with this PR's two test files copied in (measured:npx tsc -p . && node --import ./dist/test/isolate.js --test dist/test/glob.test.js dist/test/push-check.test.jsin a detached worktree):For item 1, the same run on
d5650da8(main before #1088) fails the new tree/blob test. The push check passed a tree head with no names read, and only the remote refused it (measured):"remote: error: cannot update ref 'refs/heads/task': trying to write non-commit object ...". On main that test passes, because #1088 already refuses such an end (measured, same command).Pass after, on this branch (measured, same command):
# pass 27,# fail 0. This includes the no-narrowing test, which compares the old and new?on 13 globs over about 22k generated paths (emoji, lone surrogates, NFC and NFD accents, slashes). Every path the old regex matched, the new one matches.Other checks (all measured):
npm run typecheck: exit 0.npm run check:denylist:denylist: 1693 files clean.node scripts/push-gate.mjs --range origin/main..HEAD:push gate: 3 commit(s) and 1 tip(s) clean.flock /var/tmp/wl-coord/fulltest.lock nice -n 15 ionice -c3 npm test: 6037 tests, 5924 pass, 18 fail, 69 skipped, 26 todo.machine-sudoers-*(13 in closed, one each in narrow and removal), plus one each incoordinator(worktree dependency cache) anddesktop-removed(round 19 packages). On main those files gave 117 pass, 17 fail, the same tests (measured).discord-seam-ci) was a port collision (EADDRINUSE 127.0.0.1:46009). It passed when rerun alone (measured:# pass 1).Not done / follow-ups
spellingsis where it would go.guardrails/engine.ts, which has another owner, still compare spellings as written. Look-alike spellings at review time are covered separately by the confusable-path skeleton inreview.ts(inferred).confusables.tsglobParts: its?is still one UTF-16 unit (inferred from reading the code).Round 2 (head 691ed1c): answers R123a and R123b
This replaces round 1's item 2 description and its "Review of every caller" section, which were wrong. Round 1 widened
?for every caller. R123a measured that this made one money-list reading narrower than main. R123b found that it also widened allow-style globs and leftread-denialsdisagreeing with the hook. Critical set: yes, still. Every changedsrc/file and the locked push-check test are as before, and the PR now also touchessrc/guardrails/access.ts,src/read-denials.tsandsrc/review.ts.What changed
src/guardrails/glob.ts). One parser builds two readings of?: one UTF-16 unit (main) and one character (exactly what theuflag gives, written without it). From them comeglobRegExp(glob, style)and the linear-timeglobMatcher(glob, style), wherestyleis required and is'deny'or'allow'.?) compiles to the same source as main's.read-denials' own matcher is deleted. It now usesglobMatcher. Its positive patterns are deny and its!exceptions are allow, as in the hook (access.ts: secret globs and search filters are deny,!exceptions are allow).!exceptions, docs/tests/harness-config when they exempt a path from the app code, docs-only, lockfile/generated skips, experiment editable paths, and the money-list cover test (R123a's fix).before: the new glob test shows a path in the money set that isn't inmoneyBefore. So a widened money glob can't take a large change out of the large-change L2.keep(...): main's kept readings.globToRegExp, now documented as the pre-push check: refuse a non-commit range end; glob ? and astral characters; NFC/NFD #966 reading.globToRegExpis a closed list. Anever-growtable intest/glob.test.tsnames the only files that may still call it:review.ts(the uses just above), andguardrails/engine.ts,merge-policy-vacuity.tsandvacuity.ts. The last three are owned by guardrails: refuse more spellings git reads as a hook-skipping push (#993) [critical: tool guard] #1101 and fix(vacuity): pair moved code git doesn't pair, only ever taking measure away (#1014) #1068, and they read exactly as on main (follow-up). A new caller elsewhere fails that test.u-flag reading).secrets/cafe*against an NFC name.engine.ts, the vacuity files,confusables.ts).Fail before (on round 1's head e61eea7), measured
npx tsc -p . && node --import ./dist/test/isolate.js --test dist/test/glob-styles.test.js dist/test/read-denials-matcher.test.js, in a detached worktree with this round'stest/glob-styles.test.tscopied in. For the Sandbox: a task's hidden paths and the project's secret paths are denied by the OS sandbox, not only by the guard hook #890 file, e61eea7's own test was used with onlykeys/?.pem,**/?,?/**,[ab]?and four astral paths added to its corpora.billing/**/? # see /🔑;beforereading;!keys/?.pemexception;?lowering a level ('L0' !== 'L1')."keys/?.pem" on "keys/🔑.pem", so that test would have caught item 2.Pass after (head 691ed1c), measured
node --import ./dist/test/isolate.js --test dist/test/glob.test.js dist/test/glob-styles.test.js dist/test/read-denials-matcher.test.js dist/test/push-check.test.js: 39 pass, 0 fail.dist, then the files above rerun), all caught:spellingswithout NFD: 1 fail;globRegExpalways deny: 7 fail;globMatcheralways deny: 3 fail.--test-timeout=180000): 775 pass, 2 fail, plus the adversarial file timing out at 180 s under that load.coordinator(worktree dependency cache) anddesktop-removed(round 19 packages). Both fail on origin/main in this environment too (measured in round 1).adversarial.test.jsalone, with--test-timeout=900000: 269 pass, 0 fail, in 11m46s.npm testwasn't rerun this round, because the brief allows one; CI is authoritative.npm run typecheck: exit 0.npm run check:denylist: 1695 files clean.node scripts/push-gate.mjs --range origin/main..HEAD: 5 commits and 1 tip clean.git merge-tree --write-tree:push-check.ts,coordinator.tsand others, and this PR addsread-denials.tsto that overlap.Weakening scan (inferred unless marked)
engine.tsand the vacuity files call the unchanged pre-push check: refuse a non-commit range end; glob ? and astral characters; NFC/NFD #966 reading.access.tsshell-word filter splices each segment's deny source. That is a per-segment union, a superset of the whole-glob union, so it is still only wider for a filter whose match refuses.Follow-ups
guardrails/engine.ts(protected, agent-editable and AGENTS.md paths) and the vacuity files to a style once guardrails: refuse more spellings git reads as a hook-skipping push (#993) [critical: tool guard] #1101 and fix(vacuity): pair moved code git doesn't pair, only ever taking measure away (#1014) #1068 land.confusables.ts' look-alike glob matcher still reads?as one unit.