Skip to content

Commit 9ac5a1a

Browse files
committed
chore(wheelhouse): cascade template@8a15af1b
Auto-applied by socket-wheelhouse sync-scaffolding into cascade-socket-cli-9299. 11 file(s) touched: - .claude/hooks/fleet/prefer-async-spawn-guard/index.mts - .claude/hooks/fleet/prefer-async-spawn-guard/test/index.test.mts - .github/workflows/ci.yml - scripts/fleet/setup/external-tools.json - scripts/fleet/setup/index.mts - scripts/fleet/setup/lib/check-firewall.mjs - scripts/fleet/setup/lib/install-tool.mjs - scripts/fleet/setup/lib/jq.mjs - scripts/fleet/setup/lib/platform.mjs - scripts/fleet/setup/lib/read-pinned-version.mjs - scripts/fleet/setup/setup-tools.mjs
1 parent 6d05c4b commit 9ac5a1a

11 files changed

Lines changed: 1034 additions & 5 deletions

File tree

.claude/hooks/fleet/prefer-async-spawn-guard/index.mts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,14 @@ const CHILD_PROCESS_REQUIRE_RE =
5252

5353
/**
5454
* Files where importing `node:child_process` is legitimate: this hook's own
55-
* files, the oxlint rules that match the banned shapes, and the markdownlint
55+
* files, the oxlint rules that match the banned shapes, the markdownlint
5656
* self-skip shim (a `.mjs` rule loaded by markdownlint-cli2, which can't await
57-
* the async lib wrapper, so its documented fallback is the sync builtin).
57+
* the async lib wrapper, so its documented fallback is the sync builtin), and
58+
* the pre-pnpm bootstrap `.mjs` provisioners under `scripts/fleet/setup/`.
59+
* Those install pnpm itself on a bare machine BEFORE node_modules exists, so
60+
* `@socketsecurity/lib`'s async `spawn` wrapper isn't on disk to import — the
61+
* sync builtin is the only option (same constraint as the markdownlint shim);
62+
* each carries an `oxlint-disable socket/prefer-async-spawn` documenting it.
5863
*/
5964
export function isExemptPath(filePath: string): boolean {
6065
return (
@@ -72,7 +77,11 @@ export function isExemptPath(filePath: string): boolean {
7277
) ||
7378
filePath.includes(
7479
'/.config/fleet/markdownlint-rules/_shared/wheelhouse-self-skip.',
75-
)
80+
) ||
81+
// Pre-pnpm bootstrap .mjs provisioners (scripts/fleet/setup/{lib/,*}.mjs):
82+
// run before node_modules exists, so the lib spawn wrapper isn't importable
83+
// yet. Scoped to `.mjs` so the dir's `.mts` steps stay guarded.
84+
(filePath.includes('/scripts/fleet/setup/') && filePath.endsWith('.mjs'))
7685
)
7786
}
7887

