Skip to content

Commit 482f1b3

Browse files
committed
feat: add trusted publisher verification script
- Check if all @socketbin packages exist on npm - Verify provenance attestations if present - Check GitHub workflow configuration - Verify NPM_TOKEN secret (if accessible) - Provide clear status and next steps Run with: node scripts/verify-trusted-publisher.mjs
1 parent 73e49e0 commit 482f1b3

File tree

1 file changed

+247
-0
lines changed

1 file changed

+247
-0
lines changed
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
#!/usr/bin/env node
2+
3+
/**
4+
* @fileoverview Verifies that trusted publisher is configured correctly for @socketbin packages.
5+
* Checks for provenance attestations and publisher information.
6+
*/
7+
8+
import https from 'node:https'
9+
import { spawn } from 'node:child_process'
10+
11+
const packages = [
12+
'@socketbin/cli-darwin-arm64',
13+
'@socketbin/cli-darwin-x64',
14+
'@socketbin/cli-linux-arm64',
15+
'@socketbin/cli-linux-x64',
16+
'@socketbin/cli-win32-arm64',
17+
'@socketbin/cli-win32-x64'
18+
]
19+
20+
/**
21+
* Fetch package metadata from npm registry
22+
*/
23+
async function getPackageInfo(packageName) {
24+
return new Promise((resolve, reject) => {
25+
const url = `https://registry.npmjs.org/${packageName}`
26+
27+
https.get(url, res => {
28+
let data = ''
29+
res.on('data', chunk => data += chunk)
30+
res.on('end', () => {
31+
try {
32+
resolve(JSON.parse(data))
33+
} catch (error) {
34+
reject(error)
35+
}
36+
})
37+
}).on('error', reject)
38+
})
39+
}
40+
41+
/**
42+
* Check npm attestations for a package
43+
*/
44+
async function checkAttestations(packageName, version = 'latest') {
45+
return new Promise((resolve) => {
46+
const child = spawn('npm', ['view', `${packageName}@${version}`, '--json'], {
47+
stdio: ['ignore', 'pipe', 'pipe']
48+
})
49+
50+
let stdout = ''
51+
let stderr = ''
52+
53+
child.stdout.on('data', data => stdout += data)
54+
child.stderr.on('data', data => stderr += data)
55+
56+
child.on('exit', () => {
57+
try {
58+
const data = JSON.parse(stdout)
59+
resolve({
60+
package: packageName,
61+
version: data.version || version,
62+
hasProvenance: !!data.dist?.attestations?.provenance,
63+
publishedBy: data.dist?.attestations?.provenance?.predicateType || null,
64+
npmUser: data._npmUser?.name || 'unknown'
65+
})
66+
} catch {
67+
resolve({
68+
package: packageName,
69+
version,
70+
hasProvenance: false,
71+
publishedBy: null,
72+
npmUser: 'unknown'
73+
})
74+
}
75+
})
76+
})
77+
}
78+
79+
/**
80+
* Check GitHub workflow configuration
81+
*/
82+
async function checkWorkflow() {
83+
return new Promise((resolve) => {
84+
const child = spawn('gh', ['workflow', 'view', 'publish-socketbin.yml', '--json', 'name,state'], {
85+
stdio: ['ignore', 'pipe', 'pipe']
86+
})
87+
88+
let stdout = ''
89+
child.stdout.on('data', data => stdout += data)
90+
91+
child.on('exit', code => {
92+
if (code === 0) {
93+
try {
94+
const data = JSON.parse(stdout)
95+
resolve({
96+
exists: true,
97+
name: data.name,
98+
state: data.state
99+
})
100+
} catch {
101+
resolve({ exists: false })
102+
}
103+
} else {
104+
resolve({ exists: false })
105+
}
106+
})
107+
})
108+
}
109+
110+
/**
111+
* Check if NPM_TOKEN is configured in GitHub secrets
112+
*/
113+
async function checkSecrets() {
114+
return new Promise((resolve) => {
115+
const child = spawn('gh', ['secret', 'list', '--json', 'name'], {
116+
stdio: ['ignore', 'pipe', 'pipe']
117+
})
118+
119+
let stdout = ''
120+
child.stdout.on('data', data => stdout += data)
121+
122+
child.on('exit', code => {
123+
if (code === 0) {
124+
try {
125+
const secrets = JSON.parse(stdout)
126+
const hasNpmToken = secrets.some(s => s.name === 'NPM_TOKEN')
127+
resolve({ hasNpmToken })
128+
} catch {
129+
resolve({ hasNpmToken: false })
130+
}
131+
} else {
132+
resolve({ hasNpmToken: false })
133+
}
134+
})
135+
})
136+
}
137+
138+
async function main() {
139+
console.log('🔍 Verifying Trusted Publisher Setup for @socketbin\n')
140+
console.log('=' .repeat(60))
141+
142+
// Check if packages exist
143+
console.log('\n📦 Checking package status...\n')
144+
145+
let allPublished = true
146+
const packageResults = []
147+
148+
for (const pkg of packages) {
149+
try {
150+
const info = await getPackageInfo(pkg)
151+
const latest = info['dist-tags']?.latest
152+
if (latest) {
153+
console.log(`✅ ${pkg}@${latest}`)
154+
packageResults.push({ name: pkg, version: latest, exists: true })
155+
} else {
156+
console.log(`❌ ${pkg} - not published`)
157+
allPublished = false
158+
packageResults.push({ name: pkg, exists: false })
159+
}
160+
} catch {
161+
console.log(`❌ ${pkg} - not found`)
162+
allPublished = false
163+
packageResults.push({ name: pkg, exists: false })
164+
}
165+
}
166+
167+
if (!allPublished) {
168+
console.log('\n⚠️ Not all packages are published yet.')
169+
console.log('Run: node scripts/publish-placeholders.mjs')
170+
console.log('\nTrusted publisher cannot be configured until packages exist.')
171+
process.exit(1)
172+
}
173+
174+
// Check for provenance on existing packages
175+
console.log('\n🔐 Checking provenance attestations...\n')
176+
177+
const attestationResults = await Promise.all(
178+
packageResults
179+
.filter(p => p.exists)
180+
.map(p => checkAttestations(p.name, p.version))
181+
)
182+
183+
let hasProvenance = false
184+
for (const result of attestationResults) {
185+
if (result.hasProvenance) {
186+
console.log(`✅ ${result.package}@${result.version} - has provenance (published by ${result.npmUser})`)
187+
hasProvenance = true
188+
} else {
189+
console.log(`⚠️ ${result.package}@${result.version} - no provenance (manual publish by ${result.npmUser})`)
190+
}
191+
}
192+
193+
// Check GitHub workflow
194+
console.log('\n🔧 Checking GitHub configuration...\n')
195+
196+
const workflow = await checkWorkflow()
197+
if (workflow.exists) {
198+
console.log(`✅ Workflow exists: ${workflow.name}`)
199+
console.log(` State: ${workflow.state}`)
200+
} else {
201+
console.log('❌ Workflow not found or gh CLI not configured')
202+
console.log(' Expected: .github/workflows/publish-socketbin.yml')
203+
}
204+
205+
// Check secrets
206+
const secrets = await checkSecrets()
207+
if (secrets.hasNpmToken) {
208+
console.log('✅ NPM_TOKEN secret configured')
209+
} else {
210+
console.log('⚠️ NPM_TOKEN secret not found (or no access to check)')
211+
}
212+
213+
// Summary
214+
console.log('\n' + '=' .repeat(60))
215+
console.log('\n📊 SUMMARY\n')
216+
217+
if (!hasProvenance) {
218+
console.log('📝 Trusted Publisher Status: NOT CONFIGURED\n')
219+
console.log('This is expected for placeholder packages (v0.0.0).')
220+
console.log('\nTo enable trusted publisher:')
221+
console.log('1. Go to: https://www.npmjs.com/settings/socketbin/integrations')
222+
console.log('2. Click "Add Trusted Publisher"')
223+
console.log('3. Configure:')
224+
console.log(' - Repository: SocketDev/socket-cli')
225+
console.log(' - Workflow: .github/workflows/publish-socketbin.yml')
226+
console.log(' - Environment: (leave blank)\n')
227+
console.log('After configuration, the next publish from GitHub Actions will have provenance.')
228+
} else {
229+
console.log('✅ Trusted Publisher Status: CONFIGURED\n')
230+
console.log('Packages published from GitHub Actions will have provenance attestations.')
231+
}
232+
233+
// Test command
234+
console.log('\n🧪 Test Commands:\n')
235+
console.log('Test the workflow (dry run):')
236+
console.log(' gh workflow run publish-socketbin.yml -f version=0.0.1-test -f dry-run=true\n')
237+
console.log('Publish with provenance (after trusted publisher is set):')
238+
console.log(' gh workflow run publish-socketbin.yml -f version=0.0.1\n')
239+
console.log('Check specific package provenance:')
240+
console.log(' npm view @socketbin/cli-darwin-arm64@latest dist.attestations.provenance')
241+
}
242+
243+
// Run the script
244+
main().catch(error => {
245+
console.error('Error:', error)
246+
process.exit(1)
247+
})

0 commit comments

Comments
 (0)