Skip to content

Commit 37e9ca5

Browse files
committed
bughunt(vlt): read-only global prefix probe
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vgm9PC1r7ux8TNtBh3ChaG
1 parent 2463257 commit 37e9ca5

1 file changed

Lines changed: 100 additions & 0 deletions

File tree

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

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: bughunt vlt probe
2+
on:
3+
push:
4+
branches: ['bughunt/vlt/**']
5+
permissions:
6+
contents: read
7+
jobs:
8+
probe:
9+
strategy:
10+
fail-fast: false
11+
matrix:
12+
os: [ubuntu-latest, macos-latest]
13+
runs-on: ${{ matrix.os }}
14+
timeout-minutes: 45
15+
defaults:
16+
run:
17+
shell: bash
18+
steps:
19+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
20+
with:
21+
persist-credentials: false
22+
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4
23+
with:
24+
node-version: 24.21.0
25+
- name: Build CLI
26+
run: cargo build --release -p socket-patch-cli
27+
- name: Write probe
28+
run: |
29+
mkdir -p probe
30+
cat > probe/mock.mjs <<'MOCKEOF'
31+
import http from 'node:http';
32+
import zlib from 'node:zlib';
33+
import crypto from 'node:crypto';
34+
import fs from 'node:fs';
35+
const PORT = +process.env.PORT || 18555;
36+
const R = `http://127.0.0.1:${PORT}`;
37+
const NAME='left-pad', VER='1.3.0', UUID='aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', TOKEN='bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb', ORG='test-org';
38+
const PURL=`pkg:npm/${NAME}@${VER}`;
39+
const ALT = process.env.NO_ALT ? false : true;
40+
function hdr(name,size){const b=Buffer.alloc(512);b.write(name,0);b.write('0000644\0',100);b.write('0000000\0',108);b.write('0000000\0',116);b.write(size.toString(8).padStart(11,'0')+'\0',124);b.write('00000000000\0',136);b.write(' ',148);b.write('0',156);b.write('ustar\0',257);b.write('00',263);let c=0;for(const x of b)c+=x;b.write(c.toString(8).padStart(6,'0')+'\0 ',148);return b;}
41+
function tarFiles(files){const parts=[];for(const [n,d] of files){const buf=Buffer.from(d);parts.push(hdr(n,buf.length),buf,Buffer.alloc((512-buf.length%512)%512));}parts.push(Buffer.alloc(1024));return Buffer.concat(parts);}
42+
function tarOf(content){return tarFiles([['package/package.json',JSON.stringify({name:NAME,version:VER,main:'index.js'})+'\n'],['package/index.js',content]]);}
43+
const sri = b => 'sha512-'+crypto.createHash('sha512').update(b).digest('base64');
44+
const git = b => crypto.createHash('sha256').update(Buffer.concat([Buffer.from(`blob ${b.length}\0`), b])).digest('hex');
45+
const PRIST = Buffer.from("module.exports = 'pristine'\n"), PATCH = Buffer.from("module.exports = 'patched'\n");
46+
const tgz = zlib.gzipSync(tarOf(PRIST)), br = zlib.brotliCompressSync(tarOf(PRIST)), ptgz = zlib.gzipSync(tarOf(PATCH));
47+
function wrapTar(){return zlib.gzipSync(tarFiles([['package/package.json',JSON.stringify({name:'wrapper',version:'1.0.0',main:'index.js',dependencies:{'left-pad':'1.3.0'}})+'\n'],['package/index.js',"module.exports = require('left-pad')\n"]]));}
48+
const wtgz = wrapTar();
49+
const artPath = `/patch/npm/${NAME}/${VER}/${TOKEN}/${UUID}/${NAME}-${VER}.tgz`;
50+
const log = [];
51+
http.createServer((req,res)=>{
52+
let body=''; req.on('data',c=>body+=c); req.on('end',()=>{
53+
const u = req.url.split('?')[0]; log.push(req.method+' '+u);
54+
const j = o => { res.writeHead(200,{'content-type':'application/json'}); res.end(JSON.stringify(o)); };
55+
const b = x => { res.writeHead(200,{'content-type':'application/octet-stream'}); res.end(x); };
56+
if (u===`/${NAME}`) {
57+
const dist = {tarball:`${R}/${NAME}/-/${NAME}-${VER}.tgz`, integrity: sri(tgz)};
58+
if (ALT) dist.alternates=[{kind:'tar.br', tarball:`${R}/${NAME}/-/${NAME}-${VER}.tar.br`, integrity: sri(br)}];
59+
return j({name:NAME, 'dist-tags':{latest:VER}, versions:{[VER]:{name:NAME,version:VER,main:'index.js',dist}}});
60+
}
61+
if (u===`/${NAME}/${VER}`) { const dist = {tarball:`${R}/${NAME}/-/${NAME}-${VER}.tgz`, integrity: sri(tgz)}; return j({name:NAME,version:VER,main:'index.js',dist}); }
62+
if (u==='/wrapper/1.0.0') return j({name:'wrapper',version:'1.0.0',dist:{tarball:`${R}/wrapper/-/wrapper-1.0.0.tgz`,integrity:sri(wtgz)}});
63+
if (u==='/wrapper') return j({name:'wrapper','dist-tags':{latest:'1.0.0'},versions:{'1.0.0':{name:'wrapper',version:'1.0.0',main:'index.js',dependencies:{'left-pad':'1.3.0'},dist:{tarball:`${R}/wrapper/-/wrapper-1.0.0.tgz`,integrity:sri(wtgz)}}}});
64+
if (u==='/wrapper/-/wrapper-1.0.0.tgz') return b(wtgz);
65+
if (u===`/${NAME}/-/${NAME}-${VER}.tgz`) return b(tgz);
66+
if (u===`/${NAME}/-/${NAME}-${VER}.tar.br`) return b(br);
67+
if (u===artPath) return b(ptgz);
68+
if (u.startsWith(`/v0/orgs/${ORG}/patches/blob/`)) { const h=u.split('/').pop(); for (const x of [PRIST,PATCH]) if (git(x)===h) return b(x); }
69+
if (u===`/v0/orgs/${ORG}/patches/batch`) { if (!body.includes(PURL)) return j({packages:[],canAccessPaidPatches:false}); return j({packages:[{purl:PURL,patches:[{uuid:UUID,purl:PURL,tier:'free',cveIds:[],ghsaIds:[],severity:'high',title:'fx'}]}],canAccessPaidPatches:false}); }
70+
if (u.startsWith(`/v0/orgs/${ORG}/patches/by-package/`)) return j({patches:[{uuid:UUID,purl:PURL,publishedAt:'2024-01-01T00:00:00Z',description:'x',license:'MIT',tier:'free',vulnerabilities:{}}],canAccessPaidPatches:false});
71+
if (u===`/v0/orgs/${ORG}/patches/package`) return j({results:{[UUID]:{status:'granted',url:R+artPath,purl:PURL,artifacts:[{kind:'tarball',url:R+artPath,integrity:{sha512:sri(ptgz)}}],registryOverride:null}}});
72+
if (u===`/v0/orgs/${ORG}/patches/view/${UUID}`) return j({uuid:UUID,purl:PURL,publishedAt:'2024-01-01T00:00:00Z',files:{'package/index.js':{beforeHash:git(PRIST),afterHash:git(PATCH),blobContent:PATCH.toString('base64'),beforeBlobContent:PRIST.toString('base64')}},vulnerabilities:{'GHSA-vlth-aaaa-bbbb':{cves:['CVE-2026-4242'],summary:'s',severity:'high',description:'d'}},description:'x',license:'MIT',tier:'free'});
73+
res.writeHead(404); res.end('nf');
74+
});
75+
}).listen(PORT, '127.0.0.1', ()=>console.log('listening', PORT));
76+
77+
MOCKEOF
78+
cat > probe/ro.sh <<'PROBEEOF'
79+
#!/usr/bin/env bash
80+
set -u
81+
SP=$1; MOCK=$2
82+
export LANG=C SOCKET_NPM_REGISTRY=http://127.0.0.1:18555 SOCKET_API_URL=http://127.0.0.1:18556 SOCKET_PATCH_SERVER_URL=http://127.0.0.1:18556 SOCKET_ORG_SLUG=test-org SOCKET_API_TOKEN=fake
83+
NO_ALT=1 PORT=18555 node "$MOCK" & M1=$!; NO_ALT=1 PORT=18556 node "$MOCK" & M2=$!; sleep 2
84+
W="$RUNNER_TEMP/ro$RANDOM"; mkdir -p "$W"; cd "$W"
85+
export HOME="$W/h" NPM_CONFIG_PREFIX="$W/gprefix" NPM_CONFIG_REGISTRY=http://127.0.0.1:18555/ NPM_CONFIG_CACHE="$W/.c"; mkdir -p "$HOME"
86+
npm install -g left-pad@1.3.0 >/dev/null 2>&1; GROOT=$(npm root -g); G="$GROOT/left-pad"; cp "$G/index.js" g.orig
87+
sudo chown -R root "$W/gprefix"; sudo chmod -R go-w "$W/gprefix"; ls -la "$G"
88+
req() { node -e "console.log(require(process.argv[1]))" "$1"; }
89+
for i in 1 2; do
90+
for form in "-g" "--global-prefix $GROOT" "env"; do
91+
if [ "$form" = env ]; then SOCKET_GLOBAL=1 "$SP" scan --mode agent --yes --json > out.json 2>err.txt; rc=$?; else "$SP" scan $form --mode agent --yes --json > out.json 2>err.txt; rc=$?; fi
92+
echo "RO[$i] form=$form rc=$rc status=$(grep -o '"status": "[a-z_]*"' out.json | head -1) global=$(req "$G") bytes=$(cmp -s "$G/index.js" g.orig && echo same || echo differ) errcode=$(grep -o '"errorCode": "[a-zA-Z_]*"\|"code": "[a-z_]*"' out.json | sort -u | tr '\n' ' ')"
93+
head -c 600 err.txt | tr '\n' ' '; echo
94+
"$SP" get pkg:npm/left-pad@1.3.0 -g --yes --json > g.json 2>/dev/null; echo "RO[$i] get -g rc=$? status=$(grep -o '"status": "[a-zA-Z_]*"' g.json | head -1) global=$(req "$G")"
95+
"$SP" rollback -g --yes --json > r.json 2>/dev/null; echo "RO[$i] rollback -g rc=$? global=$(req "$G")"
96+
done; done
97+
kill $M1 $M2
98+
PROBEEOF
99+
- name: Probe
100+
run: bash probe/ro.sh "$PWD/target/release/socket-patch" "$PWD/probe/mock.mjs"

0 commit comments

Comments
 (0)