.claude/hooks/fleet/prefer-async-spawn-guard/test/index.test.mts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,10 @@ describe('prefer-async-spawn-guard / isExemptPath', () => {
7474
'/repo/.config/fleet/markdownlint-rules/_shared/wheelhouse-self-skip.mjs',
7575
'/repo/dist/foo.js',
7676
'/repo/node_modules/x/y.js',
77+
// Pre-pnpm bootstrap .mjs provisioners (install pnpm before node_modules
78+
// exists, so the lib spawn wrapper isn't importable yet).
79+
'/repo/scripts/fleet/setup/lib/install-tool.mjs',
80+
'/repo/scripts/fleet/setup/setup-tools.mjs',
7781
]) {
7882
assert.equal(isExemptPath(p), true, p)
7983
}
@@ -82,5 +86,9 @@ describe('prefer-async-spawn-guard / isExemptPath', () => {
8286
test('does not exempt ordinary source', () => {
8387
assert.equal(isExemptPath('/repo/scripts/foo.mts'), false)
8488
assert.equal(isExemptPath('/repo/src/bar.ts'), false)
89+
// The setup dir's `.mts` steps run AFTER setup, so they stay guarded —
90+
// only the pre-node `.mjs` bootstrap files are exempt.
91+
assert.equal(isExemptPath('/repo/scripts/fleet/setup/index.mts'), false)
92+
assert.equal(isExemptPath('/repo/scripts/fleet/setup/token.mts'), false)
8593
})
8694
})

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,4 @@ concurrency:
2929
jobs:
3030
ci:
3131
name: Run CI Pipeline
32-
uses: SocketDev/socket-registry/.github/workflows/ci.yml@4069dfc8c54c0b6ffa43924857e6b89296370efe # main (2026-06-09)
32+
uses: SocketDev/socket-registry/.github/workflows/ci.yml@1c633fb12cf27b12f06f9d685e89d34e302822c3 # main (2026-06-13)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
{
2+
"pnpm": {
3+
"notes": [
4+
"pnpm publishes 7 platform-native binaries: linux-{x64,arm64}{,-musl}, darwin-arm64, win-{x64,arm64}. Verified against v11.6.0 (2026-06-13).",
5+
"linux-*-musl tarballs are first-class assets with distinct integrity from the glibc tarballs — the binaries are linked against different libcs and only the matching one runs on its target. Don't 'simplify' by pointing musl keys at the glibc asset.",
6+
"darwin-x64 is the odd one out: upstream dropped the SEA binary in 11.0.5 because of nodejs/node#62893 (upstream LIEF/Mach-O bug that the Node team has declined to fix). Intel Mac instead installs the npm-registry JS tarball (`pnpm-<version>.tgz`) + runs it through system Node. update-external-tools.mts recognizes the `<pkg>-<version>.tgz` asset shape and fetches its integrity from the npm registry rather than the GitHub release.",
7+
"v11.6.0 was bumped via update-external-tools.mts (all 8 platforms re-hashed: GitHub assets + darwin-x64 from the npm registry). It published 2026-06-11, inside the 7-day minimumReleaseAge soak, so the bump rode a dated `soakBypass` entry (auto-disarms at `removable`) — pnpm releases are GitHub-asset distributions from a known publisher; the soak targets npm typosquats / malicious freshpubs. Drop the cleared soakBypass on the next routine bump."
8+
],
9+
"description": "Fast, disk space efficient package manager",
10+
"repository": "github:pnpm/pnpm",
11+
"version": "11.6.0",
12+
"soakBypass": {
13+
"version": "11.6.0",
14+
"published": "2026-06-11",
15+
"removable": "2026-06-18"
16+
},
17+
"release": "asset",
18+
"platforms": {
19+
"darwin-arm64": {
20+
"asset": "pnpm-darwin-arm64.tar.gz",
21+
"integrity": "sha512-DHKwseQ/HKcfXLOrzwLGFAd4SWOyo3jW+PileiHwQaI8/ZDpg0IR1vVz0SzBWWv7O7HinYUjbm1elENkR8EG9w=="
22+
},
23+
"darwin-x64": {
24+
"asset": "pnpm-11.6.0.tgz",
25+
"integrity": "sha512-mjZRgiQIDG/lFlr9z+eb+hGMKb5wPz9GKx4y7+HpjkfodQsUjggoYlCq1BE8x5k8pBPE4s1Ed1JwjC7ldRvJXw=="
26+
},
27+
"linux-arm64": {
28+
"asset": "pnpm-linux-arm64.tar.gz",
29+
"integrity": "sha512-x1bEpvzYu6CLlxc78cfNl4pDTa2sITFCaictgW/TK+QFL1uD1IJe9ssV3tAfclD+RhsIaSrxanPajHzJjGyrlg=="
30+
},
31+
"linux-arm64-musl": {
32+
"asset": "pnpm-linux-arm64-musl.tar.gz",
33+
"integrity": "sha512-gpdSD/YT0eAm3jmS6dWdWwzDuW0gaRuWVQ4qjsWBDX9/KcYCWW1PLZ3JLZ6tiXkkT2a1GSKQUaHuKul57wbqlQ=="
34+
},
35+
"linux-x64": {
36+
"asset": "pnpm-linux-x64.tar.gz",
37+
"integrity": "sha512-uj1Zz76+lcHATLkCrM/JUIIUaIYgXEEXOXNvSO+g3cYd5RXpS6MacuII9TRBAknr2n5XTIi/bAbOLfxF3hk4nw=="
38+
},
39+
"linux-x64-musl": {
40+
"asset": "pnpm-linux-x64-musl.tar.gz",
41+
"integrity": "sha512-4IC9DBZbiJVXz2/VtrZFtXc+OVXUIOhGv6WfN/p27k/rFJOj/57iNNC+MzZDRzlCZsZIAb3WAJUe2B4AAPLsnQ=="
42+
},
43+
"win-arm64": {
44+
"asset": "pnpm-win32-arm64.zip",
45+
"integrity": "sha512-VITunLEwYnoEeVF/UP5QD1qOCDhDy+C+BVhBKq5IT4UTiP3X2wanWCtL1nk5OTHg+oPB7NHaWah0SkLqtMcqTA=="
46+
},
47+
"win-x64": {
48+
"asset": "pnpm-win32-x64.zip",
49+
"integrity": "sha512-oX2y8mihTVM6QEDA8MdXyBGOQ8xxGjqhX1I9+jLfrFY5vCrwpkArhu8bTMq//vMPaS2Rl/nQ7cSgOySnhsvFog=="
50+
}
51+
}
52+
},
53+
"sfw": {
54+
"notes": [
55+
"SFW (Socket Firewall) is published in two flavors: free (public, SocketDev/sfw-free) and enterprise (private, SocketDev/firewall-release). Both ship the same 7-platform set: linux-{x64,arm64}{,-musl}, darwin-{x64,arm64}, win-x64. win-arm64 is intentionally absent — upstream does not yet build it. Unlike zizmor (a security audit), SFW is a required dependency of the install flow, so consumers on win-arm64 must skip SFW-dependent steps until upstream support lands.",
56+
"Setup action picks the enterprise flavor when SOCKET_API_KEY is in env, otherwise the free flavor. Enterprise downloads require GITHUB_TOKEN auth (private repo); install-tool.mjs forwards GITHUB_TOKEN automatically when set."
57+
],
58+
"description": "Socket Firewall — package manager command wrapper",
59+
"version": "1.12.0",
60+
"release": "asset",
61+
"free": {
62+
"repository": "github:SocketDev/sfw-free",
63+
"binaryName": "sfw",
64+
"platforms": {
65+
"darwin-arm64": {
66+
"asset": "sfw-free-macos-arm64",
67+
"integrity": "sha512-lwh/AIf7HXVIrE28LDfvtJqnaGb7azC+Up8Hi/c9hIfn9wMRt55misCKx9b6CjYi+d3bHladYNYPlqVtlqNpcQ=="
68+
},
69+
"darwin-x64": {
70+
"asset": "sfw-free-macos-x86_64",
71+
"integrity": "sha512-iBLJ7bzrnnUPmUbN8FFzmXNYowWnahOD4DWzKYbneeCsvFa1xlHT4LaLWTysatd5npJIO7QOiRow6yw/tgjCWw=="
72+
},
73+
"linux-arm64": {
74+
"asset": "sfw-free-linux-arm64",
75+
"integrity": "sha512-TZ0hzAzPyNfi1PgqU5+TzkrlBcWXZlXaSHkx1/wzIck4vlZXFQI8i7CCvWYihrJQ3zgEwVI30MmrqsJ9W7xWQw=="
76+
},
77+
"linux-arm64-musl": {
78+
"asset": "sfw-free-musl-linux-arm64",
79+
"integrity": "sha512-O+X0JxQJJn2YpAJFP38ZuG156pewgk+HJBVUTJZM8AMZSbERLy6LLDD2S8uwPXpMXDD9uRy8/h7EpRcu1OQLcw=="
80+
},
81+
"linux-x64": {
82+
"asset": "sfw-free-linux-x86_64",
83+
"integrity": "sha512-Yuu+qoqxa0n7WIS9NMI3uuitUMoELbbUqJm3W6L2AsMJNZpVekXKmrZIhEjxWjJqvKt3mErKxK+izdP3/F+64Q=="
84+
},
85+
"linux-x64-musl": {
86+
"asset": "sfw-free-musl-linux-x86_64",
87+
"integrity": "sha512-U4WJeq+/Z634uFvW0+Hvmb/BUutMeiZQ1dwP40/wKMiCDwKGPr+Unl4KqwaG3qaLjkTRJ938sUWQy+/gFeEmDg=="
88+
},
89+
"win-x64": {
90+
"asset": "sfw-free-windows-x86_64.exe",
91+
"integrity": "sha512-tkZHeaxydBStW6SsCi5S2jLMtdj2UQ/PdZb/ch8W532UjFdZUJD0oygW/YWliK0HQkcyw5GQm2d1iZU0P/yElg=="
92+
}
93+
}
94+
},
95+
"enterprise": {
96+
"repository": "github:SocketDev/firewall-release",
97+
"binaryName": "sfw",
98+
"platforms": {
99+
"darwin-arm64": {
100+
"asset": "sfw-macos-arm64",
101+
"integrity": "sha512-G7te2xB1Q+K/k/2Wijbn96eJZUZoNFlDNKURydLBLB69Jkuc1M1lNFbqxiyP8tfOlMIBKWxRwfZyeX9ipPy4Ew=="
102+
},
103+
"darwin-x64": {
104+
"asset": "sfw-macos-x86_64",
105+
"integrity": "sha512-/ogpJY01pDTEcvDPq09FNxGP5eXu4d+ab2RxT1r4he0ptfCOGOO3rQXfxTFqrOmS+OSz5RZe+4qPupM4nGriMQ=="
106+
},
107+
"linux-arm64": {
108+
"asset": "sfw-linux-arm64",
109+
"integrity": "sha512-oXhTWx/I/1yZRn0ik3DL5y2/4RZqv/msJpTi6m190jBGg/x7bgqJO4uCOUJe1+iudK3bNGsYB8zs6vIJTLwA7g=="
110+
},
111+
"linux-arm64-musl": {
112+
"asset": "sfw-musl-linux-arm64",
113+
"integrity": "sha512-VtvO4OkLNO7XW1YwY73WoIZeRp7sMg+LbdeL2CVy5bgysTnuBxKrkkJvW41BsuScVdf7nt/bh5V8ZBAMN993rg=="
114+
},
115+
"linux-x64": {
116+
"asset": "sfw-linux-x86_64",
117+
"integrity": "sha512-91W90AOLI0RBN6lsPor2wf7wUvV3hzebXf0SM7SEzVPGM76Yjwj2D5E/jtJ8LjNNE7afggUDEtgMvFSTmgnZDg=="
118+
},
119+
"linux-x64-musl": {
120+
"asset": "sfw-musl-linux-x86_64",
121+
"integrity": "sha512-5CUE3LnXKzRqoT7SmT/yDBtyVyiUqwKtgS11j7qEhb2KJI3kztBuUQwBoOKPxxwpS0X7R/DuANvax7pQ76f4xw=="
122+
},
123+
"win-x64": {
124+
"asset": "sfw-windows-x86_64.exe",
125+
"integrity": "sha512-GXKV67rN0XTP+2v9VTfzz84N09x9UkEItj2wmcA7pmy5YoLPF/+Z/XkVGoUHzVSTTeivbYicRLAxl8BNkoUZ6w=="
126+
}
127+
}
128+
}
129+
}
130+
}

