Skip to content

Commit 11258b0

Browse files
committed
bughunt(yarn-classic): probe offline mirror + git-sourced deps on macOS/Windows
1 parent f6b7fb9 commit 11258b0

1 file changed

Lines changed: 149 additions & 0 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
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+
name: probe ${{ matrix.os }} yarn ${{ matrix.yarn }}
10+
runs-on: ${{ matrix.os }}
11+
timeout-minutes: 45
12+
strategy:
13+
fail-fast: false
14+
matrix:
15+
os: [ubuntu-latest, macos-latest, windows-latest]
16+
yarn: ['1.10.1', '1.22.22']
17+
defaults:
18+
run:
19+
shell: bash
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
23+
with:
24+
persist-credentials: false
25+
- name: Install Rust
26+
run: rustup show
27+
- name: Setup Node.js
28+
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
29+
with:
30+
node-version: '24.x'
31+
- name: Build socket-patch
32+
run: cargo build -p socket-patch-cli
33+
- name: Probe (offline mirror + git-sourced dep)
34+
env:
35+
COREPACK_ENABLE_DOWNLOAD_PROMPT: '0'
36+
YARN_V: ${{ matrix.yarn }}
37+
run: |
38+
cat > "$RUNNER_TEMP/probe.sh" <<'PROBE_EOF'
39+
#!/usr/bin/env bash
40+
# probe.sh SP_BIN YARN_VERSION — prints RESULT lines
41+
set -u
42+
SP=$1; V=$2; W=$(pwd)/probe-work; rm -rf "$W"; mkdir -p "$W"; cd "$W"
43+
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
44+
PY=python3; command -v python3 >/dev/null && python3 -c 1 2>/dev/null || PY=python
45+
PORT=18555; API=http://127.0.0.1:$PORT
46+
winpath() { if command -v cygpath >/dev/null; then cygpath -m "$1"; else echo "$1"; fi; }
47+
Y() { corepack yarn@$V "$@"; }
48+
sp() { "$SP" "$@" --api-url $API --org test-org --api-token fake; }
49+
marker() { if [ -f "$1" ] && head -c 22 "$1" | grep -q SOCKET-PATCHED; then echo PATCHED; elif [ -f "$1" ]; then echo UNPATCHED; else echo MISSING; fi; }
50+
fresh() { rm -rf "$2"; mkdir -p "$2"; (cd "$1" && tar cf - --exclude=node_modules --exclude=.c . ) | (cd "$2" && tar xf -); }
51+
# --- patched left-pad tarball + mock API ---
52+
mkdir pk && (cd pk && npm pack left-pad@1.3.0 --silent >/dev/null)
53+
cat > mk.py <<'PY'
54+
import tarfile,io,hashlib,base64,json,sys
55+
src=tarfile.open("pk/left-pad-1.3.0.tgz"); buf=io.BytesIO(); dst=tarfile.open(fileobj=buf,mode="w:gz")
56+
for m in src.getmembers():
57+
if not m.isfile(): continue
58+
d=src.extractfile(m).read(); rel=m.name.split("/",1)[1]
59+
if rel=="index.js": b=d; d=b"/* SOCKET-PATCHED */\n"+d; a=d
60+
t=tarfile.TarInfo("package/"+rel); t.mode=0o644; t.mtime=0; t.size=len(d); dst.addfile(t,io.BytesIO(d))
61+
dst.close(); tgz=buf.getvalue(); open("pk/patched.tgz","wb").write(tgz)
62+
g=lambda d: hashlib.sha256(b"blob %d\0"%len(d)+d).hexdigest()
63+
json.dump({"org":"test-org","pkgs":[{"name":"left-pad","version":"1.3.0","uuid":"7c8d9e0f-1a2b-4c3d-8e4f-5a6b7c8d9e0f","tgz":"pk/patched.tgz","sha1":hashlib.sha1(tgz).hexdigest(),"sri":"sha512-"+base64.b64encode(hashlib.sha512(tgz).digest()).decode(),"files":{"package/index.js":{"before":g(b),"after":g(a),"blob":base64.b64encode(a).decode()}}}]},open("cfg.json","w"))
64+
PY
65+
$PY mk.py
66+
cat > mock.py <<'PY'
67+
#!/usr/bin/env python3
68+
"""Hold-open mock Socket patch API. Usage: mock.py PORT CONFIG.json
69+
CONFIG: {"org":"test-org","token":"...","pkgs":[{"name","version","uuid","tgz","files":{"package/index.js":{"before":hex,"after":hex}}, "sha1","sri"}]}"""
70+
import json, sys, os, re, urllib.parse
71+
from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler
72+
PORT=int(sys.argv[1]); CFG=json.load(open(sys.argv[2]))
73+
ORG=CFG["org"]; TOKEN=CFG.get("token","33333333-3333-4333-8333-333333333333")
74+
LOG=open(sys.argv[2]+".log","a",buffering=1)
75+
def purl(p):
76+
n=p["name"]
77+
pass
78+
return f"pkg:npm/{n}@{p['version']}"
79+
def hurl(p):
80+
return f"http://127.0.0.1:{PORT}/patch/npm/{p['name']}/{p['version']}/{TOKEN}/{p['uuid']}/{p['name'].split('/')[-1]}-{p['version']}.tgz"
81+
class H(BaseHTTPRequestHandler):
82+
def log_message(self,*a): pass
83+
def send(self,obj,code=200,raw=None):
84+
b=raw if raw is not None else json.dumps(obj).encode()
85+
self.send_response(code); self.send_header("Content-Length",str(len(b)))
86+
self.send_header("Content-Type","application/octet-stream" if raw is not None else "application/json"); self.end_headers(); self.wfile.write(b)
87+
def body(self):
88+
n=int(self.headers.get("Content-Length") or 0); return self.rfile.read(n).decode() if n else ""
89+
def summary(self,p):
90+
return {"uuid":p["uuid"],"purl":purl(p),"tier":"free","cveIds":[],"ghsaIds":["GHSA-x-"+p["uuid"][:4]],"severity":"high","title":"fixture "+p["name"],
91+
"publishedAt":"2026-01-01T00:00:00Z","description":"x","license":"MIT","vulnerabilities":{}}
92+
def do_POST(self):
93+
b=self.body(); LOG.write(f"POST {self.path} {b[:300]}\n")
94+
if self.path.endswith("/patches/batch"):
95+
pk=[{"purl":purl(p),"patches":[self.summary(p)]} for p in CFG["pkgs"] if purl(p) in urllib.parse.unquote(b) or purl(p) in b]
96+
return self.send({"packages":pk,"canAccessPaidPatches":False})
97+
if self.path.endswith("/patches/package"):
98+
res={}
99+
for p in CFG["pkgs"]:
100+
if p["uuid"] in b:
101+
u=hurl(p); res[p["uuid"]]={"status":"granted","url":u,"purl":purl(p),"artifacts":[{"kind":"tarball","url":u,"integrity":{"sha512":p["sri"],"sha1":p["sha1"]}}],"registryOverride":None}
102+
return self.send({"results":res})
103+
self.send({"error":"nf"},404)
104+
def do_GET(self):
105+
LOG.write(f"GET {self.path}\n")
106+
m=re.search(r"/patches/by-package/(.+)$",self.path)
107+
if m:
108+
q=urllib.parse.unquote(m.group(1))
109+
return self.send({"patches":[self.summary(p) for p in CFG["pkgs"] if purl(p)==q or urllib.parse.unquote(purl(p))==q],"canAccessPaidPatches":False})
110+
m=re.search(r"/patches/view/([0-9a-f-]+)$",self.path)
111+
if m:
112+
for p in CFG["pkgs"]:
113+
if p["uuid"]==m.group(1):
114+
files={k:{"beforeHash":v["before"],"afterHash":v["after"],**({"blobContent":v["blob"]} if "blob" in v else {})} for k,v in p["files"].items()}
115+
return self.send({"uuid":p["uuid"],"purl":purl(p),"publishedAt":"2026-01-01T00:00:00Z","files":files,
116+
"vulnerabilities":{"GHSA-x-"+p["uuid"][:4]:{"cves":["CVE-2026-1"],"summary":"s","severity":"high","description":"d"}},"description":"x","license":"MIT","tier":"free"})
117+
if self.path.startswith("/patch/"):
118+
for p in CFG["pkgs"]:
119+
if p["uuid"] in self.path:
120+
return self.send(None,raw=open(p["tgz"],"rb").read())
121+
# blob fetch
122+
self.send({"error":"nf"},404)
123+
ThreadingHTTPServer(("127.0.0.1",PORT),H).serve_forever()
124+
125+
PY
126+
$PY mock.py $PORT cfg.json > mock.out 2>&1 &
127+
MOCKPID=$!; sleep 2
128+
result() { echo "RESULT os=${RUNNER_OS:-local} yarn=$V $*"; }
129+
# --- case 1: hosted + yarn-offline-mirror ---
130+
P=$W/mir; mkdir -p $P; echo '{"name":"b","version":"1.0.0","private":true,"dependencies":{"left-pad":"1.3.0"}}' > $P/package.json
131+
printf 'yarn-offline-mirror "./mirror"\n' > $P/.yarnrc
132+
(cd $P && YARN_CACHE_FOLDER=$P/.c Y install --no-progress >/dev/null 2>&1)
133+
fresh $P $W/mirctl; (cd $W/mirctl && YARN_CACHE_FOLDER=$W/cc0 Y install --frozen-lockfile --no-progress >/dev/null 2>&1); result "case=mirror-control(no-socket-patch) rc=$? $(marker $W/mirctl/node_modules/left-pad/index.js)"
134+
sp scan --mode hosted --json --yes --cwd "$(winpath $P)" > $W/mirscan.json 2>/dev/null; result "case=mirror-hosted-scan rc=$? $(tr -d '\n' < $W/mirscan.json | grep -o '"redirected": *[0-9]*' | head -1) warnings=$(grep -o '"code": *"[a-z_]*"' $W/mirscan.json | tr -d ' \n')"
135+
for mode in "" "--offline"; do fresh $P $W/mirf; (cd $W/mirf && YARN_CACHE_FOLDER=$W/cc$RANDOM Y install --frozen-lockfile $mode --no-progress > $W/mirf.log 2>&1); rc=$?; result "case=mirror-hosted-fresh-install${mode} rc=$rc $(marker $W/mirf/node_modules/left-pad/index.js) err=$(grep -o 'Integrity check failed\|offline mode' $W/mirf.log | head -1 | tr ' ' _)"; done
136+
# --- case 2: git-sourced dep, hosted & vendored ---
137+
G=$W/lpgit; mkdir -p $G; tar xzf pk/left-pad-1.3.0.tgz -C $G --strip-components=1; (cd $G && git init -q && git add -A && git -c user.email=a@b -c user.name=a commit -qm v && git tag v1.3.0)
138+
for m in hosted vendored; do
139+
P=$W/git-$m; mkdir -p $P; echo "{\"name\":\"g\",\"version\":\"1.0.0\",\"private\":true,\"dependencies\":{\"left-pad\":\"git+file:///$(winpath $G | sed 's#^/##')#v1.3.0\"}}" > $P/package.json
140+
(cd $P && YARN_CACHE_FOLDER=$P/.c Y install --no-progress > $P/../i-$m.log 2>&1) || result "case=git-$m fixture-install-failed $(tail -1 $P/../i-$m.log)"
141+
extra=""; [ $m = vendored ] && extra="--detached --vendor-source build"
142+
sp scan --mode $m $extra --json --yes --cwd "$(winpath $P)" --vex "$(winpath $W/v-$m.json)" > $W/g-$m.json 2>/dev/null; result "case=git-$m-scan rc=$? status=$(grep -o '"status": *"[a-z_]*"' $W/g-$m.json | head -1 | tr -d ' ') inrun-vex=$(grep -o '"not_affected"' $W/v-$m.json | head -1)"
143+
fresh $P $W/gf-$m; (cd $W/gf-$m && YARN_CACHE_FOLDER=$W/gc$RANDOM Y install --frozen-lockfile --no-progress > $W/gf-$m.log 2>&1); rc=$?; result "case=git-$m-fresh-install rc=$rc $(marker $W/gf-$m/node_modules/left-pad/index.js) err=$(grep -m1 '^error' $W/gf-$m.log | cut -c1-80 | tr ' ' _)"
144+
done
145+
kill $MOCKPID 2>/dev/null
146+
PROBE_EOF
147+
BIN="$PWD/target/debug/socket-patch"; [ -f "$BIN.exe" ] && BIN="$BIN.exe"
148+
cd "$RUNNER_TEMP" && bash probe.sh "$BIN" "$YARN_V" 2>&1 | tee probe.log | grep RESULT || true
149+
echo '--- mock log ---'; cat probe-work/cfg.json.log 2>/dev/null | cut -c1-200 | tail -20

0 commit comments

Comments
 (0)