|
| 1 | +name: bughunt-nuget |
| 2 | +on: |
| 3 | + push: |
| 4 | + branches: ['bughunt/nuget/**'] |
| 5 | +permissions: |
| 6 | + contents: read |
| 7 | +jobs: |
| 8 | + probe: |
| 9 | + strategy: |
| 10 | + fail-fast: false |
| 11 | + matrix: |
| 12 | + include: |
| 13 | + - { os: ubuntu-latest, sdk: '6.0.x', tool: '2.1.7' } |
| 14 | + - { os: ubuntu-latest, sdk: '9.0.x', tool: '3.0.3' } |
| 15 | + - { os: ubuntu-latest, sdk: '10.0.x', tool: '3.0.3' } |
| 16 | + - { os: macos-latest, sdk: '8.0.x', tool: '3.0.3' } |
| 17 | + - { os: macos-latest, sdk: '9.0.x', tool: '3.0.3' } |
| 18 | + - { os: windows-latest, sdk: '8.0.x', tool: '3.0.3' } |
| 19 | + - { os: windows-latest, sdk: '9.0.x', tool: '3.0.3' } |
| 20 | + - { os: windows-latest, sdk: '10.0.x', tool: '3.0.3' } |
| 21 | + runs-on: ${{ matrix.os }} |
| 22 | + timeout-minutes: 45 |
| 23 | + steps: |
| 24 | + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 |
| 25 | + with: |
| 26 | + persist-credentials: false |
| 27 | + - uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0 |
| 28 | + with: |
| 29 | + dotnet-version: ${{ matrix.sdk }} |
| 30 | + - uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 |
| 31 | + with: |
| 32 | + python-version: '3.12' |
| 33 | + - run: rustup show |
| 34 | + - run: cargo build --release -p socket-patch-cli |
| 35 | + - name: probe |
| 36 | + shell: bash |
| 37 | + run: | |
| 38 | + SP="$GITHUB_WORKSPACE/target/release/socket-patch" |
| 39 | + [ -f "$SP.exe" ] && SP="$(cygpath -w "$SP.exe")" |
| 40 | + cat > "$RUNNER_TEMP/probe.py" <<'PYEOF' |
| 41 | + import hashlib, json, os, subprocess, sys, threading, shutil, platform |
| 42 | + from http.server import BaseHTTPRequestHandler, HTTPServer |
| 43 | + SP = sys.argv[1]; TOOLVER = sys.argv[2] |
| 44 | + TMP = os.environ.get('RUNNER_TEMP', '/tmp'); W = os.path.join(TMP, 'bh'); os.makedirs(W, exist_ok=True) |
| 45 | + HOME = os.path.expanduser('~'); LOG = [] |
| 46 | + class H(BaseHTTPRequestHandler): |
| 47 | + def do_POST(self): |
| 48 | + b = self.rfile.read(int(self.headers.get('content-length', 0))); LOG.append(b.decode()) |
| 49 | + try: comps = json.loads(b).get('components', []) |
| 50 | + except Exception: comps = [] |
| 51 | + pk = [{"purl": c['purl'], "patches": [{"uuid": "11111111-1111-4111-8111-%012d" % i, "purl": c['purl'], "tier": "free", "cveIds": ["CVE-2099-0001"], "ghsaIds": [], "severity": "high", "title": "t"}]} for i, c in enumerate(comps) if c.get('purl', '').startswith('pkg:nuget/')] |
| 52 | + o = json.dumps({"packages": pk, "canAccessPaidPatches": False}).encode() |
| 53 | + self.send_response(200); self.send_header('content-type', 'application/json'); self.send_header('content-length', str(len(o))); self.end_headers(); self.wfile.write(o) |
| 54 | + def do_GET(self): self.send_response(404); self.end_headers() |
| 55 | + def log_message(self, *a): pass |
| 56 | + srv = HTTPServer(('127.0.0.1', 0), H); threading.Thread(target=srv.serve_forever, daemon=True).start() |
| 57 | + PROXY = 'http://127.0.0.1:%d' % srv.server_port |
| 58 | + ENV = dict(os.environ, SOCKET_NO_CONFIG='1', SOCKET_TELEMETRY_DISABLED='1', DOTNET_CLI_TELEMETRY_OPTOUT='1', DOTNET_NOLOGO='1') |
| 59 | + for k in list(ENV): |
| 60 | + if k.startswith('SOCKET_') and k not in ('SOCKET_NO_CONFIG', 'SOCKET_TELEMETRY_DISABLED'): ENV.pop(k) |
| 61 | + def run(cmd, cwd=W): |
| 62 | + r = subprocess.run(cmd, cwd=cwd, env=ENV, capture_output=True, text=True, shell=isinstance(cmd, str)) |
| 63 | + return r.returncode, r.stdout + r.stderr |
| 64 | + def scan_purls(): |
| 65 | + LOG.clear(); rc, out = run([SP, 'scan', '-g', '-e', 'nuget', '--json', '--proxy-url', PROXY]) |
| 66 | + s = set() |
| 67 | + for b in LOG: |
| 68 | + for c in json.loads(b).get('components', []): s.add(c['purl']) |
| 69 | + return rc, sorted(s), out |
| 70 | + def proj(name, refs): |
| 71 | + d = os.path.join(W, name); os.makedirs(d, exist_ok=True) |
| 72 | + open(os.path.join(d, name + '.csproj'), 'w').write('<Project Sdk="Microsoft.NET.Sdk"><PropertyGroup><TargetFramework>netstandard2.0</TargetFramework></PropertyGroup><ItemGroup>%s</ItemGroup></Project>' % ''.join('<PackageReference Include="%s" Version="%s" />' % r for r in refs)) |
| 73 | + rc, out = run(['dotnet', 'restore'], cwd=d); print('restore', name, rc, out.strip().splitlines()[-1] if out.strip() else '') |
| 74 | + return d |
| 75 | + def gh(b): return hashlib.sha256(b"blob %d\0" % len(b) + b).hexdigest() |
| 76 | + def stage(purl, src, rel): |
| 77 | + g = os.path.join(W, 'gm'); shutil.rmtree(g, ignore_errors=True); os.makedirs(os.path.join(g, '.socket', 'blobs')) |
| 78 | + before = open(src, 'rb').read(); after = before + b"\nSOCKET_MARKER\n" |
| 79 | + for b in (before, after): open(os.path.join(g, '.socket', 'blobs', gh(b)), 'wb').write(b) |
| 80 | + m = {"patches": {purl: {"uuid": "11111111-1111-4111-8111-000000000001", "exportedAt": "2024-01-01T00:00:00Z", "files": {rel: {"beforeHash": gh(before), "afterHash": gh(after)}}, "vulnerabilities": {"GHSA-xxxx-xxxx-xxxx": {"cves": ["CVE-2099-0001"], "summary": "s", "severity": "high", "description": "d"}}, "description": "f", "license": "MIT", "tier": "free"}}} |
| 81 | + json.dump(m, open(os.path.join(g, '.socket', 'manifest.json'), 'w')); return g |
| 82 | + def marked(p): return os.path.exists(p) and b'SOCKET_MARKER' in open(p, 'rb').read() |
| 83 | + R = {} |
| 84 | + print('== os', platform.system(), 'sdk', run(['dotnet', '--version'])[1].strip()) |
| 85 | + # --- A: dotnet global tool |
| 86 | + rc, out = run(['dotnet', 'tool', 'install', '-g', 'dotnetsay', '--version', TOOLVER]); print('tool install', rc, out.strip()[-200:]) |
| 87 | + store = os.path.join(HOME, '.dotnet', 'tools', '.store', 'dotnetsay', TOOLVER, 'dotnetsay', TOOLVER) |
| 88 | + print('tool store exists:', os.path.isdir(store), store) |
| 89 | + proj('warm', [('Newtonsoft.Json', '13.0.3')]) |
| 90 | + rc, purls, out = scan_purls(); print('scan -g rc', rc, 'nuget purls sent:', purls) |
| 91 | + R['A_scan_g_lists_global_tool'] = any('dotnetsay' in p for p in purls) |
| 92 | + g = stage('pkg:nuget/dotnetsay@' + TOOLVER, os.path.join(store, 'dotnetsay.nuspec'), 'dotnetsay.nuspec') |
| 93 | + rc, out = run([SP, 'apply', '-g', '--offline'], cwd=g); print('apply -g tool rc', rc, out.strip().splitlines()[-1:]) |
| 94 | + R['A_apply_g_patches_global_tool'] = marked(os.path.join(store, 'dotnetsay.nuspec')) |
| 95 | + run([SP, 'rollback', '-g', '--offline'], cwd=g) |
| 96 | + # --- B: user-level globalPackagesFolder |
| 97 | + if platform.system() == 'Windows': ucfg = os.path.join(os.environ['APPDATA'], 'NuGet', 'NuGet.Config') |
| 98 | + else: ucfg = os.path.join(HOME, '.nuget', 'NuGet', 'NuGet.Config') |
| 99 | + gpf = os.path.join(W, 'gpf'); os.makedirs(os.path.dirname(ucfg), exist_ok=True) |
| 100 | + open(ucfg, 'w').write('<?xml version="1.0" encoding="utf-8"?>\n<configuration><packageSources><add key="nuget.org" value="https://api.nuget.org/v3/index.json" protocolVersion="3" /></packageSources><config><add key="globalPackagesFolder" value="%s" /></config></configuration>\n' % gpf) |
| 101 | + print('locals:', run(['dotnet', 'nuget', 'locals', 'global-packages', '-l'])[1].strip()) |
| 102 | + proj('relo', [('Newtonsoft.Json', '13.0.3'), ('Humanizer.Core', '2.14.1')]) |
| 103 | + rc, purls, out = scan_purls(); print('scan -g (gpf) rc', rc, 'nuget purls sent:', purls) |
| 104 | + R['B_scan_g_sees_configured_gpf'] = any('humanizer.core' in p.lower() for p in purls) |
| 105 | + dflt = os.path.join(HOME, '.nuget', 'packages', 'newtonsoft.json', '13.0.3', 'LICENSE.md'); real = os.path.join(gpf, 'newtonsoft.json', '13.0.3', 'LICENSE.md') |
| 106 | + g = stage('pkg:nuget/newtonsoft.json@13.0.3', real, 'LICENSE.md') |
| 107 | + rc, out = run([SP, 'apply', '-g', '--offline'], cwd=g); print('apply -g (gpf) rc', rc, out.strip().splitlines()[-1:]) |
| 108 | + R['B_apply_g_patched_configured_gpf'] = marked(real); R['B_apply_g_patched_default_cache'] = marked(dflt) |
| 109 | + rc, out = run([SP, 'vex', '-g', '--offline', '--product', 'pkg:nuget/x@1.0.0'], cwd=g); R['B_vex_says_not_affected'] = '"not_affected"' in out |
| 110 | + run([SP, 'rollback', '-g', '--offline'], cwd=g) |
| 111 | + print('RESULT', json.dumps(R)) |
| 112 | + PYEOF |
| 113 | + python "$RUNNER_TEMP/probe.py" "$SP" "${{ matrix.tool }}" |
0 commit comments