Skip to content

Commit 1bf77d5

Browse files
committed
bughunt probe: nuget global mode
1 parent 2463257 commit 1bf77d5

1 file changed

Lines changed: 113 additions & 0 deletions

File tree

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

Comments
 (0)