Skip to content

Commit 7631a80

Browse files
committed
bughunt probe: pip system-site venv
1 parent 2463257 commit 7631a80

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

‎.github/workflows/bughunt-pip.yml‎

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
name: bughunt-pip
2+
on:
3+
push:
4+
branches: ['bughunt/pip/**']
5+
permissions:
6+
contents: read
7+
jobs:
8+
probe:
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
os: [ubuntu-latest, macos-latest, windows-latest]
13+
python: ['3.8', '3.13']
14+
pip: ['20.3.4', 'latest']
15+
runs-on: ${{ matrix.os }}
16+
timeout-minutes: 45
17+
defaults:
18+
run:
19+
shell: bash
20+
steps:
21+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
22+
- run: rustup show
23+
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
24+
with:
25+
save-if: false
26+
- run: cargo build --release -p socket-patch-cli
27+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
28+
with:
29+
python-version: ${{ matrix.python }}
30+
- name: probe
31+
run: |
32+
set -x
33+
W="$RUNNER_TEMP/w"; mkdir -p "$W"; cd "$W"
34+
BIN="$GITHUB_WORKSPACE/target/release/socket-patch"; [ -f "$BIN.exe" ] && BIN="$BIN.exe"
35+
python -m venv pv
36+
if [ -d pv/Scripts ]; then PY=pv/Scripts/python; else PY=pv/bin/python; fi
37+
if [ "${{ matrix.pip }}" = latest ]; then $PY -m pip install -q -U pip; else $PY -m pip install -q "pip==${{ matrix.pip }}"; fi
38+
$PY -m pip --version
39+
$PY -m pip download -q six==1.16.0 --no-deps -d dl
40+
cat > mock.py <<'PYEOF'
41+
import base64, hashlib, json, re, sys, zipfile, io
42+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
43+
PORT=int(sys.argv[1]) if len(sys.argv)>1 else 8765
44+
ORG="test-org"; PURL="pkg:pypi/six@1.16.0"; UUID="5a6b7c8d-9e0f-4a1b-8c2d-3e4f5a6b7c8d"
45+
TOKEN="11111111-2222-4333-8444-555555555555"; WHEEL="six-1.16.0-py2.py3-none-any.whl"
46+
GHSA="GHSA-real-pypi-0001"; CVE="CVE-2026-7201"
47+
orig=zipfile.ZipFile("dl/"+WHEEL).read("six.py")
48+
patched=orig+b"\n# SOCKET-PATCHED\nSOCKET_PATCHED = 1\n"
49+
def d(b): return base64.urlsafe_b64encode(hashlib.sha256(b).digest()).rstrip(b"=").decode()
50+
meta=b"Metadata-Version: 2.1\nName: six\nVersion: 1.16.0\nSummary: x\n"
51+
wh=b"Wheel-Version: 1.0\nGenerator: t\nRoot-Is-Purelib: true\nTag: py2-none-any\nTag: py3-none-any\n"
52+
mem=[("six.py",patched),("six-1.16.0.dist-info/METADATA",meta),("six-1.16.0.dist-info/WHEEL",wh)]
53+
rec="".join(f"{n},sha256={d(b)},{len(b)}\n" for n,b in mem)+"six-1.16.0.dist-info/RECORD,,\n"
54+
buf=io.BytesIO(); z=zipfile.ZipFile(buf,"w")
55+
for n,b in mem+[("six-1.16.0.dist-info/RECORD",rec.encode())]: z.writestr(n,b)
56+
z.close(); wheel=buf.getvalue()
57+
sha256=hashlib.sha256(wheel).hexdigest(); sha512="sha512-"+base64.b64encode(hashlib.sha512(wheel).digest()).decode()
58+
def g(b): return hashlib.sha256(b"blob %d\0"%len(b)+b).hexdigest()
59+
BASE=f"http://127.0.0.1:{PORT}"
60+
APATH=f"/patch/pypi/six/1.16.0/{TOKEN}/{UUID}/{WHEEL}"
61+
view={"uuid":UUID,"purl":PURL,"publishedAt":"Fri, 27 Mar 2026 00:00:00 GMT","files":{"six.py":{"beforeHash":g(orig),"afterHash":g(patched),"blobContent":base64.b64encode(patched).decode()}},
62+
"vulnerabilities":{GHSA:{"cves":[CVE],"summary":"s","severity":"high","description":"d"}},"description":"x","license":"MIT","tier":"free"}
63+
class H(BaseHTTPRequestHandler):
64+
def log_message(self,*a): sys.stderr.write("REQ "+self.command+" "+self.path+"\n")
65+
def j(self,o):
66+
b=json.dumps(o).encode(); self.send_response(200); self.send_header("content-type","application/json"); self.send_header("content-length",str(len(b))); self.end_headers(); self.wfile.write(b)
67+
def do_HEAD(self):
68+
p=self.path.split("?")[0]
69+
if p==APATH: self.send_response(200); self.send_header("content-length",str(len(wheel))); self.end_headers()
70+
else: self.send_response(404); self.end_headers()
71+
def do_GET(self):
72+
p=self.path.split("?")[0]
73+
if p==APATH:
74+
self.send_response(200); self.send_header("content-type","application/octet-stream"); self.send_header("content-length",str(len(wheel))); self.end_headers(); self.wfile.write(wheel)
75+
elif p.endswith("/patches/view/"+UUID) or p=="/patch/view/"+UUID: self.j(view)
76+
elif re.match(f"^/v0/orgs/{ORG}/patches/by-package/.+$",p):
77+
self.j({"patches":[{"uuid":UUID,"purl":PURL,"publishedAt":"2026-03-27T00:00:00Z","description":"x","license":"MIT","tier":"free","vulnerabilities":{}}],"canAccessPaidPatches":False})
78+
else: self.send_response(404); self.end_headers()
79+
def do_POST(self):
80+
n=int(self.headers.get("content-length",0)); body=self.rfile.read(n)
81+
p=self.path.split("?")[0]
82+
if p.endswith("/patches/batch"):
83+
want=PURL in body.decode(errors="replace")
84+
self.j({"packages":[{"purl":PURL,"patches":[{"uuid":UUID,"purl":PURL,"tier":"free","cveIds":[CVE],"ghsaIds":[GHSA],"severity":"high","title":"t"}]}] if want else [],"canAccessPaidPatches":False})
85+
elif p.endswith("/patches/package"):
86+
u=BASE+APATH
87+
self.j({"results":{UUID:{"status":"granted","url":u,"purl":PURL,"artifacts":[{"kind":"tarball","url":u,"integrity":{"sha256":sha256,"sha512":sha512}}],"registryOverride":None}}})
88+
else: self.send_response(404); self.end_headers()
89+
print("wheel sha256",sha256,flush=True)
90+
ThreadingHTTPServer(("127.0.0.1",PORT),H).serve_forever()
91+
PYEOF
92+
python mock.py 8765 > mock.log 2>&1 &
93+
sleep 3; cat mock.log
94+
export NO_PROXY=127.0.0.1 SOCKET_TELEMETRY_DISABLED=1 SOCKET_NO_CONFIG=1
95+
A="--api-url http://127.0.0.1:8765 --api-token fake --org test-org --patch-server-url http://127.0.0.1:8765"
96+
echo "#### C: venv --system-site-packages; six 1.16.0 in the BASE interpreter"
97+
python -m pip install -q six==1.16.0
98+
for mode in hosted vendored; do
99+
d="c-$mode"; rm -rf "$d"; mkdir "$d"; cd "$d"; printf 'six==1.16.0\n' > requirements.txt
100+
../$PY -m venv --system-site-packages .venv
101+
if [ -d .venv/Scripts ]; then VP=.venv/Scripts/python; else VP=.venv/bin/python; fi
102+
if [ "${{ matrix.pip }}" = latest ]; then $VP -m pip install -q -U pip; else $VP -m pip install -q "pip==${{ matrix.pip }}"; fi
103+
"$BIN" scan --mode $mode --json $A --cwd . > scan.json 2> scan.err; src=$?
104+
warns=$(python -c "import json;d=json.load(open('scan.json'));print([w.get('code') for w in d.get('redirect',{}).get('warnings',[])], d.get('status'))")
105+
$VP -m pip install -q --disable-pip-version-check -r requirements.txt > pip.txt 2>&1; prc=$?
106+
six=$($VP -c "import six;print(six.__file__, 'PATCHED' if getattr(six,'SOCKET_PATCHED',0) else 'UNPATCHED')")
107+
"$BIN" vex $A --cwd . --product pkg:pypi/app@1 --output vex.json > vex.out 2>&1; vrc=$?
108+
st=$(python -c "import json;print([s['status'] for s in json.load(open('vex.json'))['statements']])" 2>/dev/null)
109+
oos=$(grep -c 'does not match' vex.out)
110+
echo "RESULT C-$mode scan_rc=$src warnings=$warns pip_rc=$prc six=$six vex_rc=$vrc vex=$st out_of_sync_warn=$oos"
111+
cat scan.err | grep -v -i token | tail -3
112+
cd ..
113+
done

0 commit comments

Comments
 (0)