mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-26 22:51:47 +02:00
Three shapes still exited 0 with a credential in the push. A push to a URL borrowed origin's tip as its base and excluded every commit any remote had seen, so a credential already on origin shipped to a new, empty URL remote unscanned. A URL is described by no remote-tracking ref: the push target is now a three-way value (configured remote / URL / unknown), a URL target anchors on nothing and excludes nothing, and an unknown target keeps the historical origin-shaped behavior. When git named a remote tip the local clone lacks, only an EMPTY guessed range was blocked; a stale tracking ref holding the credential plus one harmless local commit produced a non-empty, credential-free guess that passed. An absent named tip now gets no local narrowing at all and the whole reachable history is scanned, which is affordable because every scan is sliced under the engine's cap. The inter-slice overlap carried whole lines only, so a line longer than the overlap contributed nothing and a label at its end never met the secret at the start of the next slice. The overlap now carries such a line's suffix. Gate: test/redact-prepush-fail-open.sh grows to 30 scenarios. E5 expected the URL-push leak to be ALLOWED and now expects the block, E5b pins that a clean URL push is still allowed, 19 and 20 pin the other two shapes, and E8b pins that a clean SHA-256 first push is allowed (E8 carried a credential, so it could not tell a working empty-tree fix from a broken one). The S1 URL-push case in redact-prepush-scan-range.test.ts keeps its no-error assertion and now also asserts the block, because its fixture's secret really is delivered to that URL. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
466 lines
19 KiB
Bash
Executable File
466 lines
19 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# test/redact-prepush-fail-open.sh — regression gate for pre-push credential
|
|
# redaction fail-open paths and adjacent range-resolution invariants.
|
|
#
|
|
# Usage:
|
|
# bash test/redact-prepush-fail-open.sh [scanner-path]
|
|
#
|
|
# Defaults to the in-tree scanner at bin/gstack-redact-prepush.
|
|
#
|
|
# Every fixture credential below is synthetic: assembled at runtime from
|
|
# halves that match nothing on their own. None is, or ever was, a live secret.
|
|
set -uo pipefail
|
|
|
|
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
SCANNER="${1:-$REPO_ROOT/bin/gstack-redact-prepush}"
|
|
|
|
command -v bun >/dev/null 2>&1 || { echo "bun not found"; exit 2; }
|
|
command -v git >/dev/null 2>&1 || { echo "git not found"; exit 2; }
|
|
command -v python3 >/dev/null 2>&1 || { echo "python3 not found"; exit 2; }
|
|
[ -f "$SCANNER" ] || { echo "no scanner at $SCANNER"; exit 2; }
|
|
|
|
_K1='AKIA'; _K2='3QZ7YB2LKWVNPCJD'; KEY="${_K1}${_K2}"
|
|
_P1='-----BEGIN '; _P2='RSA PRIVATE KEY'; _P3='-----'
|
|
PEM_BEGIN="${_P1}${_P2}${_P3}"; PEM_END="${_P3}END ${_P2}${_P3}"
|
|
_S1='wJalrXUtnFEMI/K7MDENG'; _S2='/bPxRfiCYzQ9v2tKzXq'; SEC="${_S1}${_S2}"; export SEC
|
|
ZERO40=0000000000000000000000000000000000000000
|
|
|
|
WORK=$(mktemp -d -t prepush-gate-XXXXXX)
|
|
trap 'rm -rf "$WORK"' EXIT
|
|
|
|
STAGE="$WORK/stage"
|
|
mkdir -p "$STAGE/bin"
|
|
ln -s "$REPO_ROOT/lib" "$STAGE/lib"
|
|
cp "$SCANNER" "$STAGE/bin/cand.ts"
|
|
CAND="$STAGE/bin/cand.ts"
|
|
|
|
WINDOW=$(grep -oE 'const (WINDOW_BYTES|SCAN_CHUNK_BYTES) = [0-9]+ \* 1024' "$CAND" \
|
|
| grep -oE '[0-9]+ \* 1024' | head -1 | awk '{print $1*1024}')
|
|
: "${WINDOW:=786432}"
|
|
|
|
R="$WORK/repos"
|
|
mkdir -p "$R"
|
|
|
|
mkrepo() {
|
|
local d="$R/$1"
|
|
rm -rf "$d"
|
|
mkdir -p "$d"
|
|
cd "$d" || exit 1
|
|
git init -q .
|
|
git config user.email t@t.t
|
|
git config user.name t
|
|
git checkout -q -b probe
|
|
}
|
|
|
|
run_probe() {
|
|
cd "$1" || exit 1
|
|
local sha
|
|
sha=$(git rev-parse HEAD)
|
|
printf 'refs/heads/probe %s refs/heads/probe %s\n' "$sha" "${2:-$ZERO40}" \
|
|
| bun "$CAND" 2>&1
|
|
echo "___EXIT:$?"
|
|
}
|
|
|
|
verdict() {
|
|
local out="$1" rc id
|
|
rc=$(printf '%s' "$out" | sed -n 's/^___EXIT:\(.*\)$/\1/p' | tail -1)
|
|
[ "$rc" = "0" ] && { echo "ALLOW"; return; }
|
|
id=$(printf '%s' "$out" | grep -oE 'HIGH [a-z0-9._]+' | head -1 | awk '{print $2}')
|
|
if [ -z "$id" ]; then
|
|
id=$(printf '%s' "$out" | grep -oE '^ engine\.[a-z_]+' | head -1 | tr -d ' ')
|
|
fi
|
|
if [ -n "$id" ]; then
|
|
echo "BLOCK($id)"
|
|
else
|
|
echo "BLOCK(unscannable)"
|
|
fi
|
|
}
|
|
|
|
TOTAL=0
|
|
FAILURES=0
|
|
|
|
row() {
|
|
local name="$1" out="$2" want="$3"
|
|
TOTAL=$((TOTAL + 1))
|
|
local got
|
|
got=$(verdict "$out")
|
|
local mark="ok"
|
|
if [ "$want" = "BLOCK" ]; then
|
|
case "$got" in
|
|
BLOCK*) ;;
|
|
*) mark="FAIL (want $want)"; FAILURES=$((FAILURES + 1)) ;;
|
|
esac
|
|
elif [ "$got" != "$want" ]; then
|
|
mark="FAIL (want $want)"
|
|
FAILURES=$((FAILURES + 1))
|
|
fi
|
|
printf '%-48s %-26s %-26s %s\n' "$name" "$want" "$got" "$mark"
|
|
}
|
|
|
|
printf '\n%-48s %-26s %-26s %s\n' "SCENARIO" "EXPECTED" "ACTUAL" "GATE"
|
|
printf -- '-%.0s' {1..110}; printf '\n'
|
|
|
|
# 1: 2 MiB innocuous
|
|
mkrepo s1; python3 -c "
|
|
with open('d.json','w') as f:
|
|
f.write('{\n')
|
|
for i in range(60000): f.write(' \"prop%07d\": {\"n\": %d},\n' % (i, i*7919%999983))
|
|
f.write(' \"end\": 1\n}\n')"
|
|
git add -A >/dev/null; git commit -qm big
|
|
S1=$(run_probe "$R/s1")
|
|
row "1 clean 2 MiB, no credential" "$S1" "ALLOW"
|
|
|
|
# 2: small + AWS key
|
|
mkrepo s2; printf 'cfg = 1\naws_key = "%s"\n' "$KEY" > app.py
|
|
git add -A >/dev/null; git commit -qm secret
|
|
S2=$(run_probe "$R/s2")
|
|
row "2 small + AWS key" "$S2" "BLOCK(aws.access_key)"
|
|
|
|
# 3: key buried in 3 MiB
|
|
mkrepo s3; python3 -c "
|
|
k='$KEY'
|
|
with open('d.json','w') as f:
|
|
f.write('{\n')
|
|
for i in range(45000): f.write(' \"prop%07d\": {\"n\": %d},\n' % (i, i*7919%999983))
|
|
f.write(' \"aws_key\": \"%s\",\n' % k)
|
|
for i in range(45000): f.write(' \"item%07d\": {\"n\": %d},\n' % (i, i*104729%999983))
|
|
f.write(' \"end\": 1\n}\n')"
|
|
git add -A >/dev/null; git commit -qm buried
|
|
S3=$(run_probe "$R/s3")
|
|
row "3 AWS key buried in 3 MiB" "$S3" "BLOCK(aws.access_key)"
|
|
|
|
# 4: PEM straddling the cut
|
|
mkrepo s4; PEM_BEGIN="$PEM_BEGIN" PEM_END="$PEM_END" python3 -c "
|
|
import base64, os
|
|
W=$WINDOW; line='f'*40; n=(W-500)//41
|
|
body='\n'.join(base64.b64encode(bytes((i*37+j)%256 for i in range(48))).decode() for j in range(25))
|
|
with open('d.txt','w') as f:
|
|
for i in range(n): f.write(line+'\n')
|
|
f.write(os.environ['PEM_BEGIN']+'\n'+body+'\n'+os.environ['PEM_END']+'\n')
|
|
for i in range(2000): f.write('t'*40+'\n')"
|
|
git add -A >/dev/null; git commit -qm pem
|
|
S4=$(run_probe "$R/s4")
|
|
row "4 PEM straddling window cut" "$S4" "BLOCK(pem.private_key)"
|
|
|
|
# 5: key exactly at the cut
|
|
mkrepo s5; python3 -c "
|
|
k='$KEY'; W=$WINDOW; line='f'*40; n=(W-60)//41
|
|
with open('d.txt','w') as f:
|
|
for i in range(n): f.write(line+'\n')
|
|
f.write('aws_key = \"%s\"\n' % k)
|
|
for i in range(2000): f.write('t'*40+'\n')"
|
|
git add -A >/dev/null; git commit -qm atcut
|
|
S5=$(run_probe "$R/s5")
|
|
row "5 AWS key exactly at window cut" "$S5" "BLOCK(aws.access_key)"
|
|
|
|
# 6: bogus local sha -> range unreadable -> must fail closed
|
|
mkrepo s6; echo hi > a.txt; git add -A >/dev/null; git commit -qm init
|
|
run_badlocal() { cd "$1" || exit 1
|
|
printf 'refs/heads/probe deadbeefdeadbeefdeadbeefdeadbeefdeadbeef refs/heads/probe 0000000000000000000000000000000000000000\n' \
|
|
| bun "$CAND" 2>&1; echo "___EXIT:$?"; }
|
|
S6=$(run_badlocal "$R/s6")
|
|
row "6 unreadable range (fail-closed)" "$S6" "BLOCK(unscannable)"
|
|
|
|
# 7: credential on a line whose own content starts with "++"
|
|
mkrepo s7; printf 'harmless\n++ aws_key = "%s"\n' "$KEY" > notes.patch
|
|
git add -A >/dev/null; git commit -qm plusplus
|
|
S7=$(run_probe "$R/s7")
|
|
row "7 credential on a '++...' line" "$S7" "BLOCK(aws.access_key)"
|
|
|
|
# 8: malformed stdin
|
|
run_malformed() { cd "$1" || exit 1
|
|
printf 'refs/heads/probe\n' | bun "$CAND" 2>&1; echo "___EXIT:$?"; }
|
|
mkrepo s8; echo hi > a.txt; git add -A >/dev/null; git commit -qm init
|
|
S8=$(run_malformed "$R/s8")
|
|
row "8 malformed stdin (fail-closed)" "$S8" "BLOCK(unscannable)"
|
|
|
|
# 9: gcp key spanning the cut
|
|
mkrepo s9; PEM_BEGIN="$PEM_BEGIN" python3 -c "
|
|
import os
|
|
W=$WINDOW; line='f'*40; n=(W-20)//41
|
|
with open('sa.json','w') as f:
|
|
for i in range(n): f.write(line+'\n')
|
|
f.write('\"private_key\"\n')
|
|
f.write(': \"'+os.environ['PEM_BEGIN']+'\n')
|
|
for i in range(2000): f.write('t'*40+'\n')"
|
|
git add -A >/dev/null; git commit -qm gcpsplit
|
|
S9=$(run_probe "$R/s9")
|
|
row "9 gcp key spanning the window cut" "$S9" "BLOCK(pem.private_key)"
|
|
|
|
# 10: diff.external replaces the diff
|
|
mkrepo s10; printf 'cfg = 1\naws_key = "%s"\n' "$KEY" > app.py
|
|
git add -A >/dev/null; git commit -qm extdiff
|
|
git config diff.external /bin/echo
|
|
S10=$(run_probe "$R/s10")
|
|
row "10 diff.external set + real key" "$S10" "BLOCK(aws.access_key)"
|
|
|
|
# 11: 2 MiB single minified line carrying a credential
|
|
mkrepo s11; KEY="$KEY" python3 -c "
|
|
import os
|
|
k=os.environ['KEY']
|
|
with open('bundle.min.js','w') as f:
|
|
f.write('var d={'+','.join('\"k%05d\":%d'%(i,i) for i in range(90000))+',\"aws_key\":\"'+k+'\"};')"
|
|
git add -A >/dev/null; git commit -qm minified
|
|
S11=$(run_probe "$R/s11")
|
|
row "11 credential in a 2 MiB one-liner" "$S11" "BLOCK"
|
|
|
|
# 12: push to a remote that is NOT origin
|
|
run_remote() { cd "$1" || exit 1; local sha; sha=$(git rev-parse HEAD)
|
|
printf 'refs/heads/main %s refs/heads/main 0000000000000000000000000000000000000000\n' "$sha" \
|
|
| bun "$CAND" publish https://example.invalid/publish.git 2>&1; echo "___EXIT:$?"; }
|
|
mkrepo s12; git branch -m main 2>/dev/null || true
|
|
printf 'cfg = 1\naws_key = "%s"\n' "$KEY" > app.py
|
|
git add -A >/dev/null; git commit -qm seed
|
|
git update-ref refs/remotes/origin/main HEAD
|
|
git remote add origin https://example.invalid/origin.git
|
|
git remote add publish https://example.invalid/publish.git
|
|
S12=$(run_remote "$R/s12")
|
|
row "12 push to a non-origin remote" "$S12" "BLOCK(aws.access_key)"
|
|
|
|
# 13: 4 fields, valid local sha, junk REMOTE sha
|
|
run_junkremote() { cd "$1" || exit 1; local sha; sha=$(git rev-parse HEAD)
|
|
printf 'refs/heads/main %s refs/heads/main not-a-sha\n' "$sha" \
|
|
| bun "$CAND" origin https://example.invalid/origin.git 2>&1; echo "___EXIT:$?"; }
|
|
S13=$(run_junkremote "$R/s12")
|
|
row "13 junk REMOTE sha" "$S13" "BLOCK(unscannable)"
|
|
|
|
# 14: zero-width padding between proximity label and secret
|
|
mkrepo s14; SEC="${_S1}${_S2}" python3 -c "
|
|
import os
|
|
zw='\u200b'*200000
|
|
with open('conf.txt','w') as f:
|
|
f.write('aws_secret_access_key =\n')
|
|
f.write(zw+'\n')
|
|
f.write('\"'+os.environ['SEC']+'\"\n')"
|
|
git add -A >/dev/null; git commit -qm zerowidth
|
|
S14=$(run_probe "$R/s14")
|
|
row "14 zero-width padding at the seam" "$S14" "BLOCK(aws.secret_key)"
|
|
|
|
# 15: localSha="0" is not a branch delete
|
|
run_shortzero() { cd "$1" || exit 1
|
|
printf 'refs/heads/probe 0 refs/heads/probe 0000000000000000000000000000000000000000\n' \
|
|
| bun "$CAND" origin url 2>&1; echo "___EXIT:$?"; }
|
|
S15=$(run_shortzero "$R/s2")
|
|
row "15 localSha=\"0\" is not a delete" "$S15" "BLOCK(unscannable)"
|
|
|
|
# 16: missing-but-shaped remote sha
|
|
run_missingremote() { cd "$1" || exit 1; local sha; sha=$(git rev-parse HEAD)
|
|
printf 'refs/heads/main %s refs/heads/main cafebabecafebabecafebabecafebabecafebabe\n' "$sha" \
|
|
| bun "$CAND" origin https://example.invalid/origin.git 2>&1; echo "___EXIT:$?"; }
|
|
S16=$(run_missingremote "$R/s12")
|
|
# Now that an absent named tip drops ALL local narrowing, this blocks with the
|
|
# actual finding rather than an "unscannable" apology: the whole reachable
|
|
# range is scanned and the credential is named.
|
|
row "16 missing-but-shaped remote sha" "$S16" "BLOCK(aws.access_key)"
|
|
|
|
# 17: zero-width across slice boundary
|
|
mkrepo s17; SEC="${_S1}${_S2}" python3 -c "
|
|
import os
|
|
zw='\u200b'*4500
|
|
with open('conf.txt','w') as f:
|
|
f.write('aws_secret_access_key =\n')
|
|
for i in range(70): f.write(zw+'\n')
|
|
f.write('\"'+os.environ['SEC']+'\"\n')"
|
|
git add -A >/dev/null; git commit -qm zwslice
|
|
S17=$(run_probe "$R/s17")
|
|
row "17 zero-width across slice boundary" "$S17" "BLOCK(aws.secret_key)"
|
|
|
|
# 18: proximity split by slice cut
|
|
mkrepo s18; SEC="${_S1}${_S2}" python3 -c "
|
|
import os
|
|
W=$WINDOW; line='-'*40
|
|
lab='aws_secret_access_key ='
|
|
sec='\"'+os.environ['SEC']+'\"'
|
|
n=(W-len(lab)-1)//41
|
|
assert 41*n+len(lab)+1+len(sec)+1 > W, 'fixture does not straddle the cut'
|
|
with open('conf.txt','w') as f:
|
|
for i in range(n): f.write(line+'\n')
|
|
f.write(lab+'\n')
|
|
f.write(sec+'\n')
|
|
for i in range(100): f.write('-'*40+'\n')"
|
|
git add -A >/dev/null; git commit -qm proxsplit
|
|
S18=$(run_probe "$R/s18")
|
|
row "18 proximity split by slice cut" "$S18" "BLOCK(aws.secret_key)"
|
|
|
|
# 20: an absent named tip whose GUESS is non-empty but wrong.
|
|
# Row 16 covers the absent-tip case where the guess scans nothing. This is the
|
|
# other half: the stale tracking ref holds the credential, one harmless local
|
|
# commit sits on top, so the guessed range is non-empty and credential-free.
|
|
# Blocking only the empty guess let this through with exit 0.
|
|
mkrepo s20
|
|
printf 'aws_access_key_id = %s\n' "$KEY" > secret.txt
|
|
git add -A >/dev/null
|
|
git commit -qm cred >/dev/null
|
|
git update-ref refs/remotes/origin/main HEAD
|
|
git remote add origin https://example.invalid/o.git
|
|
git remote set-head origin main >/dev/null 2>&1
|
|
echo 'harmless = 1' > ok.py
|
|
git add -A >/dev/null
|
|
git commit -qm harmless >/dev/null
|
|
run_absent_tip() {
|
|
cd "$1" || exit 1
|
|
local sha
|
|
sha=$(git rev-parse HEAD)
|
|
printf 'refs/heads/main %s refs/heads/main aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa7\n' "$sha" \
|
|
| bun "$CAND" origin https://example.invalid/o.git 2>&1
|
|
echo "___EXIT:$?"
|
|
}
|
|
S20=$(run_absent_tip "$R/s20")
|
|
row "20 absent named tip, non-empty wrong guess" "$S20" "BLOCK(aws.access_key)"
|
|
# E1: ordinary new-branch push must scan ONLY the new commit
|
|
mkrepo e1
|
|
printf 'aws_key = "%s"\n' "$KEY" > old.py
|
|
git add -A >/dev/null; git commit -qm old >/dev/null
|
|
git update-ref refs/remotes/origin/main HEAD
|
|
git remote add origin https://example.invalid/o.git
|
|
git checkout -q -b feature
|
|
echo 'harmless = 1' > new.py; git add -A >/dev/null; git commit -qm new >/dev/null
|
|
E1=$(cd "$R/e1" && printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$ZERO40" \
|
|
| bun "$CAND" origin https://example.invalid/o.git 2>&1; echo "___EXIT:$?")
|
|
row "E1 new branch off origin/main stays narrow" "$E1" "ALLOW"
|
|
|
|
# E2: default branch reachable via origin/HEAD -> trunk
|
|
mkrepo e2
|
|
printf 'aws_key = "%s"\n' "$KEY" > old.py
|
|
git add -A >/dev/null; git commit -qm old >/dev/null
|
|
git update-ref refs/remotes/origin/trunk HEAD
|
|
git symbolic-ref refs/remotes/origin/HEAD refs/remotes/origin/trunk
|
|
git remote add origin https://example.invalid/o.git
|
|
git checkout -q -b feature
|
|
echo 'harmless = 1' > new.py; git add -A >/dev/null; git commit -qm new >/dev/null
|
|
E2=$(cd "$R/e2" && printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$ZERO40" \
|
|
| bun "$CAND" origin https://example.invalid/o.git 2>&1; echo "___EXIT:$?")
|
|
row "E2 origin/HEAD -> trunk resolves, stays narrow" "$E2" "ALLOW"
|
|
|
|
# E3: fetched second remote anchors on its own default branch
|
|
mkrepo e3
|
|
printf 'aws_key = "%s"\n' "$KEY" > old.py
|
|
git add -A >/dev/null; git commit -qm old >/dev/null
|
|
git update-ref refs/remotes/publish/main HEAD
|
|
git remote add origin https://example.invalid/o.git
|
|
git remote add publish https://example.invalid/p.git
|
|
git checkout -q -b feature
|
|
echo 'harmless = 1' > new.py; git add -A >/dev/null; git commit -qm new >/dev/null
|
|
E3=$(cd "$R/e3" && printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$ZERO40" \
|
|
| bun "$CAND" publish https://example.invalid/p.git 2>&1; echo "___EXIT:$?")
|
|
row "E3 fetched non-origin remote anchors on its own" "$E3" "ALLOW"
|
|
|
|
# E4: real branch delete stays a skip
|
|
mkrepo e4; echo hi > a.txt; git add -A >/dev/null; git commit -qm init >/dev/null
|
|
E4=$(cd "$R/e4" && printf 'refs/heads/main %s refs/heads/main %s\n' "$ZERO40" "$(git rev-parse HEAD)" \
|
|
| bun "$CAND" origin https://example.invalid/o.git 2>&1; echo "___EXIT:$?")
|
|
row "E4 branch delete skipped" "$E4" "ALLOW"
|
|
|
|
# E5: a URL push must NOT borrow origin's tip as its base.
|
|
# The credential is already on origin/main and the local commit adds only
|
|
# harmless content. Anchoring on origin (or excluding origin's commits as
|
|
# "already pushed") scans the harmless commit only and ships the credential to
|
|
# a remote that never had it. A URL target is described by no tracking ref.
|
|
mkrepo e5
|
|
printf 'aws_key = "%s"\n' "$KEY" > old.py
|
|
git add -A >/dev/null; git commit -qm old >/dev/null
|
|
git update-ref refs/remotes/origin/main HEAD
|
|
git remote add origin https://example.invalid/o.git
|
|
git checkout -q -b feature
|
|
echo 'harmless = 1' > new.py; git add -A >/dev/null; git commit -qm new >/dev/null
|
|
E5=$(cd "$R/e5" && printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$ZERO40" \
|
|
| bun "$CAND" https://example.invalid/direct.git '' 2>&1; echo "___EXIT:$?")
|
|
row "E5 URL push does not borrow origin's base" "$E5" "BLOCK(aws.access_key)"
|
|
|
|
# E5b: the other half of E5 — over-scanning must not turn every URL push into a
|
|
# block. Same shape, no credential anywhere: the push has to be allowed.
|
|
mkrepo e5b
|
|
echo 'old = 1' > old.py
|
|
git add -A >/dev/null; git commit -qm old >/dev/null
|
|
git update-ref refs/remotes/origin/main HEAD
|
|
git remote add origin https://example.invalid/o.git
|
|
git checkout -q -b feature
|
|
echo 'harmless = 1' > new.py; git add -A >/dev/null; git commit -qm new >/dev/null
|
|
E5B=$(cd "$R/e5b" && printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$ZERO40" \
|
|
| bun "$CAND" https://example.invalid/direct.git '' 2>&1; echo "___EXIT:$?")
|
|
row "E5b clean URL push still allowed" "$E5B" "ALLOW"
|
|
|
|
# 19: a line longer than the overlap must still contribute its END to the seam.
|
|
# One ~770 KB ASCII line whose tail is the qualifying label, then a line holding
|
|
# the secret. Carrying whole lines only leaves the seam with no context, so
|
|
# aws.secret_key never fires even though both halves are in the pushed diff.
|
|
mkrepo s19
|
|
SEC="$SEC" python3 -c "
|
|
import os
|
|
sec=os.environ['SEC']
|
|
filler='x'*770047
|
|
with open('big.txt','w') as f:
|
|
f.write(filler+' aws_secret_access_key =\n')
|
|
f.write(sec+' '+'y'*20000+'\n')"
|
|
git add -A >/dev/null; git commit -qm bigline >/dev/null
|
|
S19=$(run_probe "$R/s19")
|
|
row "19 label at the end of an over-overlap line" "$S19" "BLOCK(aws.secret_key)"
|
|
|
|
# E6: long-line slicer survives multi-byte text
|
|
mkrepo e6; KEY="$KEY" python3 -c "
|
|
import os
|
|
k=os.environ['KEY']
|
|
pad='žřáčě\U0001f600'
|
|
with open('b.min.js','w') as f:
|
|
f.write('var d=\"'+pad*160000+'\",aws_key=\"'+k+'\";')"
|
|
git add -A >/dev/null; git commit -qm mb >/dev/null
|
|
E6=$(cd "$R/e6" && printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$ZERO40" \
|
|
| bun "$CAND" origin https://example.invalid/o.git 2>&1; echo "___EXIT:$?")
|
|
row "E6 long-line slicer survives multi-byte text" "$E6" "BLOCK"
|
|
|
|
# E7: clean repo, ordinary existing-branch push
|
|
mkrepo e7; echo 'a = 1' > a.py; git add -A >/dev/null; git commit -qm one >/dev/null
|
|
BASE=$(git rev-parse HEAD)
|
|
git update-ref refs/remotes/origin/main "$BASE"
|
|
git remote add origin https://example.invalid/o.git
|
|
echo 'b = 2' >> a.py; git add -A >/dev/null; git commit -qm two >/dev/null
|
|
E7=$(cd "$R/e7" && printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$BASE" \
|
|
| bun "$CAND" origin https://example.invalid/o.git 2>&1; echo "___EXIT:$?")
|
|
row "E7 ordinary push of clean content" "$E7" "ALLOW"
|
|
|
|
# E8: sha256 repo, new branch
|
|
if git init -q --object-format=sha256 "$R/.probe256" 2>/dev/null; then
|
|
rm -rf "$R/.probe256"
|
|
d="$R/e8"; rm -rf "$d"; mkdir -p "$d"; cd "$d" || exit 1
|
|
git init -q --object-format=sha256 .
|
|
git config user.email t@t.t; git config user.name t; git checkout -q -b main
|
|
printf 'cfg = 1\naws_key = "%s"\n' "$KEY" > app.py
|
|
git add -A >/dev/null; git commit -qm seed >/dev/null
|
|
git remote add origin https://example.invalid/o.git
|
|
ZERO64=$(printf '0%.0s' {1..64})
|
|
E8=$(cd "$d" && printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$ZERO64" \
|
|
| bun "$CAND" origin https://example.invalid/o.git 2>&1; echo "___EXIT:$?")
|
|
row "E8 sha256 repo, new branch" "$E8" "BLOCK(aws.access_key)"
|
|
|
|
# E8b: the row that actually proves the empty-tree OID fix. E8 alone does not:
|
|
# it carries a credential, so a build that cannot resolve the fallback range
|
|
# still "blocks", just with a diff error instead of a finding. A CLEAN sha256
|
|
# first push must be ALLOWED — under the hardcoded SHA-1 empty-tree OID it was
|
|
# hard-blocked, which is unusable rather than safe.
|
|
d="$R/e8b"
|
|
rm -rf "$d"
|
|
mkdir -p "$d"
|
|
cd "$d" || exit 1
|
|
git init -q --object-format=sha256 .
|
|
git config user.email t@t.t
|
|
git config user.name t
|
|
git checkout -q -b main
|
|
printf 'cfg = 1\nharmless = 2\n' > app.py
|
|
git add -A >/dev/null
|
|
git commit -qm seed >/dev/null
|
|
git remote add origin https://example.invalid/o.git
|
|
E8B=$(printf 'refs/heads/main %s refs/heads/main %s\n' "$(git rev-parse HEAD)" "$ZERO64" \
|
|
| bun "$CAND" origin https://example.invalid/o.git 2>&1; echo "___EXIT:$?")
|
|
row "E8b clean sha256 first push allowed" "$E8B" "ALLOW"
|
|
fi
|
|
|
|
echo
|
|
if [ "$FAILURES" -eq 0 ]; then
|
|
echo "GATE: PASS ($TOTAL/$TOTAL)"
|
|
exit 0
|
|
else
|
|
echo "GATE: FAIL"
|
|
exit 1
|
|
fi
|