Skip to content

Commit b73eae9

Browse files
committed
bughunt(uv): probe vendored repair of a deleted committed wheel
1 parent f6b7fb9 commit b73eae9

1 file changed

Lines changed: 250 additions & 0 deletions

File tree

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

Lines changed: 250 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
name: bughunt uv probe
2+
on:
3+
push:
4+
branches: ["bughunt/uv/**"]
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+
runs-on: ${{ matrix.os }}
14+
timeout-minutes: 45
15+
defaults:
16+
run:
17+
shell: bash
18+
steps:
19+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
20+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
21+
with:
22+
python-version: "3.11"
23+
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
24+
- name: Build CLI
25+
run: cargo build --release -p socket-patch-cli
26+
- name: Write probe files
27+
run: |
28+
mkdir -p "$RUNNER_TEMP/mock" "$RUNNER_TEMP/work"
29+
cat > "$RUNNER_TEMP/mock/mock.py" <<'PYEOF'
30+
#!/usr/bin/env python3
31+
"""Local mock of the Socket patch API serving one patched pypi package (six 1.16.0)."""
32+
import base64, hashlib, io, json, os, re, sys, zipfile
33+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
34+
35+
HERE = os.path.dirname(os.path.abspath(__file__))
36+
PORT = int(os.environ.get("MOCK_PORT", "18080"))
37+
ORG = "test-org"
38+
UUID = "0f6e7c1a-1111-4222-8333-444455556666"
39+
TOKEN = "11111111-2222-4333-8444-555555555555"
40+
SUFFIX = b"\n# SOCKET-PATCHED\nSOCKET_PATCHED = 1\n"
41+
NAME, VER = "six", "1.16.0"
42+
PURL = f"pkg:pypi/{NAME}@{VER}"
43+
LEAF = f"{NAME}-{VER}-py2.py3-none-any.whl"
44+
LOG = os.path.join(HERE, "requests.log")
45+
46+
47+
def git_sha256(b):
48+
return hashlib.sha256(b"blob %d\0" % len(b) + b).hexdigest()
49+
50+
51+
def build():
52+
src = zipfile.ZipFile(os.path.join(HERE, LEAF))
53+
members = []
54+
dist = f"{NAME}-{VER}.dist-info"
55+
for info in src.infolist():
56+
if info.filename == f"{dist}/RECORD":
57+
continue
58+
data = src.read(info.filename)
59+
if info.filename == "six.py":
60+
orig = data
61+
data = data + SUFFIX
62+
members.append((info.filename, data))
63+
rec = ""
64+
for n, d in members:
65+
dig = base64.urlsafe_b64encode(hashlib.sha256(d).digest()).rstrip(b"=").decode()
66+
rec += f"{n},sha256={dig},{len(d)}\n"
67+
rec += f"{dist}/RECORD,,\n"
68+
members.append((f"{dist}/RECORD", rec.encode()))
69+
buf = io.BytesIO()
70+
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
71+
for n, d in members:
72+
zi = zipfile.ZipInfo(n, date_time=(2020, 1, 1, 0, 0, 0)); zi.compress_type = zipfile.ZIP_DEFLATED
73+
z.writestr(zi, d)
74+
return orig, orig + SUFFIX, buf.getvalue()
75+
76+
77+
ORIG, PATCHED, WHEEL = build()
78+
SHA = hashlib.sha256(WHEEL).hexdigest()
79+
BASE = f"http://127.0.0.1:{PORT}"
80+
ART_PATH = f"/patch/pypi/{NAME}/{VER}/{TOKEN}/{UUID}/{LEAF}"
81+
ART = BASE + ART_PATH
82+
VIEW = {
83+
"uuid": UUID, "purl": PURL, "publishedAt": "Fri, 27 Mar 2026 00:00:00 GMT",
84+
"files": {"six.py": {"beforeHash": git_sha256(ORIG), "afterHash": git_sha256(PATCHED),
85+
"blobContent": base64.b64encode(PATCHED).decode()}},
86+
"vulnerabilities": {"GHSA-uvbu-ildv-ex01": {"cves": ["CVE-2026-7101"], "summary": "s",
87+
"severity": "high", "description": "d"}},
88+
"description": "uv bughunt patch", "license": "MIT", "tier": "free",
89+
}
90+
91+
92+
class H(BaseHTTPRequestHandler):
93+
def log_message(self, *a):
94+
pass
95+
96+
def send(self, code, body, ctype="application/json"):
97+
if not isinstance(body, bytes):
98+
body = json.dumps(body).encode()
99+
self.send_response(code)
100+
self.send_header("Content-Type", ctype)
101+
self.send_header("Content-Length", str(len(body)))
102+
self.end_headers()
103+
self.wfile.write(body)
104+
105+
def route(self):
106+
with open(LOG, "a") as f:
107+
f.write(f"{self.command} {self.path}\n")
108+
p = self.path.split("?")[0]
109+
n = int(self.headers.get("Content-Length") or 0)
110+
body = self.rfile.read(n) if n else b""
111+
if p == ART_PATH:
112+
if self.command == "HEAD":
113+
self.send_response(200); self.send_header("Content-Length", str(len(WHEEL))); self.end_headers(); return
114+
return self.send(200, WHEEL, "application/octet-stream")
115+
if re.fullmatch(rf"/v0/orgs/{ORG}/patches/batch", p) or p.endswith("/patch/batch"):
116+
try:
117+
comps = json.loads(body or b"{}").get("components", [])
118+
except Exception:
119+
comps = []
120+
purls = [c.get("purl", "") for c in comps] if comps else [PURL]
121+
if not any(pu.lower().startswith(f"pkg:pypi/{NAME}@{VER}") for pu in purls):
122+
return self.send(200, {"packages": [], "canAccessPaidPatches": False})
123+
return self.send(200, {"packages": [{"purl": PURL, "patches": [{
124+
"uuid": UUID, "purl": PURL, "tier": "free", "cveIds": ["CVE-2026-7101"],
125+
"ghsaIds": ["GHSA-uvbu-ildv-ex01"], "severity": "HIGH", "title": "uv bughunt"}]}],
126+
"canAccessPaidPatches": False})
127+
if "/patches/by-package/" in p or "/patch/by-package/" in p:
128+
return self.send(200, {"patches": [{"uuid": UUID, "purl": PURL,
129+
"publishedAt": "2026-09-01T00:00:00Z", "description": "uv bughunt",
130+
"license": "MIT", "tier": "free", "vulnerabilities": {}}],
131+
"canAccessPaidPatches": False})
132+
if p.endswith("/patches/package") or p.endswith("/patch/package"):
133+
return self.send(200, {"results": {UUID: {"status": "granted", "url": ART, "purl": PURL,
134+
"artifacts": [{"kind": "tarball", "url": ART, "integrity": {"sha256": SHA}}],
135+
"registryOverride": None}}})
136+
if p.endswith(f"/view/{UUID}"):
137+
return self.send(200, VIEW)
138+
if "/blob/" in p:
139+
h = p.rsplit("/", 1)[1]
140+
if h == git_sha256(PATCHED):
141+
return self.send(200, PATCHED, "application/octet-stream")
142+
if h == git_sha256(ORIG):
143+
return self.send(200, ORIG, "application/octet-stream")
144+
return self.send(404, {"error": "not found", "path": p})
145+
146+
do_GET = route
147+
do_POST = route
148+
do_HEAD = route
149+
150+
151+
if __name__ == "__main__":
152+
print(json.dumps({"art": ART, "sha": SHA, "patched_py_sha": hashlib.sha256(PATCHED).hexdigest(),
153+
"orig_py_sha": hashlib.sha256(ORIG).hexdigest()}), flush=True)
154+
ThreadingHTTPServer(("127.0.0.1", PORT), H).serve_forever()
155+
PYEOF
156+
cat > "$RUNNER_TEMP/driver.py" <<'PYEOF'
157+
#!/usr/bin/env python3
158+
"""uv vendored repair probe (lock-only checkout, committed wheel deleted). usage: driver.py SOCKET_PATCH UV WORKDIR"""
159+
import glob, json, os, shutil, subprocess, sys, hashlib
160+
161+
SP, UV, WORK = sys.argv[1], sys.argv[2], sys.argv[3]
162+
API = ["--api-url", "http://127.0.0.1:18080", "--api-token", "fake-token", "--org", "test-org"]
163+
ENV = dict(os.environ, SOCKET_TELEMETRY_DISABLED="1", SOCKET_NO_CONFIG="1")
164+
PYPROJECT = '[project]\nname = "uvp"\nversion = "0.1.0"\nrequires-python = ">=3.9"\ndependencies = ["six==1.16.0"]\n'
165+
PATCHED = "c0c1c71ca455"
166+
167+
168+
def run(cmd, cwd, **kw):
169+
return subprocess.run(cmd, cwd=cwd, env=kw.get("env", ENV), capture_output=True, text=True, encoding="utf-8", errors="replace")
170+
171+
172+
def sp(args, cwd):
173+
p = run([SP] + args + API, cwd)
174+
try:
175+
return p.returncode, json.loads(p.stdout)
176+
except Exception:
177+
return p.returncode, {"raw": p.stdout[-500:], "stderr": p.stderr[-500:]}
178+
179+
180+
def six_sha(d):
181+
hits = glob.glob(os.path.join(d, ".venv", "**", "six.py"), recursive=True)
182+
return hashlib.sha256(open(hits[0], "rb").read()).hexdigest()[:12] if hits else None
183+
184+
185+
def install(d, tag, *flags):
186+
shutil.rmtree(os.path.join(d, ".venv"), ignore_errors=True)
187+
env = dict(ENV, UV_CACHE_DIR=os.path.join(WORK, "cache-" + tag))
188+
p = run([UV, "sync"] + list(flags), d, env=env)
189+
return {"rc": p.returncode, "six": six_sha(d), "err": p.stderr[-300:] if p.returncode else ""}
190+
191+
192+
def git(d, *a):
193+
return run(["git"] + list(a), d)
194+
195+
196+
shutil.rmtree(WORK, ignore_errors=True)
197+
proj = os.path.join(WORK, "proj")
198+
os.makedirs(proj)
199+
with open(os.path.join(proj, "pyproject.toml"), "w", newline="\n") as f:
200+
f.write(PYPROJECT)
201+
with open(os.path.join(proj, ".gitignore"), "w", newline="\n") as f:
202+
f.write(".venv\n")
203+
r = {}
204+
for c in (["lock", "-q"], ["sync", "-q"]):
205+
run([UV] + c, proj)
206+
rc, s = sp(["scan", "--mode", "vendored", "--json", "--yes"], proj)
207+
r["vendorScan"] = [rc, s.get("vendor", {}).get("summary", {}).get("applied")]
208+
git(proj, "init", "-q", ".")
209+
git(proj, "add", "-A")
210+
git(proj, "commit", "-qm", "vendored")
211+
r["committed"] = git(proj, "ls-files", ".socket").stdout.split()
212+
def repair_case(tag, with_venv):
213+
clone = os.path.join(WORK, "clone-" + tag)
214+
git(WORK, "clone", "-q", proj, clone)
215+
c = {}
216+
if with_venv:
217+
c["preInstall"] = install(clone, tag + "0", "--locked")
218+
wheels = glob.glob(os.path.join(clone, ".socket", "vendor", "pypi", "*", "*.whl"))
219+
for w in wheels:
220+
os.remove(w)
221+
rc, rep = sp(["repair", "--json"], clone)
222+
c["repair"] = {"rc": rc, "status": rep.get("status"),
223+
"events": [(e.get("action"), e.get("errorCode"), (e.get("error") or "")[:260]) for e in rep.get("events", [])]}
224+
c["wheelBack"] = bool(glob.glob(os.path.join(clone, ".socket", "vendor", "pypi", "*", "*.whl")))
225+
c["afterRepairLocked"] = install(clone, tag + "1", "--locked")
226+
c["gitStatus"] = git(clone, "status", "--short").stdout.strip().splitlines()
227+
ok = rc == 0 and c["wheelBack"] and c["afterRepairLocked"]["six"] == PATCHED and c["afterRepairLocked"]["rc"] == 0
228+
c["result"] = "PASS" if ok else "FAIL"
229+
return c
230+
231+
232+
r["lockOnly"] = repair_case("lockonly", False)
233+
r["withPatchedVenv"] = repair_case("venv", True)
234+
r["uv"] = subprocess.run([UV, "--version"], capture_output=True, text=True).stdout.strip()
235+
print("RESULT " + json.dumps(r))
236+
PYEOF
237+
python -m pip download --no-deps six==1.16.0 -d "$RUNNER_TEMP/mock"
238+
- name: Run probe
239+
run: |
240+
python "$RUNNER_TEMP/mock/mock.py" > "$RUNNER_TEMP/mock.out" 2>&1 &
241+
sleep 3; cat "$RUNNER_TEMP/mock.out"
242+
SP="$PWD/target/release/socket-patch"; [ -f "$SP.exe" ] && SP="$SP.exe"
243+
git config --global user.email probe@example.com; git config --global user.name probe
244+
for v in 0.2.37 0.12.21; do
245+
python -m venv "$RUNNER_TEMP/uv-$v"
246+
if [ -d "$RUNNER_TEMP/uv-$v/Scripts" ]; then B="$RUNNER_TEMP/uv-$v/Scripts"; else B="$RUNNER_TEMP/uv-$v/bin"; fi
247+
"$B/python" -m pip install -q "uv==$v"
248+
UVB="$B/uv"; [ -f "$UVB.exe" ] && UVB="$UVB.exe"
249+
python "$RUNNER_TEMP/driver.py" "$SP" "$UVB" "$RUNNER_TEMP/work/$v" || true
250+
done

0 commit comments

Comments
 (0)