Skip to content

Commit b649cab

Browse files
committed
bughunt probe: yarn classic GitHub shorthand (real codeload)
1 parent 9c43dfc commit b649cab

1 file changed

Lines changed: 161 additions & 0 deletions

File tree

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
name: bughunt yarn-classic probe
2+
on:
3+
push:
4+
branches: ['bughunt/yarn-classic/**']
5+
permissions:
6+
contents: read
7+
jobs:
8+
probe:
9+
runs-on: ${{ matrix.os }}
10+
timeout-minutes: 45
11+
strategy:
12+
fail-fast: false
13+
matrix:
14+
os: [ubuntu-latest, macos-latest, windows-latest]
15+
yarn: ['1.7.0', '1.10.1', '1.22.22']
16+
defaults:
17+
run:
18+
shell: bash
19+
steps:
20+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
21+
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
22+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
23+
with:
24+
node-version: '22.x'
25+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
26+
with:
27+
python-version: '3.12'
28+
- name: Build socket-patch
29+
run: cargo build --release -p socket-patch-cli
30+
- name: Install yarn
31+
run: npm install --prefix "$RUNNER_TEMP/y" yarn@${{ matrix.yarn }} && node "$RUNNER_TEMP/y/node_modules/yarn/bin/yarn.js" --version
32+
- name: Write harness
33+
run: |
34+
mkdir -p "$RUNNER_TEMP/h"
35+
cat > "$RUNNER_TEMP/h/mock.py" <<'MOCKEOF'
36+
#!/usr/bin/env python3
37+
"""Minimal Socket patch API mock: left-pad@1.3.0 patch (prepends a marker to index.js)."""
38+
import base64, gzip, hashlib, io, json, os, sys, tarfile, urllib.request
39+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
40+
41+
PORT = int(sys.argv[1]) if len(sys.argv) > 1 else 8787
42+
SRC = sys.argv[2] if len(sys.argv) > 2 else None # optional local left-pad-1.3.0.tgz
43+
BASE = f"http://127.0.0.1:{PORT}"
44+
NAME, VER = "left-pad", "1.3.0"
45+
PURL = f"pkg:npm/{NAME}@{VER}"
46+
UUID = "11111111-2222-4333-8444-555555555555"
47+
MARKER = b"/* SOCKET-PATCHED-MOCK */\n"
48+
49+
raw = open(SRC, "rb").read() if SRC else urllib.request.urlopen(
50+
f"https://registry.npmjs.org/{NAME}/-/{NAME}-{VER}.tgz").read()
51+
files = {}
52+
with tarfile.open(fileobj=io.BytesIO(raw), mode="r:gz") as t:
53+
for m in t.getmembers():
54+
if m.isfile():
55+
files[m.name] = t.extractfile(m).read()
56+
orig = files["package/index.js"]
57+
patched = MARKER + orig
58+
files["package/index.js"] = patched
59+
buf = io.BytesIO()
60+
with tarfile.open(fileobj=buf, mode="w", format=tarfile.USTAR_FORMAT) as t:
61+
for n in sorted(files):
62+
ti = tarfile.TarInfo(n); ti.size = len(files[n]); ti.mtime = 0; ti.mode = 0o644
63+
t.addfile(ti, io.BytesIO(files[n]))
64+
gz = io.BytesIO()
65+
with gzip.GzipFile(fileobj=gz, mode="wb", mtime=0) as g:
66+
g.write(buf.getvalue())
67+
TGZ = gz.getvalue()
68+
69+
def gsha(b): return hashlib.sha256(b"blob %d\0" % len(b) + b).hexdigest()
70+
BLOBS = {gsha(orig): orig, gsha(patched): patched}
71+
SRI = "sha512-" + base64.b64encode(hashlib.sha512(TGZ).digest()).decode()
72+
SHA1 = hashlib.sha1(TGZ).hexdigest()
73+
ART = f"{BASE}/artifacts/{UUID}/{NAME}-{VER}.tgz"
74+
SUMMARY = {"uuid": UUID, "purl": PURL, "tier": "free", "cveIds": ["CVE-2026-0001"],
75+
"ghsaIds": ["GHSA-aaaa-bbbb-cccc"], "severity": "high", "title": "mock"}
76+
VIEW = {"uuid": UUID, "purl": PURL, "publishedAt": "2026-01-01T00:00:00Z",
77+
"files": {"package/index.js": {"beforeHash": gsha(orig), "afterHash": gsha(patched),
78+
"blobContent": base64.b64encode(patched).decode()}},
79+
"vulnerabilities": {"GHSA-aaaa-bbbb-cccc": {"cves": ["CVE-2026-0001"], "summary": "s",
80+
"severity": "high", "description": "d"}},
81+
"description": "x", "license": "MIT", "tier": "free"}
82+
GRANT = {"status": "granted", "purl": PURL, "url": ART,
83+
"artifacts": [{"kind": "tarball", "url": ART, "integrity": {"sha512": SRI, "sha1": SHA1}}],
84+
"registryOverride": None}
85+
86+
class H(BaseHTTPRequestHandler):
87+
def log_message(self, *a): sys.stderr.write("MOCK %s\n" % (a[0] % a[1:]))
88+
def send(self, code, body, ctype="application/json"):
89+
if not isinstance(body, bytes): body = json.dumps(body).encode()
90+
self.send_response(code); self.send_header("Content-Type", ctype)
91+
self.send_header("Content-Length", str(len(body))); self.end_headers(); self.wfile.write(body)
92+
def do_GET(self):
93+
p = self.path.split("?")[0]
94+
if p.startswith(f"/artifacts/{UUID}/"): return self.send(200, TGZ, "application/octet-stream")
95+
if "/by-package/" in p:
96+
return self.send(200, {"patches": [dict(SUMMARY, publishedAt="2026-01-01T00:00:00Z",
97+
description="x", license="MIT", vulnerabilities={})], "canAccessPaidPatches": False})
98+
if p.endswith(f"/view/{UUID}"): return self.send(200, VIEW)
99+
if "/blob/" in p:
100+
h = p.rsplit("/", 1)[1]
101+
return self.send(200, BLOBS[h], "application/octet-stream") if h in BLOBS else self.send(404, {})
102+
self.send(404, {"error": "nf"})
103+
def do_POST(self):
104+
p = self.path.split("?")[0]
105+
body = self.rfile.read(int(self.headers.get("Content-Length") or 0)).decode("utf-8", "replace")
106+
if p.endswith("/patches/batch") or p.endswith("/patch/batch"):
107+
pk = [{"purl": PURL, "patches": [SUMMARY]}] if (NAME + "@" + VER) in body.replace("%40", "@") else []
108+
return self.send(200, {"packages": pk, "canAccessPaidPatches": False})
109+
if p.endswith("/package"):
110+
return self.send(200, {"results": {UUID: GRANT} if UUID in body else {}})
111+
self.send(404, {"error": "nf"})
112+
113+
if __name__ == "__main__":
114+
print(json.dumps({"sri": SRI, "sha1": SHA1, "after": gsha(patched)}), flush=True)
115+
ThreadingHTTPServer(("127.0.0.1", PORT), H).serve_forever()
116+
MOCKEOF
117+
cat > "$RUNNER_TEMP/h/gh.sh" <<'PROBEEOF'
118+
#!/usr/bin/env bash
119+
# usage: gh.sh <socket-patch bin> <dep spec> <yarn cmd...>
120+
set -u
121+
BIN="$1"; DEP="$2"; shift 2; Y="$*"
122+
W="${PROBE_WORK:-$PWD/work}"; rm -rf "$W"; mkdir -p "$W"
123+
API="--api-url http://127.0.0.1:8787 --org o --api-token x"
124+
export SOCKET_PATCH_SERVER_URL=http://127.0.0.1:8787
125+
MARK='SOCKET-PATCHED-MOCK'
126+
res(){ echo "RESULT $1 $2 ${3:-}"; }
127+
patched(){ grep -q "$MARK" "$1/node_modules/left-pad/index.js" 2>/dev/null; }
128+
fresh(){ rm -rf node_modules; $Y cache clean > /dev/null 2>&1; $Y install --frozen-lockfile --no-progress --cache-folder "$W/c-$1" > "$W/$1.log" 2>&1; local e=$?; [ $e -ne 0 ] && tail -15 "$W/$1.log"; return $e; }
129+
mk(){ local d="$W/$1"; mkdir -p "$d"; cd "$d"
130+
printf '{"name":"p","version":"1.0.0","private":true,"dependencies":{"left-pad":"%s"}}\n' "$DEP" > package.json
131+
$Y install --no-progress --cache-folder "$W/c0-$1" > "$W/$1.install.log" 2>&1 || { tail -20 "$W/$1.install.log"; res "$1" BLOCKED install; return 1; }
132+
echo "--- $1 original lock"; cat yarn.lock
133+
cp yarn.lock "$W/$1.lock.orig"; }
134+
for M in hosted vendored; do
135+
if mk $M; then
136+
$BIN scan --mode $M --json --yes $API > "$W/$M.scan.json" 2>&1; echo "$M scan exit $?"
137+
python3 -c "import json,sys;t=open(sys.argv[1]).read();d=json.loads(t[t.index('{'):]);print(json.dumps(d.get('redirect') or d.get('vendor'))[:1500])" "$W/$M.scan.json" 2>/dev/null || head -c 1500 "$W/$M.scan.json"
138+
echo "--- $M lock after scan"; cat yarn.lock
139+
cmp -s yarn.lock "$W/$M.lock.orig" && res $M-rewired FAIL unchanged || res $M-rewired PASS
140+
if fresh $M-f1; then patched . && res $M-frozen-patched PASS || res $M-frozen-patched FAIL unpatched; else res $M-frozen-install FAIL; fi
141+
$BIN vex --output v.json --product pkg:npm/p@1.0.0 $API > "$W/$M.vex.log" 2>&1; echo "vex exit $?"
142+
if grep -q 'left-pad@1.3.0' v.json 2>/dev/null; then patched . && res $M-vex PASS attests-patched || res $M-vex FAIL attests-unpatched; else patched . && res $M-vex FAIL omitted-but-patched || res $M-vex PASS omitted; tail -3 "$W/$M.vex.log"; fi
143+
$BIN rollback --json --yes $API > "$W/$M.rb.json" 2>&1; echo "rollback exit $?"; head -c 800 "$W/$M.rb.json"; echo
144+
echo "--- $M lock after rollback"; cat yarn.lock
145+
cmp -s yarn.lock "$W/$M.lock.orig" && res $M-rollback-byte-exact PASS || res $M-rollback-byte-exact DIFF
146+
if fresh $M-f2; then patched . && res $M-post-rollback FAIL still-patched || res $M-post-rollback PASS; else res $M-post-rollback-install FAIL; fi
147+
cd "$W"
148+
fi
149+
done
150+
echo DONE
151+
PROBEEOF
152+
- name: Probe
153+
run: |
154+
python "$RUNNER_TEMP/h/mock.py" 8787 > "$RUNNER_TEMP/mock.log" 2>&1 &
155+
for i in $(seq 1 60); do curl -s -o /dev/null -XPOST http://127.0.0.1:8787/x/package -d '{}' && break; sleep 1; done
156+
BIN="$GITHUB_WORKSPACE/target/release/socket-patch"; [ -f "$BIN.exe" ] && BIN="$BIN.exe"
157+
for DEP in 'stevemao/left-pad#v1.3.0' 'github:stevemao/left-pad#v1.3.0' 'https://github.com/stevemao/left-pad/archive/v1.3.0.tar.gz'; do
158+
echo "########## DEP $DEP"
159+
cd "$RUNNER_TEMP" && PROBE_WORK="$RUNNER_TEMP/w" bash "$RUNNER_TEMP/h/gh.sh" "$BIN" "$DEP" node "$RUNNER_TEMP/y/node_modules/yarn/bin/yarn.js" 2>&1 | grep -v '^Warning: --api-token\|Got: x' | sed "s|^RESULT |RESULT [$DEP] |" | tee -a "$RUNNER_TEMP/probe.log"
160+
done
161+
echo "===== SUMMARY ${{ matrix.os }} yarn ${{ matrix.yarn }}"; grep '^RESULT' "$RUNNER_TEMP/probe.log"

0 commit comments

Comments
 (0)