Repository navigation
bughunt(bun): probe -g with custom Bun global dirs #5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: bughunt-bun | |
| on: | |
| push: | |
| branches: ['bughunt/bun/**'] | |
| permissions: | |
| contents: read | |
| jobs: | |
| build: | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 | |
| with: | |
| key: bun-native | |
| save-if: false | |
| - run: cargo build --locked --release -p socket-patch-cli | |
| - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 | |
| with: | |
| name: cli-${{ matrix.os }} | |
| path: | | |
| target/release/socket-patch | |
| target/release/socket-patch.exe | |
| probe: | |
| needs: build | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| os: [ubuntu-latest, macos-latest, windows-latest] | |
| bun: ['1.1.45', '1.2.23', '1.3.14', '1.4.2'] | |
| runs-on: ${{ matrix.os }} | |
| timeout-minutes: 30 | |
| steps: | |
| - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4 | |
| with: | |
| name: cli-${{ matrix.os }} | |
| path: cli | |
| - name: probe | |
| shell: bash | |
| run: | | |
| chmod +x cli/socket-patch* || true | |
| cat > probe.py <<'PY' | |
| # Bun global (-g) probe: real `bun add -g`, a local mock patch API, socket-patch -g verbs. | |
| import base64, hashlib, json, os, platform, shutil, subprocess, sys, tarfile, tempfile, threading, urllib.parse, io, glob | |
| from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | |
| CLI, BUNVER = os.path.abspath(sys.argv[1]), sys.argv[2] | |
| WIN = os.name == 'nt' | |
| ROOT = tempfile.mkdtemp(prefix='bun g ü ') # space + unicode in every path | |
| def sh(cmd, env, cwd=ROOT, check=False): | |
| r = subprocess.run(cmd, env=env, cwd=cwd, capture_output=True, text=True, encoding='utf-8', errors='replace') | |
| if check and r.returncode: print('FAILED', cmd, r.stdout, r.stderr); sys.exit(3) | |
| return r | |
| def gh(b): return hashlib.sha256(f'blob {len(b)}\0'.encode()+b).hexdigest() | |
| base_env = dict(os.environ, SOCKET_NO_UPDATE_CHECK='1', SOCKET_TELEMETRY_DISABLED='1', SOCKET_PROXY_URL='http://127.0.0.1:8787') | |
| for k in ('BUN_INSTALL', 'BUN_INSTALL_BIN', 'BUN_INSTALL_GLOBAL_DIR'): base_env.pop(k, None) | |
| npm = shutil.which('npm') | |
| # fetch pristine bytes + the bun binary | |
| pk = os.path.join(ROOT, 'pk'); os.makedirs(pk) | |
| plat = {'Linux': 'linux', 'Darwin': 'darwin', 'Windows': 'windows'}[platform.system()] | |
| arch = 'aarch64' if platform.machine().lower() in ('arm64', 'aarch64') else 'x64' | |
| for spec in ('semver@7.6.0', 'is-number@7.0.0', f'@oven/bun-{plat}-{arch}@{BUNVER}'): | |
| sh([npm, 'pack', spec, '--silent'], base_env, cwd=pk, check=True) | |
| for t in glob.glob(os.path.join(pk, '*.tgz')): | |
| d = os.path.join(pk, os.path.basename(t)[:-4]); tarfile.open(t).extractall(d) | |
| def pristine(name, rel): return open(glob.glob(os.path.join(pk, name + '*', 'package', rel))[0], 'rb').read() | |
| bunbin = glob.glob(os.path.join(pk, 'oven-bun-*', 'package', 'bin', 'bun*'))[0] | |
| P = {}; BLOBS = {} | |
| def add(uuid, purl, rel, before): | |
| after = before + ('\nglobalThis.__SOCKET_PATCHED = (globalThis.__SOCKET_PATCHED||[]).concat(["%s"]);\n' % purl).encode() | |
| BLOBS[gh(before)] = before; BLOBS[gh(after)] = after | |
| P[uuid] = dict(uuid=uuid, purl=purl, publishedAt='Fri, 27 Mar 2026 19:12:42 GMT', | |
| files={'package/' + rel: dict(beforeHash=gh(before), afterHash=gh(after), blobContent=base64.b64encode(after).decode(), beforeBlobContent=base64.b64encode(before).decode())}, | |
| vulnerabilities={'GHSA-xxxx-yyyy-zzzz': dict(cves=['CVE-2099-0001'], summary='t', severity='high', description='t')}, | |
| description='mock', license='MIT', tier='free') | |
| U1, U2 = '11111111-1111-4111-8111-111111111111', '22222222-2222-4222-8222-222222222222' | |
| add(U1, 'pkg:npm/semver@7.6.0', 'bin/semver.js', pristine('semver', 'bin/semver.js')) | |
| add(U2, 'pkg:npm/is-number@7.0.0', 'index.js', pristine('is-number', 'index.js')) | |
| BYPURL = {p['purl']: p for p in P.values()} | |
| class H(BaseHTTPRequestHandler): | |
| def log_message(self, *a): pass | |
| def send(self, code, obj=None, raw=None): | |
| body = raw if raw is not None else json.dumps(obj).encode() | |
| self.send_response(code); self.send_header('Content-Length', str(len(body))); self.end_headers(); self.wfile.write(body) | |
| def do_POST(self): | |
| body = json.loads(self.rfile.read(int(self.headers.get('Content-Length', 0))) or b'{}') | |
| out = [] | |
| for c in body.get('components', []): | |
| p = BYPURL.get(c['purl']) | |
| if p: out.append(dict(purl=p['purl'], patches=[dict(uuid=p['uuid'], purl=p['purl'], tier='free', cveIds=['CVE-2099-0001'], ghsaIds=['GHSA-xxxx-yyyy-zzzz'], severity='high', title='mock', publishedAt=p['publishedAt'])])) | |
| self.send(200, dict(packages=out, canAccessPaidPatches=False)) | |
| def do_GET(self): | |
| path = urllib.parse.unquote(self.path); last = path.rsplit('/', 1)[1] | |
| if '/view/' in path: return self.send(200, P[last]) if last in P else self.send(404, {}) | |
| if '/blob/' in path: return self.send(200, raw=BLOBS[last]) if last in BLOBS else self.send(404, {}) | |
| if '/by-package/' in path: | |
| p = BYPURL.get(path.split('/by-package/', 1)[1]) | |
| return self.send(200, dict(patches=[dict(uuid=p['uuid'], purl=p['purl'], publishedAt=p['publishedAt'], description='mock', license='MIT', tier='free', vulnerabilities=p['vulnerabilities'])] if p else [], canAccessPaidPatches=False)) | |
| self.send(404, {}) | |
| threading.Thread(target=ThreadingHTTPServer(('127.0.0.1', 8787), H).serve_forever, daemon=True).start() | |
| results = [] | |
| def cell(name, ok, detail=''): | |
| results.append((name, ok)); print(f"CELL {plat}/{BUNVER}/{name}: {'PASS' if ok else 'FAIL'} {detail}", flush=True) | |
| def layout(label, extra, via_npm=False): | |
| home = os.path.join(ROOT, label); work = os.path.join(home, 'work'); os.makedirs(work) | |
| env = dict(base_env); env['BUN_INSTALL'] = os.path.join(home, '.bun') | |
| bindir = os.path.join(home, '.bun', 'bin'); os.makedirs(bindir) | |
| if via_npm: # npm-installed bun: only a bun.cmd / bun shim on PATH | |
| prefix = os.path.join(home, 'npmprefix') | |
| sh([npm, 'install', '-g', '--prefix', prefix, f'bun@{BUNVER}'], env, check=True) | |
| shimdir = prefix if WIN else os.path.join(prefix, 'bin') | |
| print(' npm shim dir:', os.listdir(shimdir)) | |
| env['PATH'] = shimdir + os.pathsep + os.path.dirname(shutil.which('node')) + os.pathsep + (r'C:\Windows\System32' if WIN else '/usr/bin:/bin') | |
| else: | |
| shutil.copy(bunbin, bindir); env['PATH'] = bindir + os.pathsep + env['PATH'] | |
| for k, v in extra.items(): env[k] = os.path.join(home, v) | |
| bun = shutil.which('bun', path=env['PATH']) | |
| r = sh([bun, 'add', '-g', 'semver@7.6.0', 'is-number@7.0.0'], env, cwd=work) | |
| print(f' [{label}] bun add -g rc={r.returncode}; bun pm bin -g =', sh([bun, 'pm', 'bin', '-g'], env, cwd=work).stdout.strip()) | |
| found = [os.path.join(dp, 'semver.js') for dp, dn, fn in os.walk(home) if 'semver.js' in fn and dp.endswith(os.path.join('node_modules', 'semver', 'bin')) and 'cache' not in dp and 'npmprefix' not in dp] | |
| gdir = os.path.dirname(os.path.dirname(os.path.dirname(found[0]))) | |
| print(f' [{label}] global node_modules =', gdir) | |
| return env, work, gdir | |
| def run_layout(label, extra, via_npm=False): | |
| env, work, gdir = layout(label, extra, via_npm) | |
| files = [os.path.join(gdir, 'semver', 'bin', 'semver.js'), os.path.join(gdir, 'is-number', 'index.js')] | |
| orig = [open(f, 'rb').read() for f in files] | |
| r = sh([CLI, 'scan', '-g', '--json'], env, cwd=work) | |
| try: purls = sorted(p['purl'] for p in json.loads(r.stdout)['packages']) | |
| except Exception: purls = None | |
| cell(f'{label}/scan-g-report', purls == ['pkg:npm/is-number@7.0.0', 'pkg:npm/semver@7.6.0'], f'rc={r.returncode} purls={purls}') | |
| r = sh([CLI, 'scan', '-g', '--mode', 'hosted'], env, cwd=work) | |
| cell(f'{label}/scan-g-hosted-refuses', r.returncode != 0 and not os.path.exists(os.path.join(work, '.socket')), f'rc={r.returncode} {r.stderr.strip()[:160]}') | |
| r = sh([CLI, 'scan', '-g', '--mode', 'agent'], env, cwd=work) | |
| patched = [b'__SOCKET_PATCHED' in open(f, 'rb').read() for f in files] | |
| cell(f'{label}/scan-g-agent-apply', all(patched), f'rc={r.returncode} patched={patched} {r.stdout.strip()[-200:]!r}') | |
| node = sh(['node', '-e', f'require({json.dumps(files[1])});process.stdout.write(String(globalThis.__SOCKET_PATCHED))'], env, cwd=work) | |
| cell(f'{label}/runtime-import', 'is-number' in node.stdout, node.stdout + node.stderr[-200:]) | |
| vex = os.path.join(work, 'v.json') | |
| r = sh([CLI, 'vex', '-g', '--product', 'pkg:generic/t@1', '-O', vex], env, cwd=work) | |
| st = json.load(open(vex)).get('statements', []) if os.path.exists(vex) else [] | |
| cell(f'{label}/vex-g-applied', r.returncode == 0 and len(st) >= 1, f'rc={r.returncode} statements={len(st)} {r.stderr.strip()[-200:]}') | |
| if all(patched): # tamper is-number back: vex must still only attest semver | |
| open(files[1], 'wb').write(orig[1]); os.remove(vex) | |
| r = sh([CLI, 'vex', '-g', '--product', 'pkg:generic/t@1', '-O', vex], env, cwd=work) | |
| cell(f'{label}/vex-g-omits-unapplied', 'not_applied' in r.stderr, f'rc={r.returncode} {r.stderr.strip()[-200:]}') | |
| sh([CLI, 'apply', '-g'], env, cwd=work) | |
| cell(f'{label}/apply-g-repatch', b'__SOCKET_PATCHED' in open(files[1], 'rb').read()) | |
| r = sh([CLI, 'rollback', '-g'], env, cwd=work) | |
| cell(f'{label}/rollback-g-byte-exact', [open(f, 'rb').read() for f in files] == orig, f'rc={r.returncode} {r.stdout.strip()[-200:]!r}') | |
| shutil.rmtree(os.path.join(work, '.socket'), ignore_errors=True) | |
| r = sh([CLI, 'get', '-g', U1], env, cwd=work) | |
| cell(f'{label}/get-g', b'__SOCKET_PATCHED' in open(files[0], 'rb').read(), f'rc={r.returncode} {(r.stdout + r.stderr).strip()[-200:]!r}') | |
| sh([CLI, 'rollback', '-g'], env, cwd=work) | |
| run_layout('default', {}) | |
| run_layout('bun_install_bin', {'BUN_INSTALL_BIN': 'localbin'}) | |
| run_layout('bun_install_global_dir', {'BUN_INSTALL_GLOBAL_DIR': 'gdir'}) | |
| run_layout('npm_installed_bun', {}, via_npm=True) | |
| fails = [n for n, ok in results if not ok] | |
| print('SUMMARY', plat, BUNVER, 'fails:', fails) | |
| PY | |
| CLI=cli/socket-patch; [ -f cli/socket-patch.exe ] && CLI=cli/socket-patch.exe | |
| PY=python3; [ "$RUNNER_OS" = Windows ] && PY=python | |
| PYTHONIOENCODING=utf-8 $PY probe.py "$CLI" "${{ matrix.bun }}" 2>&1 | tee probe.log |