Skip to content

Commit 629c356

Browse files
committed
bughunt pip probe: egg-info installs
1 parent 2463257 commit 629c356

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

‎.github/workflows/bughunt-pip.yml‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
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

Comments
 (0)