A malicious git tree drives be get/be patch to write or unlink files OUTSIDE the worktree. Tree-entry names flow byte-for-byte into io.open(..,"c")/io.symlink/io.unlink with no ../absolute /empty-segment rejection, and ingest runs no fsck. One central guard in GET/PATCH's shared write sink closes it. Method: Issues.
shared/store.js:505 builds each leaf path as
prefix + "/" + e.name — e.name is raw git tree-entry bytes.
shared/util/path.js:25-27 join is pure concat: dir+"/"+name.shared/checkout.js:95-104 materialise opens join(wtRoot,rel)
via io.open(full,"c") / io.symlink with NO name check.
verbs/get/get.js:342 get-leaf → checkout.materialise(g.wt,rel..).verbs/get/get.js:328 delete-leaf → io.unlink(join(g.wt,rel)).verbs/patch/patch.js:316 theirs-side → same checkout.materialise.verbs/patch/patch.js:320 deleteLeaf → io.unlink(join(wtRoot,..))... escapes the wt; ../../.ssh/authorized_keys
is writable; ingest (shared/ingest.js) does NO fsck/name validation.
shared/stage.js:51-55 isMeta blocks .git/.be/..be.idx
segments but NOT .. (so even staging would not catch it).
""/./.. segment or an absolute leading /.checkout.materialise (covers get+patch file leaves) AND
at the producer store.js::readTreeRecursive (covers delete leaves).
stage.js::isMeta to also reject the .. segment.safeRel(rel) -> bool: /-split, scan segments.readTreeRecursive stops poisoned deletes too,
since delete leaves bypass materialise (io.unlink direct).
materialise guard is the belt-and-braces sink for both verbs... entry; assert be get
writes nothing outside wt and errors (test/js/get/traversal).
safeRel (one place) + wire into materialise + producer.isMeta to reject ..; assert stage rejects it.f9180c2e + be/test 21d8ca90 (be-js-get-traversal)... holding evil.txt
flattens to leaf path ../evil.txt; unpatched jab get wrote PWNED into $WORK/evil.txt OUTSIDE the wt; a delete leaf ../victim.txt io.unlinked an outside-wt sentinel.
safeRel(rel) added in ONE place: shared/util/path.js (split on
/, reject `/./../.git/.be/..be.idx seg, leading /`, NUL).
checkout.js::materialise (get+patch file writes),
store.js::readTreeRecursive producer throw (patch full-tree map → covers its write+delete leaves; checkout.apply delete loop), and stage.js::isMeta now also rejects a .. segment.
be/test/get/traversal/run.sh (be-js-get-traversal, glob-picked, no CMakeLists edit): malicious get ERRORS + writes/unlinks nothing above wt + clean sibling tree still checks out.
GREEN (guards in): passes; jab errors "checkout: unsafe path ../evil.txt". Full be/ ctest 74/74 green (sole red is the pre-existing stale be-js-bro-universal WILL_FAIL xfail).