Skip to content

Commit 6efb50e

Browse files
committed
bughunt(uv): probe v5 re-triage and user override rollback
1 parent 2463257 commit 6efb50e

1 file changed

Lines changed: 364 additions & 0 deletions

File tree

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

Lines changed: 364 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,364 @@
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, "sha512": "sha512-" + base64.b64encode(hashlib.sha512(WHEEL).digest()).decode()}}],
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_rb.py" <<'PYEOF'
157+
#!/usr/bin/env python3
158+
"""uv hosted rollback-after-edit probe. usage: driver.py SOCKET_PATCH UV WORKDIR"""
159+
import json, os, shutil, subprocess, sys
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", "--patch-server-url", "http://127.0.0.1:18080"]
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", "idna==3.7"]\n'
165+
166+
167+
def run(cmd, cwd):
168+
return subprocess.run(cmd, cwd=cwd, env=ENV, capture_output=True, text=True, encoding="utf-8", errors="replace")
169+
170+
171+
def sp(args, cwd):
172+
p = run([SP] + args + API, cwd)
173+
try:
174+
return p.returncode, json.loads(p.stdout)
175+
except Exception:
176+
return p.returncode, {"raw": p.stdout[-500:], "stderr": p.stderr[-500:]}
177+
178+
179+
def hosted_refs(d):
180+
n = 0
181+
for f in os.listdir(d):
182+
if f in ("pyproject.toml", "uv.lock") or f.endswith(".py.lock") or f.endswith(".py"):
183+
with open(os.path.join(d, f), encoding="utf-8") as fh:
184+
n += fh.read().count("127.0.0.1:18080")
185+
return n
186+
187+
188+
def case(name, mutate):
189+
d = os.path.join(WORK, name)
190+
shutil.rmtree(d, ignore_errors=True)
191+
os.makedirs(d)
192+
with open(os.path.join(d, "pyproject.toml"), "w", newline="\n") as f:
193+
f.write(PYPROJECT)
194+
for c in (["lock", "-q"], ["sync", "-q"]):
195+
p = run([UV] + c, d)
196+
if p.returncode:
197+
return {"case": name, "result": "setup-failed", "err": p.stderr[-400:]}
198+
rc, s1 = sp(["scan", "--mode", "hosted", "--json", "--yes"], d)
199+
red = s1.get("redirect", {}).get("redirected")
200+
mutate(d)
201+
rc, s2 = sp(["scan", "--mode", "hosted", "--json", "--yes"], d)
202+
rc, rb = sp(["rollback", "--json", "--yes"], d)
203+
failed = [f.get("error", "")[:400] for f in rb.get("hosted", {}).get("failed", [])]
204+
refs = hosted_refs(d)
205+
return {"case": name, "scanRedirected": red, "rollbackExit": rc, "rollbackStatus": rb.get("status"),
206+
"hostedFailed": failed, "hostedRefsLeft": refs,
207+
"result": "PASS" if rc == 0 and refs == 0 else "FAIL"}
208+
209+
210+
def none(d):
211+
pass
212+
213+
214+
def add_desc(d):
215+
p = os.path.join(d, "pyproject.toml")
216+
with open(p, encoding="utf-8") as f:
217+
t = f.read()
218+
with open(p, "w", newline="\n") as f:
219+
f.write(t.replace('version = "0.1.0"\n', 'version = "0.1.0"\ndescription = "hello"\n', 1))
220+
221+
222+
def uv_add(d):
223+
p = run([UV, "add", "-q", "certifi==2024.2.2"], d)
224+
if p.returncode:
225+
print("uv add failed:", p.stderr[-300:])
226+
227+
228+
results = [case("control-no-edit", none), case("pyproject-description", add_desc), case("uv-add-idna", uv_add)]
229+
out = subprocess.run([UV, "--version"], capture_output=True, text=True).stdout.strip()
230+
for r in results:
231+
r["uv"] = out
232+
print("RESULT " + json.dumps(r))
233+
PYEOF
234+
cat > "$RUNNER_TEMP/driver_rep.py" <<'PYEOF'
235+
#!/usr/bin/env python3
236+
"""uv vendored repair probe (lock-only checkout, committed wheel deleted). usage: driver.py SOCKET_PATCH UV WORKDIR"""
237+
import glob, json, os, shutil, subprocess, sys, hashlib
238+
239+
SP, UV, WORK = sys.argv[1], sys.argv[2], sys.argv[3]
240+
API = ["--api-url", "http://127.0.0.1:18080", "--api-token", "fake-token", "--org", "test-org", "--patch-server-url", "http://127.0.0.1:18080"]
241+
ENV = dict(os.environ, SOCKET_TELEMETRY_DISABLED="1", SOCKET_NO_CONFIG="1")
242+
PYPROJECT = '[project]\nname = "uvp"\nversion = "0.1.0"\nrequires-python = ">=3.9"\ndependencies = ["six==1.16.0"]\n'
243+
PATCHED = "c0c1c71ca455"
244+
245+
246+
def run(cmd, cwd, **kw):
247+
return subprocess.run(cmd, cwd=cwd, env=kw.get("env", ENV), capture_output=True, text=True, encoding="utf-8", errors="replace")
248+
249+
250+
def sp(args, cwd):
251+
p = run([SP] + args + API, cwd)
252+
try:
253+
return p.returncode, json.loads(p.stdout)
254+
except Exception:
255+
return p.returncode, {"raw": p.stdout[-500:], "stderr": p.stderr[-500:]}
256+
257+
258+
def six_sha(d):
259+
hits = glob.glob(os.path.join(d, ".venv", "**", "six.py"), recursive=True)
260+
return hashlib.sha256(open(hits[0], "rb").read()).hexdigest()[:12] if hits else None
261+
262+
263+
def install(d, tag, *flags):
264+
shutil.rmtree(os.path.join(d, ".venv"), ignore_errors=True)
265+
env = dict(ENV, UV_CACHE_DIR=os.path.join(WORK, "cache-" + tag))
266+
p = run([UV, "sync"] + list(flags), d, env=env)
267+
return {"rc": p.returncode, "six": six_sha(d), "err": p.stderr[-300:] if p.returncode else ""}
268+
269+
270+
def git(d, *a):
271+
return run(["git"] + list(a), d)
272+
273+
274+
shutil.rmtree(WORK, ignore_errors=True)
275+
proj = os.path.join(WORK, "proj")
276+
os.makedirs(proj)
277+
with open(os.path.join(proj, "pyproject.toml"), "w", newline="\n") as f:
278+
f.write(PYPROJECT)
279+
with open(os.path.join(proj, ".gitignore"), "w", newline="\n") as f:
280+
f.write(".venv\n")
281+
r = {}
282+
for c in (["lock", "-q"], ["sync", "-q"]):
283+
run([UV] + c, proj)
284+
rc, s = sp(["scan", "--mode", "vendored", "--json", "--yes"], proj)
285+
r["vendorScan"] = [rc, s.get("vendor", {}).get("summary", {}).get("applied")]
286+
git(proj, "init", "-q", ".")
287+
git(proj, "add", "-A")
288+
git(proj, "commit", "-qm", "vendored")
289+
r["committed"] = git(proj, "ls-files", ".socket").stdout.split()
290+
def repair_case(tag, with_venv):
291+
clone = os.path.join(WORK, "clone-" + tag)
292+
git(WORK, "clone", "-q", proj, clone)
293+
c = {}
294+
if with_venv:
295+
c["preInstall"] = install(clone, tag + "0", "--locked")
296+
wheels = glob.glob(os.path.join(clone, ".socket", "vendor", "pypi", "*", "*.whl"))
297+
for w in wheels:
298+
os.remove(w)
299+
rc, rep = sp(["repair", "--json"], clone)
300+
c["repair"] = {"rc": rc, "status": rep.get("status"),
301+
"events": [(e.get("action"), e.get("errorCode"), (e.get("error") or "")[:260]) for e in rep.get("events", [])]}
302+
c["wheelBack"] = bool(glob.glob(os.path.join(clone, ".socket", "vendor", "pypi", "*", "*.whl")))
303+
c["afterRepairLocked"] = install(clone, tag + "1", "--locked")
304+
c["gitStatus"] = git(clone, "status", "--short").stdout.strip().splitlines()
305+
ok = rc == 0 and c["wheelBack"] and c["afterRepairLocked"]["six"] == PATCHED and c["afterRepairLocked"]["rc"] == 0
306+
c["result"] = "PASS" if ok else "FAIL"
307+
return c
308+
309+
310+
r["lockOnly"] = repair_case("lockonly", False)
311+
r["withPatchedVenv"] = repair_case("venv", True)
312+
r["uv"] = subprocess.run([UV, "--version"], capture_output=True, text=True).stdout.strip()
313+
print("RESULT " + json.dumps(r))
314+
PYEOF
315+
cat > "$RUNNER_TEMP/driver_ovr.py" <<'PYEOF'
316+
#!/usr/bin/env python3
317+
"""uv hosted rollback/remove vs a user-authored override-dependencies pin. usage: driver_ovr.py SP UV WORK"""
318+
import json, os, re, shutil, subprocess, sys
319+
SP, UV, WORK = sys.argv[1], sys.argv[2], sys.argv[3]
320+
API = ["--api-url", "http://127.0.0.1:18080", "--api-token", "fake-token", "--org", "test-org", "--patch-server-url", "http://127.0.0.1:18080"]
321+
ENV = dict(os.environ, SOCKET_TELEMETRY_DISABLED="1", SOCKET_NO_CONFIG="1")
322+
PP = '[project]\nname = "uvp"\nversion = "0.1.0"\nrequires-python = ">=3.9"\ndependencies = ["python-dateutil==2.8.2"]\n\n[tool.uv]\noverride-dependencies = ["six==1.16.0"]\n'
323+
def run(cmd, cwd):
324+
return subprocess.run(cmd, cwd=cwd, env=ENV, capture_output=True, text=True, encoding="utf-8", errors="replace")
325+
def six_locked(d):
326+
t = open(os.path.join(d, "uv.lock"), encoding="utf-8").read()
327+
m = re.search(r'name = "six"\nversion = "([^"]+)"', t)
328+
return m.group(1) if m else None
329+
for cmd in (["rollback", "--json", "--yes"], ["remove", "pkg:pypi/six@1.16.0", "--json", "--yes"], None):
330+
name = cmd[0] if cmd else "control-no-socket-patch"
331+
d = os.path.join(WORK, name); shutil.rmtree(d, ignore_errors=True); os.makedirs(d)
332+
with open(os.path.join(d, "pyproject.toml"), "w", newline="\n") as f: f.write(PP)
333+
run([UV, "lock", "-q"], d)
334+
r = {"case": name}
335+
if cmd:
336+
p = run([SP, "scan", "--mode", "hosted", "--json", "--yes"] + API, d)
337+
r["scanRc"] = p.returncode
338+
p = run([SP] + cmd + API, d)
339+
r["undoRc"] = p.returncode
340+
try: r["warnings"] = [w.get("code") for w in json.loads(p.stdout).get("warnings", [])]
341+
except Exception: r["undoOut"] = p.stdout[-300:]
342+
r["userOverrideKept"] = "override-dependencies" in open(os.path.join(d, "pyproject.toml"), encoding="utf-8").read()
343+
run([UV, "lock", "-q", "--upgrade"], d)
344+
r["sixAfterUpgrade"] = six_locked(d)
345+
r["uv"] = run([UV, "--version"], d).stdout.strip()
346+
r["result"] = "PASS" if r["userOverrideKept"] and r["sixAfterUpgrade"] == "1.16.0" else "FAIL"
347+
print("RESULT " + json.dumps(r), flush=True)
348+
PYEOF
349+
python -m pip download --no-deps six==1.16.0 -d "$RUNNER_TEMP/mock"
350+
- name: Run probe
351+
run: |
352+
python "$RUNNER_TEMP/mock/mock.py" > "$RUNNER_TEMP/mock.out" 2>&1 &
353+
sleep 3; cat "$RUNNER_TEMP/mock.out"
354+
SP="$PWD/target/release/socket-patch"; [ -f "$SP.exe" ] && SP="$SP.exe"
355+
git config --global user.email probe@example.com; git config --global user.name probe
356+
for v in 0.2.37 0.5.31 0.12.21; do
357+
python -m venv "$RUNNER_TEMP/uv-$v"
358+
if [ -d "$RUNNER_TEMP/uv-$v/Scripts" ]; then B="$RUNNER_TEMP/uv-$v/Scripts"; else B="$RUNNER_TEMP/uv-$v/bin"; fi
359+
"$B/python" -m pip install -q "uv==$v"
360+
UVB="$B/uv"; [ -f "$UVB.exe" ] && UVB="$UVB.exe"
361+
echo "::group::uv $v rollback-after-edit (#379)"; python "$RUNNER_TEMP/driver_rb.py" "$SP" "$UVB" "$RUNNER_TEMP/work/rb-$v" || true; echo "::endgroup::"
362+
echo "::group::uv $v vendored repair (#381)"; python "$RUNNER_TEMP/driver_rep.py" "$SP" "$UVB" "$RUNNER_TEMP/work/rep-$v" || true; echo "::endgroup::"
363+
echo "::group::uv $v user override"; python "$RUNNER_TEMP/driver_ovr.py" "$SP" "$UVB" "$RUNNER_TEMP/work/ovr-$v" || true; echo "::endgroup::"
364+
done

0 commit comments

Comments
 (0)