scripts/fleet/setup/index.mts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ function main(): void {
3838
logger.log('=== Socket Repo Setup ===')
3939
logger.log('')
4040

41+
if (!skipTools) {
42+
logger.log('── Tools (pnpm + sfw + bootstrap) ─────────')
43+
results.push(['Tools', run(path.join(__dirname, 'setup-tools.mjs'))])
44+
logger.log('')
45+
}
46+
4147
logger.log('── Token ──────────────────────────────────')
4248
results.push([
4349
'Token',
@@ -46,7 +52,10 @@ function main(): void {
4652
logger.log('')
4753

4854
logger.log('── Claude config ──────────────────────────')
49-
results.push(['Claude config', run(path.join(__dirname, 'claude-config.mts'))])
55+
results.push([
56+
'Claude config',
57+
run(path.join(__dirname, 'claude-config.mts')),
58+
])
5059
logger.log('')
5160

5261
if (!skipNativeHost) {
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/**
2+
* @file Check a Socket package against the firewall API before downloading its
3+
* tarball directly from the npm registry. Endpoint: GET
4+
* https://firewall-api.socket.dev/purl/<encoded-purl> Response: { alerts?: [{
5+
* severity?, type?, key? }, ...] } Socket Firewall is a malware detector. The
6+
* API returns alerts only when a package is flagged as malicious — there's no
7+
* "minor severity informational alert" tier. ANY alert in the response means
8+
* malware, regardless of severity / type / key fields. Block unconditionally.
9+
* Exits 0 if the firewall returned no alerts, OR if the firewall is
10+
* unreachable / non-2xx (non-fatal so a network blip doesn't break a fresh
11+
* clone). Exits 1 if the firewall returned any alert at all. Usage: node
12+
* check-firewall.mjs <package-name> <version>
13+
*/
14+
15+
import { argv, exit, stderr, stdout } from 'node:process'
16+
17+
const pkgName = argv[2]
18+
const version = argv[3]
19+
if (!pkgName || !version) {
20+
stderr.write('Usage: node check-firewall.mjs <package-name> <version>\n')
21+
exit(2)
22+
}
23+
24+
const FIREWALL_API_URL = 'https://firewall-api.socket.dev/purl'
25+
const FIREWALL_TIMEOUT_MS = 10_000
26+
27+
const purl = `pkg:npm/${pkgName}@${version}`
28+
const url = `${FIREWALL_API_URL}/${encodeURIComponent(purl)}`
29+
30+
async function main() {
31+
const controller = new AbortController()
32+
// unref so the timer doesn't keep the event loop alive past
33+
// main() resolution.
34+
const timer = setTimeout(() => controller.abort(), FIREWALL_TIMEOUT_MS)
35+
timer.unref?.()
36+
try {
37+
// oxlint-disable-next-line socket/no-fetch-prefer-http-request -- composite-action helper runs on the raw runner before setup-node; @socketsecurity/lib-stable not installed yet.
38+
const res = await fetch(url, {
39+
headers: {
40+
'User-Agent': 'socket-registry-setup-action/1.0',
41+
Accept: 'application/json',
42+
},
43+
signal: controller.signal,
44+
})
45+
clearTimeout(timer)
46+
if (!res.ok) {
47+
stderr.write(
48+
`firewall-api: HTTP ${res.status} for ${purl} — proceeding anyway (non-fatal)\n`,
49+
)
50+
return 0
51+
}
52+
const data = await res.json()
53+
const alerts = data.alerts ?? []
54+
if (alerts.length > 0) {
55+
// Any alert from the firewall means malware. Block unconditionally;
56+
// do not branch on severity / type / key.
57+
stderr.write(
58+
`\n✗ Socket Firewall flagged ${pkgName}@${version} as malware (${alerts.length} alert(s)):\n`,
59+
)
60+
for (const a of alerts.slice(0, 10)) {
61+
stderr.write(
62+
` ${a.type ?? a.key ?? 'malware'}${a.severity ? ` (${a.severity})` : ''}\n`,
63+
)
64+
}
65+
stderr.write(
66+
'\nFix: bump the pinned version in pnpm-workspace.yaml or package.json to a known-good release.\n',
67+
)
68+
return 1
69+
}
70+
stdout.write(`✓ ${pkgName}@${version} cleared by Socket Firewall\n`)
71+
return 0
72+
} catch (e) {
73+
clearTimeout(timer)
74+
// Firewall errors are non-fatal — allow bootstrap to proceed.
75+
// Network blips or registry-down shouldn't break a fresh clone.
76+
// oxlint-disable-next-line socket/prefer-error-message -- composite-action helper runs on the raw runner before setup-node; @socketsecurity/lib-stable/errors is not installed yet.
77+
const message = e instanceof Error ? e.message : String(e)
78+
stderr.write(`firewall-api: ${message} — proceeding anyway (non-fatal)\n`)
79+
return 0
80+
}
81+
}
82+
83+
// Use exitCode + natural drain instead of process.exit() so libuv
84+
// can finish closing the fetch handles cleanly. process.exit() while
85+
// async handles are mid-shutdown trips an `Assertion failed:
86+
// !(handle->flags & UV_HANDLE_CLOSING)` abort on Node 24 + Windows.
87+
main().then(code => {
88+
process.exitCode = code
89+
})

0 commit comments

Comments
 (0)