Skip to content

Commit 958bd9d

Browse files
committed
refactor: use system Node.js with --require for shadow npm
When spawning npm/npx, we now require system Node.js to exist. Since npm must be installed, Node.js must also exist on the system. This simplifies the implementation by always using --require flag with the inject script instead of having a fallback SEA binary path. Changes: - Export findSystemNodejs() from spawn-node.mts - Check for system Node.js in npm-base.mts, error if not found - Always use --require with getShadowNpmInjectPath() - Always send IPC handshake with extra field for custom data - Remove legacy RegistryInternals type and getInternals() function - Remove IpcObject type from types.mts (now in shadow.mts)
1 parent 959e07c commit 958bd9d

File tree

5 files changed

+39
-66
lines changed

5 files changed

+39
-66
lines changed

packages/cli/src/constants.mts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -248,7 +248,6 @@ import {
248248
REPORT_LEVEL_WARN,
249249
} from './constants/reporting.mts'
250250
import {
251-
getInternals,
252251
getShadowNpmBinPath as SHADOW_getShadowNpmBinPath,
253252
getShadowNpxBinPath as SHADOW_getShadowNpxBinPath,
254253
getShadowPnpmBinPath as SHADOW_getShadowPnpmBinPath,
@@ -286,12 +285,9 @@ import { WIN32 } from './constants/types.mts'
286285
// Export types.
287286
export type {
288287
Agent,
289-
IpcObject,
290288
ProcessEnv,
291289
RegistryEnv,
292-
RegistryInternals,
293290
Remap,
294-
Sentry,
295291
SpawnOptions,
296292
} from './constants/types.mts'
297293

@@ -369,7 +365,6 @@ export {
369365
getExecPath,
370366
getGithubCachePath,
371367
getInstrumentWithSentryPath,
372-
getInternals,
373368
getMinimumVersionByAgent,
374369
getNmBunPath,
375370
getNmNodeGypPath,
@@ -621,7 +616,6 @@ export default {
621616
getExecPath,
622617
getGithubCachePath,
623618
getInstrumentWithSentryPath,
624-
getInternals,
625619
getMinimumVersionByAgent,
626620
getNmBunPath,
627621
getNmNodeGypPath,

packages/cli/src/constants/shadow.mts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import path from 'node:path'
66

77
import { distPath } from './paths.mts'
88

9-
import type { RegistryInternals } from './types.mts'
10-
119
// Re-export SOCKET_IPC_HANDSHAKE from registry
1210
export { SOCKET_IPC_HANDSHAKE } from '@socketsecurity/lib/constants/socket'
1311

@@ -30,14 +28,6 @@ export const SOCKET_CLI_SHADOW_SILENT = 'SOCKET_CLI_SHADOW_SILENT'
3028
export const SOCKET_CLI_ACCEPT_RISKS = 'SOCKET_CLI_ACCEPT_RISKS'
3129
export const SOCKET_CLI_VIEW_ALL_RISKS = 'SOCKET_CLI_VIEW_ALL_RISKS'
3230

33-
/**
34-
* Get registry internals for accessing IPC and Sentry instances.
35-
*/
36-
export function getInternals(): RegistryInternals {
37-
const registry = require('@socketsecurity/registry')
38-
return registry.internals ?? {}
39-
}
40-
4131
/**
4232
* Get the path to the shadow npm binary.
4333
*/

packages/cli/src/constants/types.mts

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,6 @@ export type { Agent } from '../utils/ecosystem/environment.mjs'
1212

1313
export type RegistryEnv = typeof ENV
1414

15-
export type RegistryInternals = {
16-
getIpc?: () => IpcObject | undefined
17-
getSentry?: () => Sentry | undefined
18-
setSentry?: (sentry: Sentry) => void
19-
}
20-
21-
export type Sentry = any
22-
23-
export type IpcObject = Readonly<{
24-
SOCKET_CLI_FIX?: string | undefined
25-
SOCKET_CLI_OPTIMIZE?: boolean | undefined
26-
SOCKET_CLI_SHADOW_ACCEPT_RISKS?: boolean | undefined
27-
SOCKET_CLI_SHADOW_API_TOKEN?: string | undefined
28-
SOCKET_CLI_SHADOW_BIN?: string | undefined
29-
SOCKET_CLI_SHADOW_PROGRESS?: boolean | undefined
30-
SOCKET_CLI_SHADOW_SILENT?: boolean | undefined
31-
}>
32-
3315
export type ProcessEnv = {
3416
[K in keyof typeof ENV]?: string | undefined
3517
}

packages/cli/src/shadow/npm-base.mts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { getOwn } from '@socketsecurity/lib/objects'
2121
import { normalizePath } from '@socketsecurity/lib/path'
2222
import { spawnSync } from '@socketsecurity/lib/spawn'
2323

24-
import { spawnNode } from '../utils/spawn/spawn-node.mjs'
24+
import { findSystemNodejs, spawnNode } from '../utils/spawn/spawn-node.mjs'
2525
import { NPM, type NPX } from '../constants/agents.mts'
2626
import { FLAG_LOGLEVEL } from '../constants/cli.mts'
2727
import ENV from '../constants/env.mts'
@@ -53,7 +53,7 @@ export type ShadowBinOptions = SpawnOptions & {
5353
}
5454

5555
export type ShadowBinResult = {
56-
spawnPromise: SpawnResult
56+
spawnPromise: Promise<SpawnResult>
5757
}
5858

5959
export default async function shadowNpmBase(
@@ -149,11 +149,17 @@ export default async function shadowNpmBase(
149149
? await installNpmLinks(shadowBinPath)
150150
: await installNpxLinks(shadowBinPath)
151151

152-
// Use spawnNode() to handle SEA bootstrap automatically.
153-
// This will:
154-
// - Use system Node.js if available (preferred)
155-
// - Fall back to self-spawning with IPC handshake (for SEA binaries)
156-
// - Handle IPC channel setup and handshake message automatically
152+
// If we're forwarding to npm, then npm exists on the system,
153+
// which means Node.js must also exist. Use system Node.js with --require.
154+
const systemNode = await findSystemNodejs()
155+
if (!systemNode) {
156+
throw new Error(
157+
'System Node.js not found. npm/npx require Node.js to be installed.',
158+
)
159+
}
160+
161+
// Use spawnNode() to handle spawning with IPC handshake.
162+
// Since we know system Node.js exists, we can always use --require.
157163
const spawnPromise = spawnNode(
158164
[
159165
...getNodeNoWarningsFlags(),

packages/cli/src/utils/spawn/spawn-node.mts

Lines changed: 26 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,22 @@ export interface SpawnNodeOptions extends SpawnOptions {
3636
/**
3737
* Additional IPC handshake data to send to subprocess.
3838
*
39-
* This is merged with bootstrap indicators (subprocess: true, parent_pid)
40-
* to create the full IPC handshake message.
39+
* This is placed in the `extra` field of the handshake message to avoid
40+
* collision with standard fields (subprocess, parent_pid).
41+
*
42+
* Final handshake structure:
43+
* {
44+
* subprocess: true,
45+
* parent_pid: <pid>,
46+
* extra: { ...ipc } // Custom data goes here
47+
* }
4148
*
4249
* Use this to pass custom configuration to the subprocess:
4350
* - Shadow npm/pnpm/yarn settings (API token, bin name, etc.)
4451
* - Custom application data
4552
*
46-
* Only used when spawning SEA binary as subprocess.
53+
* System Node.js will ignore the handshake message.
54+
* SEA subprocess will use it to skip bootstrap.
4755
*/
4856
ipc?: Record<string, unknown>
4957
}
@@ -71,32 +79,25 @@ export async function spawnNode(
7179
// Get the Node.js executable path to use.
7280
const nodePath = await getNodeExecutablePath()
7381

74-
// Determine if we need to set up IPC handshake.
75-
const needsIpcHandshake = isSeaBinary() && nodePath === process.execPath
76-
77-
// If we need IPC handshake, ensure stdio includes 'ipc'.
78-
const finalOptions = needsIpcHandshake
79-
? {
80-
...spawnOpts,
81-
stdio: ensureIpcInStdio(spawnOpts.stdio),
82-
}
83-
: spawnOpts
82+
// Always ensure stdio includes 'ipc' for handshake.
83+
// System Node.js will ignore the handshake message.
84+
// SEA subprocess will use it to skip bootstrap.
85+
const finalOptions = {
86+
...spawnOpts,
87+
stdio: ensureIpcInStdio(spawnOpts.stdio),
88+
}
8489

8590
// Spawn the Node.js process.
8691
const spawnResult = spawn(nodePath, args, finalOptions, extra)
8792

88-
// If we're spawning ourselves as a SEA subprocess, send IPC handshake.
89-
if (needsIpcHandshake) {
90-
// Build IPC handshake with bootstrap indicators + custom data.
91-
const handshakeData = {
92-
// Bootstrap indicators - always included for subprocess detection.
93-
subprocess: true,
94-
parent_pid: process.pid,
95-
// Custom IPC data (shadow config, application data, etc.).
96-
...(ipc ?? {}),
97-
}
98-
sendBootstrapHandshake(spawnResult.process, handshakeData)
93+
// Always send IPC handshake with bootstrap indicators + custom data.
94+
const handshakeData = {
95+
subprocess: true,
96+
parent_pid: process.pid,
97+
// Custom IPC data in extra field to avoid collision with standard fields.
98+
...(ipc ? { extra: { ...ipc } } : {}),
9999
}
100+
sendBootstrapHandshake(spawnResult.process, handshakeData)
100101

101102
return spawnResult
102103
}
@@ -133,7 +134,7 @@ async function getNodeExecutablePath(): Promise<string> {
133134
*
134135
* @returns Path to system Node.js, or undefined
135136
*/
136-
async function findSystemNodejs(): Promise<string | undefined> {
137+
export async function findSystemNodejs(): Promise<string | undefined> {
137138
// Use which to find 'node' in PATH (returns all matches).
138139
const nodePath = await which('node', { all: true, nothrow: true })
139140

0 commit comments

Comments
 (0)