mirror of
https://github.com/garrytan/gstack.git
synced 2026-09-26 22:51:47 +02:00
The overlap carried a long line's suffix only when the tail was otherwise empty. One short line between the long label line and the secret landed in the tail first, the tail was no longer empty, and the label was dropped again, so aws.secret_key never fired. The overlap is now filled from the seam backwards to its exact budget, taking a partial line wherever a whole one no longer fits; a character is added only if it still fits, so the slice budget invariant keeps holding. Gate rows 21 (one short line) and 22 (several) pin it; both fail on the scanner this branch forks from. The absent-tip comment claimed the range scans everything reachable. It scans the net diff of emptyTree..localSha, i.e. the final tree, not each commit, and now says so, including that a credential added and later removed inside one push is not in any diff this hook computes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
500 lines
20 KiB
Bash
Executable File
500 lines
20 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)"
|
|
|
|
# 21: same seam, but a SHORT line sits between the long line and the secret.
|
|
# Carrying a long line's suffix only when the tail is otherwise empty is not
|
|
# enough: the short line lands in the tail first, the tail is no longer empty,
|
|
# and the long line's end — where the label is — is dropped again.
|
|
mkrepo s21
|
|
SEC="$SEC" python3 -c "
|
|
import os
|
|
sec=os.environ['SEC']
|
|
filler='x'*770020
|
|
with open('big.txt','w') as f:
|
|
f.write(filler+' aws_secret_access_key =\n')
|
|
f.write('\n')
|
|
f.write(sec+' '+'y'*20000+'\n')"
|
|
git add -A >/dev/null; git commit -qm bigline-gap >/dev/null
|
|
S21=$(run_probe "$R/s21")
|
|
row "21 short line between the long label line and the secret" "$S21" "BLOCK(aws.secret_key)"
|
|
|
|
# 22: the general form of 19/21. Several short lines between the long label
|
|
# line and the secret, still well inside the pattern's proximity window. The
|
|
# property being pinned is that the LAST overlap-worth of text before a seam is
|
|
# always carried, whatever mix of long and short lines it is made of.
|
|
mkrepo s22
|
|
SEC="$SEC" python3 -c "
|
|
import os
|
|
sec=os.environ['SEC']
|
|
filler='x'*770000
|
|
with open('big.txt','w') as f:
|
|
f.write(filler+' aws_secret_access_key =\n')
|
|
for _ in range(20): f.write('\n')
|
|
f.write(sec+' '+'y'*20000+'\n')"
|
|
git add -A >/dev/null; git commit -qm bigline-gaps >/dev/null
|
|
S22=$(run_probe "$R/s22")
|
|
row "22 several short lines between long label line and secret" "$S22" "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
|