Skip to content

Commit e468a61

Browse files
committed
refactor: improve bootstrap with consistent flags and simplified IPC
- Add --no-addons and --no-warnings flags to both system and embedded Node - Remove SOCKET_CLI_PATH from IPC handshake (CLI knows its own path) - Remove unused detectSystemNode function and IPC_HANDSHAKE_TIMEOUT_MS - Fix parseInt radix for MIN_NODE_VERSION parsing - Simplify IPC bypass logic since CLI path is passed as command line arg
1 parent 7715b1a commit e468a61

File tree

1 file changed

+46
-64
lines changed

1 file changed

+46
-64
lines changed

src/sea/bootstrap.mts

Lines changed: 46 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,6 @@ try {
3535
const CLI_INSTALL_LOCK_FILE_NAME = '.install.lock'
3636
const DOWNLOAD_MESSAGE_DELAY_MS = 2_000
3737
const HTTPS_TIMEOUT_MS = 30_000
38-
const IPC_HANDSHAKE_TIMEOUT_MS = 5_000
3938
// 30 seconds total.
4039
const LOCK_MAX_RETRIES = 60
4140
const LOCK_RETRY_DELAY_MS = 500
@@ -45,9 +44,12 @@ const NPM_REGISTRY =
4544
'https://registry.npmjs.org'
4645
const SOCKET_CLI_DIR =
4746
process.env['SOCKET_CLI_DIR'] || path.join(SOCKET_HOME, '_cli')
47+
const SOCKET_CLI_PACKAGE_DIR = path.join(SOCKET_CLI_DIR, 'package')
4848
const SOCKET_CLI_PACKAGE =
4949
process.env['SOCKET_CLI_PACKAGE'] || '@socketsecurity/cli'
50-
const SOCKET_CLI_PACKAGE_JSON = path.join(SOCKET_CLI_DIR, 'package.json')
50+
const SOCKET_CLI_PACKAGE_JSON = path.join(SOCKET_CLI_PACKAGE_DIR, 'package.json')
51+
// Minimum Node.js version for system Node (v22 = Active LTS)
52+
const MIN_NODE_VERSION = parseInt(process.env['MIN_NODE_VERSION'] || '22', 10)
5153

5254
// ============================================================================
5355
// Helper utilities
@@ -70,6 +72,7 @@ function formatError(error: unknown): string {
7072
return error instanceof Error ? error.message : String(error)
7173
}
7274

75+
7376
/**
7477
* Sanitize tarball path to prevent directory traversal attacks.
7578
* Ensures extracted files stay within the target directory.
@@ -238,8 +241,8 @@ async function downloadAndInstallPackage(version: string): Promise<void> {
238241
let tarballPath: string | undefined
239242

240243
try {
241-
// Ensure CLI directory exists before acquiring lock.
242-
await fs.mkdir(SOCKET_CLI_DIR, { recursive: true })
244+
// Ensure CLI package directory exists before acquiring lock.
245+
await fs.mkdir(SOCKET_CLI_PACKAGE_DIR, { recursive: true })
243246

244247
// Acquire installation lock.
245248
lockPath = await acquireLock()
@@ -347,7 +350,7 @@ async function extractTarball(tarballPath: string): Promise<void> {
347350
// Sanitize file path to prevent directory traversal attacks.
348351
// This removes 'package/' prefix, strips '..' segments, and normalizes separators.
349352
const sanitizedPath = sanitizeTarballPath(file.name)
350-
const targetPath = path.join(SOCKET_CLI_DIR, sanitizedPath)
353+
const targetPath = path.join(SOCKET_CLI_PACKAGE_DIR, sanitizedPath)
351354

352355
if (file.type === 'directory') {
353356
await retryWithBackoff(() =>
@@ -409,6 +412,23 @@ async function extractTarball(tarballPath: string): Promise<void> {
409412
)
410413
}
411414
}
415+
416+
// CRITICAL: Ensure bin/ and shadow-bin/ files are executable.
417+
// These directories contain entry points that must be executable.
418+
const relativePath = path.relative(SOCKET_CLI_PACKAGE_DIR, targetPath)
419+
if (
420+
relativePath.startsWith('bin' + path.sep) ||
421+
relativePath.startsWith('shadow-bin' + path.sep) ||
422+
relativePath.startsWith('dist' + path.sep + 'shadow')
423+
) {
424+
await retryWithBackoff(() => fs.chmod(targetPath, 0o755)).catch(
425+
error => {
426+
console.error(
427+
`Warning: Failed to make ${targetPath} executable: ${formatError(error)}`,
428+
)
429+
},
430+
)
431+
}
412432
}
413433
}
414434

@@ -626,9 +646,16 @@ async function spawnEmbeddedNode(
626646
cliPath: string,
627647
args: string[] | readonly string[],
628648
): Promise<void> {
629-
return await spawnNodeProcess(process.execPath, args, {
649+
// Build command arguments: security/warning flags + cli path + user args
650+
const commandArgs = [
651+
'--no-addons',
652+
'--no-warnings',
653+
cliPath,
654+
...args,
655+
]
656+
657+
return await spawnNodeProcess(process.execPath, commandArgs, {
630658
env: process.env,
631-
cliPathForEmbedded: cliPath,
632659
})
633660
}
634661

@@ -641,10 +668,9 @@ async function spawnNodeProcess(
641668
commandArgs: string[] | readonly string[],
642669
options: {
643670
env: NodeJS.ProcessEnv
644-
cliPathForEmbedded?: string
645671
},
646672
): Promise<void> {
647-
const { cliPathForEmbedded, env } = options
673+
const { env } = options
648674

649675
const child = spawn(command, commandArgs, {
650676
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
@@ -657,7 +683,6 @@ async function spawnNodeProcess(
657683
const handshake: {
658684
SOCKET_IPC_HANDSHAKE: {
659685
SOCKET_CLI_STUB_PATH?: string
660-
SOCKET_CLI_PATH?: string
661686
}
662687
} = {
663688
SOCKET_IPC_HANDSHAKE: {},
@@ -666,9 +691,6 @@ async function spawnNodeProcess(
666691
if (stubPath) {
667692
handshake.SOCKET_IPC_HANDSHAKE.SOCKET_CLI_STUB_PATH = stubPath
668693
}
669-
if (cliPathForEmbedded) {
670-
handshake.SOCKET_IPC_HANDSHAKE.SOCKET_CLI_PATH = cliPathForEmbedded
671-
}
672694

673695
if (Object.keys(handshake.SOCKET_IPC_HANDSHAKE).length > 0) {
674696
try {
@@ -710,7 +732,15 @@ async function spawnSystemNode(
710732
cliPath: string,
711733
args: string[] | readonly string[],
712734
): Promise<void> {
713-
return await spawnNodeProcess('node', [cliPath, ...args], {
735+
// Build command arguments: security/warning flags + cli path + user args
736+
const commandArgs = [
737+
'--no-addons',
738+
'--no-warnings',
739+
cliPath,
740+
...args,
741+
]
742+
743+
return await spawnNodeProcess('node', commandArgs, {
714744
env: process.env,
715745
})
716746
}
@@ -724,55 +754,7 @@ async function spawnSystemNode(
724754
* Ensures CLI is installed, then spawns it with system Node.js.
725755
*/
726756
async function main(): Promise<void> {
727-
// Check if we're being spawned to execute the CLI directly (bypass bootstrap).
728-
// Parent sends CLI path via IPC handshake.
729-
if (process.send) {
730-
const cliPath = await new Promise<string | undefined>(resolve => {
731-
const timeout = setTimeout(
732-
() => resolve(undefined),
733-
IPC_HANDSHAKE_TIMEOUT_MS,
734-
)
735-
process.on('message', msg => {
736-
if (
737-
msg !== null &&
738-
typeof msg === 'object' &&
739-
'SOCKET_IPC_HANDSHAKE' in msg &&
740-
msg.SOCKET_IPC_HANDSHAKE !== null &&
741-
typeof msg.SOCKET_IPC_HANDSHAKE === 'object' &&
742-
msg.SOCKET_IPC_HANDSHAKE &&
743-
'SOCKET_CLI_PATH' in msg.SOCKET_IPC_HANDSHAKE
744-
) {
745-
clearTimeout(timeout)
746-
resolve(msg.SOCKET_IPC_HANDSHAKE.SOCKET_CLI_PATH as string)
747-
}
748-
})
749-
})
750-
751-
if (cliPath) {
752-
// Verify CLI file exists before attempting to require it.
753-
if (!existsSync(cliPath)) {
754-
console.error(
755-
`Fatal: CLI entry point not found at ${cliPath}. Installation may be corrupted.`,
756-
)
757-
// eslint-disable-next-line n/no-process-exit
758-
process.exit(1)
759-
}
760-
761-
// Set process.argv to include CLI path and user arguments.
762-
process.argv = [process.argv[0]!, cliPath, ...process.argv.slice(1)]
763-
// Load and execute the CLI with embedded Node.js.
764-
try {
765-
require(cliPath)
766-
} catch (error) {
767-
console.error(
768-
`Fatal: Failed to load CLI from ${cliPath}: ${formatError(error)}`,
769-
)
770-
// eslint-disable-next-line n/no-process-exit
771-
process.exit(1)
772-
}
773-
return
774-
}
775-
}
757+
// No IPC bypass check needed - we pass CLI path as command line argument now
776758

777759
try {
778760
// Ensure Socket home directory exists with better error messages.
@@ -816,7 +798,7 @@ async function main(): Promise<void> {
816798
const args = process.argv.slice(2)
817799

818800
// process.env.MIN_NODE_VERSION is inlined at build time.
819-
const minNodeVersion = parseInt(process.env['MIN_NODE_VERSION'] ?? '0', 0)
801+
const minNodeVersion = parseInt(process.env['MIN_NODE_VERSION'] ?? '0', 10) || MIN_NODE_VERSION
820802

821803
const systemNodeVersion = await getSystemNodeVersion(minNodeVersion)
822804

0 commit comments

Comments
 (0)