|
| 1 | +name: bughunt pip egg-info probe |
| 2 | +on: |
| 3 | + push: |
| 4 | + branches: ['bughunt/pip/**'] |
| 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 | + python: ['3.8', '3.11'] |
| 14 | + runs-on: ${{ matrix.os }} |
| 15 | + timeout-minutes: 45 |
| 16 | + steps: |
| 17 | + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
| 18 | + - uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1 |
| 19 | + - run: cargo build --release -p socket-patch-cli |
| 20 | + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 |
| 21 | + with: |
| 22 | + python-version: ${{ matrix.python }} |
| 23 | + - name: probe |
| 24 | + shell: python |
| 25 | + run: | |
| 26 | + import base64, hashlib, json, os, subprocess, sys, threading, zipfile, shutil, pathlib, re |
| 27 | + from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer |
| 28 | + WIN = os.name == 'nt' |
| 29 | + SP = str(pathlib.Path('target/release/socket-patch' + ('.exe' if WIN else '')).resolve()) |
| 30 | + W = pathlib.Path(os.environ['RUNNER_TEMP']) / 'bh'; shutil.rmtree(W, ignore_errors=True); W.mkdir() |
| 31 | + def run(cmd, cwd=None, env=None): |
| 32 | + r = subprocess.run(cmd, cwd=cwd, env=env, capture_output=True, text=True) |
| 33 | + return r.returncode, r.stdout, r.stderr |
| 34 | + run([sys.executable, '-m', 'pip', 'download', '-q', '--no-deps', 'six==1.16.0', '--only-binary', ':all:', '-d', str(W / 'wh')]) |
| 35 | + whl = next((W / 'wh').glob('six-*.whl')) |
| 36 | + orig = zipfile.ZipFile(whl).read('six.py') |
| 37 | + patched = orig.replace(b'"""Utilities for writing code', b'# SOCKET-PATCH-MARKER\n"""Utilities for writing code', 1) |
| 38 | + gh = lambda b: hashlib.sha256(b"blob %d\0" % len(b) + b).hexdigest() |
| 39 | + pw = W / 'six-1.16.0-py2.py3-none-any.whl' |
| 40 | + zi = zipfile.ZipFile(whl); zo = zipfile.ZipFile(pw, 'w', zipfile.ZIP_DEFLATED) |
| 41 | + for i in zi.infolist(): |
| 42 | + zo.writestr(i, patched if i.filename == 'six.py' else zi.read(i.filename)) |
| 43 | + zo.close(); wb = pw.read_bytes() |
| 44 | + UUID = '11111111-1111-4111-8111-111111111111'; PURL = 'pkg:pypi/six@1.16.0' |
| 45 | + class H(BaseHTTPRequestHandler): |
| 46 | + def log_message(self, *a): pass |
| 47 | + def send(self, o, c=200): |
| 48 | + b = json.dumps(o).encode(); self.send_response(c); self.send_header('content-type', 'application/json'); self.send_header('content-length', str(len(b))); self.end_headers(); self.wfile.write(b) |
| 49 | + def do_POST(self): |
| 50 | + body = self.rfile.read(int(self.headers.get('content-length', 0))).decode() |
| 51 | + if self.path.endswith('/patches/batch'): |
| 52 | + pk = [{"purl": PURL, "patches": [{"uuid": UUID, "purl": PURL, "tier": "free", "cveIds": ["CVE-2024-30004"], "ghsaIds": ["GHSA-aaaa-bbbb-cccc"], "severity": "high", "title": "six", "publishedAt": "2024-01-01T00:00:00Z"}]}] if 'pkg:pypi/six@1.16.0' in body else [] |
| 53 | + return self.send({"packages": pk, "canAccessPaidPatches": False}) |
| 54 | + if self.path.endswith('/patches/package'): |
| 55 | + url = f"http://127.0.0.1:{PORT}/patch/pypi/six/1.16.0/{UUID}/tok/six-1.16.0-py2.py3-none-any.whl" |
| 56 | + return self.send({"results": {UUID: {"status": "granted", "url": url, "purl": PURL, "artifacts": [{"kind": "tarball", "url": url, "integrity": {"sha256": hashlib.sha256(wb).hexdigest()}}], "registryOverride": None}}}) |
| 57 | + self.send({}, 404) |
| 58 | + def do_HEAD(self): |
| 59 | + self.send_response(200); self.end_headers() |
| 60 | + def do_GET(self): |
| 61 | + if self.path.endswith('.whl'): |
| 62 | + self.send_response(200); self.send_header('content-length', str(len(wb))); self.end_headers(); self.wfile.write(wb); return |
| 63 | + if '/patches/by-package/' in self.path: |
| 64 | + return self.send({"patches": [{"uuid": UUID, "purl": PURL, "publishedAt": "2024-01-01T00:00:00Z", "description": "d", "license": "MIT", "tier": "free", "vulnerabilities": {}}], "canAccessPaidPatches": False}) |
| 65 | + if '/patches/view/' in self.path: |
| 66 | + return self.send({"uuid": UUID, "purl": PURL, "publishedAt": "2024-01-01T00:00:00Z", "files": {"six.py": {"beforeHash": gh(orig), "afterHash": gh(patched), "blobContent": base64.b64encode(patched).decode()}}, "vulnerabilities": {"GHSA-aaaa-bbbb-cccc": {"cves": ["CVE-2024-30004"], "summary": "s", "severity": "high", "description": "d"}}, "description": "d", "license": "MIT", "tier": "free"}) |
| 67 | + self.send({}, 404) |
| 68 | + srv = ThreadingHTTPServer(('127.0.0.1', 0), H); PORT = srv.server_address[1] |
| 69 | + threading.Thread(target=srv.serve_forever, daemon=True).start() |
| 70 | + env = dict(os.environ, SOCKET_API_URL=f'http://127.0.0.1:{PORT}', SOCKET_API_TOKEN='fake', SOCKET_ORG_SLUG='org', SOCKET_NO_TELEMETRY='1') |
| 71 | + env.pop('VIRTUAL_ENV', None) |
| 72 | + rows = [] |
| 73 | + for pv in ['20.3.4', '22.3.1', '23.0.1', '23.1']: |
| 74 | + res = {'pip': pv} |
| 75 | + def mk(name): |
| 76 | + d = W / f'{name}-{pv}'; d.mkdir() |
| 77 | + run([sys.executable, '-m', 'venv', str(d / '.venv')]) |
| 78 | + py = str(d / '.venv' / ('Scripts/python.exe' if WIN else 'bin/python')) |
| 79 | + run([py, '-m', 'pip', 'install', '-q', f'pip=={pv}']) |
| 80 | + run([py, '-m', 'pip', 'uninstall', '-y', '-q', 'wheel']) |
| 81 | + (d / 'requirements.txt').write_text('six==1.16.0\n') |
| 82 | + rc, o, e = run([py, '-m', 'pip', 'install', '-q', '--disable-pip-version-check', '--no-binary', 'six', '-r', 'requirements.txt'], cwd=d) |
| 83 | + if rc: print('install failed', pv, e[-800:]) |
| 84 | + site = pathlib.Path(run([py, '-c', 'import six,os;print(os.path.dirname(six.__file__))'])[1].strip()) |
| 85 | + return d, py, site |
| 86 | + def marker(py): |
| 87 | + rc, o, e = run([py, '-c', "import six;print(open(six.__file__).read().count('SOCKET-PATCH-MARKER'))"]); return o.strip() |
| 88 | + d, py, site = mk('agent') |
| 89 | + res['meta'] = ','.join(sorted(p.name for p in site.glob('six-*info'))) |
| 90 | + rc, o, e = run([SP, 'scan', '--mode', 'agent', '-e', 'pypi', '--yes', '--json'], cwd=d, env=env) |
| 91 | + try: ap = json.loads(o).get('apply', {}).get('patches', []) |
| 92 | + except Exception: ap = o[-300:] |
| 93 | + res['agent_rc'] = rc; res['agent'] = [(p.get('action'), p.get('errorCode')) for p in ap] if isinstance(ap, list) else ap; res['agent_marker'] = marker(py) |
| 94 | + rc, o, e = run([SP, 'scan', '-g', '--global-prefix', str(site), '-e', 'pypi', '--json'], cwd=str(W), env=env) |
| 95 | + res['global_report_has_six'] = 'pkg:pypi/six@1.16.0' in o |
| 96 | + d, py, site = mk('hosted') |
| 97 | + rc, o, e = run([SP, 'scan', '-e', 'pypi', '--yes', '--json'], cwd=d, env=env) |
| 98 | + res['hosted_rc'] = rc; res['stale_warning'] = 'redirect_pypi_stale_install' in o |
| 99 | + run([py, '-m', 'pip', 'install', '-q', '--disable-pip-version-check', '-r', 'requirements.txt'], cwd=d) |
| 100 | + res['hosted_marker_after_install'] = marker(py) |
| 101 | + rows.append(res); print(json.dumps(res), flush=True) |
| 102 | + print('RESULTS', sys.version.split()[0], sys.platform, json.dumps(rows)) |
0 commit comments