Skip to content

bughunt(uv): probe hosted rollback with PEP 735 include-group #9

bughunt(uv): probe hosted rollback with PEP 735 include-group

bughunt(uv): probe hosted rollback with PEP 735 include-group #9

Workflow file for this run

name: bughunt uv probe
on:
push:
branches: ["bughunt/uv/**"]
permissions:
contents: read
jobs:
probe:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
timeout-minutes: 45
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
with:
python-version: "3.11"
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- name: Build CLI
run: cargo build --release -p socket-patch-cli
- name: Write probe files
run: |
mkdir -p "$RUNNER_TEMP/mock" "$RUNNER_TEMP/work"
cat > "$RUNNER_TEMP/mock/mock.py" <<'PYEOF'
#!/usr/bin/env python3
"""Local mock of the Socket patch API serving one patched pypi package (six 1.16.0)."""
import base64, hashlib, io, json, os, re, sys, zipfile
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
HERE = os.path.dirname(os.path.abspath(__file__))
PORT = int(os.environ.get("MOCK_PORT", "18080"))
ORG = "test-org"
UUID = "0f6e7c1a-1111-4222-8333-444455556666"
TOKEN = "11111111-2222-4333-8444-555555555555"
SUFFIX = b"\n# SOCKET-PATCHED\nSOCKET_PATCHED = 1\n"
NAME, VER = "six", "1.16.0"
PURL = f"pkg:pypi/{NAME}@{VER}"
LEAF = f"{NAME}-{VER}-py2.py3-none-any.whl"
LOG = os.path.join(HERE, "requests.log")
def git_sha256(b):
return hashlib.sha256(b"blob %d\0" % len(b) + b).hexdigest()
def build():
src = zipfile.ZipFile(os.path.join(HERE, LEAF))
members = []
dist = f"{NAME}-{VER}.dist-info"
for info in src.infolist():
if info.filename == f"{dist}/RECORD":
continue
data = src.read(info.filename)
if info.filename == "six.py":
orig = data
data = data + SUFFIX
members.append((info.filename, data))
rec = ""
for n, d in members:
dig = base64.urlsafe_b64encode(hashlib.sha256(d).digest()).rstrip(b"=").decode()
rec += f"{n},sha256={dig},{len(d)}\n"
rec += f"{dist}/RECORD,,\n"
members.append((f"{dist}/RECORD", rec.encode()))
buf = io.BytesIO()
with zipfile.ZipFile(buf, "w", zipfile.ZIP_DEFLATED) as z:
for n, d in members:
zi = zipfile.ZipInfo(n, date_time=(2020, 1, 1, 0, 0, 0)); zi.compress_type = zipfile.ZIP_DEFLATED
z.writestr(zi, d)
return orig, orig + SUFFIX, buf.getvalue()
ORIG, PATCHED, WHEEL = build()
SHA = hashlib.sha256(WHEEL).hexdigest()
BASE = f"http://127.0.0.1:{PORT}"
ART_PATH = f"/patch/pypi/{NAME}/{VER}/{TOKEN}/{UUID}/{LEAF}"
ART = BASE + ART_PATH
VIEW = {
"uuid": UUID, "purl": PURL, "publishedAt": "Fri, 27 Mar 2026 00:00:00 GMT",
"files": {"six.py": {"beforeHash": git_sha256(ORIG), "afterHash": git_sha256(PATCHED),
"blobContent": base64.b64encode(PATCHED).decode()}},
"vulnerabilities": {"GHSA-uvbu-ildv-ex01": {"cves": ["CVE-2026-7101"], "summary": "s",
"severity": "high", "description": "d"}},
"description": "uv bughunt patch", "license": "MIT", "tier": "free",
}
class H(BaseHTTPRequestHandler):
def log_message(self, *a):
pass
def send(self, code, body, ctype="application/json"):
if not isinstance(body, bytes):
body = json.dumps(body).encode()
self.send_response(code)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def route(self):
with open(LOG, "a") as f:
f.write(f"{self.command} {self.path}\n")
p = self.path.split("?")[0]
n = int(self.headers.get("Content-Length") or 0)
body = self.rfile.read(n) if n else b""
if p == ART_PATH:
if self.command == "HEAD":
self.send_response(200); self.send_header("Content-Length", str(len(WHEEL))); self.end_headers(); return
return self.send(200, WHEEL, "application/octet-stream")
if re.fullmatch(rf"/v0/orgs/{ORG}/patches/batch", p) or p.endswith("/patch/batch"):
try:
comps = json.loads(body or b"{}").get("components", [])
except Exception:
comps = []
purls = [c.get("purl", "") for c in comps] if comps else [PURL]
if not any(pu.lower().startswith(f"pkg:pypi/{NAME}@{VER}") for pu in purls):
return self.send(200, {"packages": [], "canAccessPaidPatches": False})
return self.send(200, {"packages": [{"purl": PURL, "patches": [{
"uuid": UUID, "purl": PURL, "tier": "free", "cveIds": ["CVE-2026-7101"],
"ghsaIds": ["GHSA-uvbu-ildv-ex01"], "severity": "HIGH", "title": "uv bughunt"}]}],
"canAccessPaidPatches": False})
if "/patches/by-package/" in p or "/patch/by-package/" in p:
return self.send(200, {"patches": [{"uuid": UUID, "purl": PURL,
"publishedAt": "2026-09-01T00:00:00Z", "description": "uv bughunt",
"license": "MIT", "tier": "free", "vulnerabilities": {}}],
"canAccessPaidPatches": False})
if p.endswith("/patches/package") or p.endswith("/patch/package"):
return self.send(200, {"results": {UUID: {"status": "granted", "url": ART, "purl": PURL,
"artifacts": [{"kind": "tarball", "url": ART, "integrity": {"sha256": SHA, "sha512": "sha512-" + base64.b64encode(hashlib.sha512(WHEEL).digest()).decode()}}],
"registryOverride": None}}})
if p.endswith(f"/view/{UUID}"):
return self.send(200, VIEW)
if "/blob/" in p:
h = p.rsplit("/", 1)[1]
if h == git_sha256(PATCHED):
return self.send(200, PATCHED, "application/octet-stream")
if h == git_sha256(ORIG):
return self.send(200, ORIG, "application/octet-stream")
return self.send(404, {"error": "not found", "path": p})
do_GET = route
do_POST = route
do_HEAD = route
if __name__ == "__main__":
print(json.dumps({"art": ART, "sha": SHA, "patched_py_sha": hashlib.sha256(PATCHED).hexdigest(),
"orig_py_sha": hashlib.sha256(ORIG).hexdigest()}), flush=True)
ThreadingHTTPServer(("127.0.0.1", PORT), H).serve_forever()
PYEOF
cat > "$RUNNER_TEMP/repro.sh" <<'SHEOF'
#!/usr/bin/env bash
# usage: repro_incl.sh <uv-binary> <workdir> <undo: rollback|remove|vendored>
set -u
UVB=$1; D=$2; MODE=$3; mkdir -p "$D" && cd "$D" || exit 1
cat > pyproject.toml <<'TOML'
[project]
name = "uvp"
version = "0.1.0"
requires-python = ">=3.9"
dependencies = ["python-dateutil==2.8.2"]
[dependency-groups]
test = ["six==1.16.0"]
dev = ["idna==3.7", {include-group = "test"}]
TOML
"$UVB" lock -q; cp uv.lock uv.lock.orig; cp pyproject.toml pyproject.toml.orig
git init -q . && git add -A && git -c user.email=a@b -c user.name=a commit -qm init
six(){ "$UVB" run -q --no-sync python -c "import six;print('PATCHED' if getattr(six,'SOCKET_PATCHED',0) else 'PRISTINE')" 2>&1 | tail -1; }
if [ "$MODE" = vendored ]; then $SP scan --mode vendored --json --yes $API > scan.json 2>/dev/null; else $SP scan --mode hosted --json --yes $API > scan.json 2>/dev/null; fi
echo "scan rc=$?"; rm -rf .venv; UV_CACHE_DIR=$(mktemp -d) "$UVB" sync -q --locked --all-groups; echo "after scan: $(six)"
case $MODE in rollback) $SP rollback --json --yes $API > undo.json 2>/dev/null;; remove) $SP remove pkg:pypi/six@1.16.0 --json --yes $API > undo.json 2>/dev/null;; vendored) $SP vendor --revert --json --yes $API > undo.json 2>/dev/null;; esac
rc=$?; python3 -c "import json;d=json.load(open('undo.json'));print('$MODE rc=$rc status=%s err=%s'%(d['status'],json.dumps(d.get('hosted',{}).get('failed') or d.get('error') or [e.get('errorCode') for e in d.get('events',[])])[:400]))"
cmp -s uv.lock uv.lock.orig && cmp -s pyproject.toml pyproject.toml.orig && echo "files byte-identical to pre-scan" || echo "files NOT restored"
rm -rf .venv; UV_CACHE_DIR=$(mktemp -d) "$UVB" sync -q --locked --all-groups; echo "after undo: $(six)"
SHEOF
python -m pip download --no-deps six==1.16.0 -d "$RUNNER_TEMP/mock"
- name: Run probe
run: |
python "$RUNNER_TEMP/mock/mock.py" > "$RUNNER_TEMP/mock.out" 2>&1 &
sleep 3; cat "$RUNNER_TEMP/mock.out"
export SP="$PWD/target/release/socket-patch"; [ -f "$SP.exe" ] && SP="$SP.exe"
export 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"
export SOCKET_TELEMETRY_DISABLED=1 SOCKET_NO_CONFIG=1
for v in 0.5.31 0.12.21; do
python -m venv "$RUNNER_TEMP/uv-$v"
if [ -d "$RUNNER_TEMP/uv-$v/Scripts" ]; then B="$RUNNER_TEMP/uv-$v/Scripts"; else B="$RUNNER_TEMP/uv-$v/bin"; fi
"$B/python" -m pip install -q "uv==$v"
UVB="$B/uv"; [ -f "$UVB.exe" ] && UVB="$UVB.exe"
echo "### RESULT uv $v on ${{ matrix.os }}"
for m in rollback remove vendored; do echo "## $m"; bash "$RUNNER_TEMP/repro.sh" "$UVB" "$RUNNER_TEMP/work/r-$v-$m" $m || true; done
done