@socketsecurity/lib
Version:
Core utilities and infrastructure for Socket.dev security tools
417 lines (416 loc) • 16.5 kB
TypeScript
// Define BufferEncoding type for TypeScript compatibility.
type BufferEncoding = globalThis.BufferEncoding;
/**
* Options for spawning a child process with promise-based completion.
*
* @property {string | undefined} cwd - Current working directory for the process
* @property {boolean | undefined} stdioString - Convert stdio output to strings (default: `true`)
* @property {StdioType | undefined} stdio - Stdio configuration (`'pipe'`, `'ignore'`, `'inherit'`, or array)
* @property {NodeJS.ProcessEnv | undefined} env - Environment variables for the process
* @property {boolean | string | undefined} shell - Whether to run command in shell, or path to shell
* @property {AbortSignal | undefined} signal - Signal to abort the process
* @property {number | undefined} timeout - Maximum time in milliseconds before killing the process
* @property {number | undefined} uid - User identity of the process (POSIX only)
* @property {number | undefined} gid - Group identity of the process (POSIX only)
*/
export type PromiseSpawnOptions = {
cwd?: string | undefined;
stdioString?: boolean | undefined;
stdio?: StdioType | undefined;
env?: NodeJS.ProcessEnv | undefined;
shell?: boolean | string | undefined;
signal?: AbortSignal | undefined;
timeout?: number | undefined;
uid?: number | undefined;
gid?: number | undefined;
};
/**
* Result returned by {@link spawn} when the child process completes.
* This is a Promise that resolves with process exit information and output,
* with additional properties for accessing the running process and stdin stream.
*
* @property {ChildProcessType} process - The running child process instance
* @property {WritableStreamType | null} stdin - Writable stream for process stdin, or `null` if not piped
*
* @example
* const result = spawn('echo', ['hello'])
* result.stdin?.write('additional input\n')
* const { code, stdout } = await result
* console.log(stdout) // 'hello'
*/
export type PromiseSpawnResult = Promise<{
cmd: string;
args: string[] | readonly string[];
code: number;
signal: NodeJS.Signals | null;
stdout: string | Buffer;
stderr: string | Buffer;
}> & {
process: ChildProcessType;
stdin: WritableStreamType | null;
};
/**
* Error object thrown when a spawned process fails.
* Extends the standard Error with process-specific information including exit code,
* signal, command details, and captured output.
*
* @property {string[]} args - Arguments passed to the command
* @property {string} cmd - Command that was executed
* @property {number} code - Process exit code
* @property {string} name - Error name (typically `'Error'`)
* @property {string} message - Error message describing the failure
* @property {NodeJS.Signals | null} signal - Signal that terminated the process, if any
* @property {string} stack - Stack trace of the error
* @property {string | Buffer} stderr - Standard error output from the process
* @property {string | Buffer} stdout - Standard output from the process
*
* @example
* try {
* await spawn('exit', ['1'])
* } catch (error) {
* if (isSpawnError(error)) {
* console.error(`Command failed with code ${error.code}`)
* console.error(`stderr: ${error.stderr}`)
* }
* }
*/
export type SpawnError = {
args: string[];
cmd: string;
code: number;
name: string;
message: string;
signal: NodeJS.Signals | null;
stack: string;
stderr: string | Buffer;
stdout: string | Buffer;
};
/**
* Spawn error variant where stdout and stderr are guaranteed to be strings.
* This type is used when `stdioString: true` is set in spawn options.
*
* @property {string} stdout - Standard output as a string
* @property {string} stderr - Standard error as a string
*/
export type SpawnErrorWithOutputString = SpawnError & {
stdout: string;
stderr: string;
};
/**
* Spawn error variant where stdout and stderr are guaranteed to be Buffers.
* This type is used when `stdioString: false` is set in spawn options.
*
* @property {Buffer} stdout - Standard output as a Buffer
* @property {Buffer} stderr - Standard error as a Buffer
*/
export type SpawnErrorWithOutputBuffer = SpawnError & {
stdout: Buffer;
stderr: Buffer;
};
/**
* Extra options passed to the underlying promise-spawn implementation.
* This is an open-ended object for passing additional metadata or configuration.
*/
export type SpawnExtra = Record<string, unknown>;
/**
* Valid values for individual stdio streams.
* - `'pipe'` - Creates a pipe between child and parent (default)
* - `'ignore'` - Ignores the stream
* - `'inherit'` - Uses parent's stream
* - `'overlapped'` - Windows-specific overlapped I/O
*/
export type IOType = 'pipe' | 'ignore' | 'inherit' | 'overlapped';
/**
* Configuration for process stdio (stdin, stdout, stderr) streams.
* Can be a single value applied to all streams, or an array specifying each stream individually.
* - `'ipc'` - Creates an IPC channel for communication with the parent
*
* @example
* // All streams piped
* stdio: 'pipe'
*
* @example
* // Custom configuration per stream: [stdin, stdout, stderr]
* stdio: ['ignore', 'pipe', 'pipe']
*/
export type StdioType = IOType | 'ipc' | Array<IOType | 'ipc'>;
/**
* Result object returned by {@link spawnSync} when the child process completes synchronously.
*
* @template T - Type of stdout/stderr (string or Buffer)
* @property {number} pid - Process ID of the spawned child
* @property {Array<T | null>} output - Array containing stdout/stderr values
* @property {T} stdout - Standard output from the process
* @property {T} stderr - Standard error from the process
* @property {number | null} status - Exit code, or `null` if killed by signal
* @property {NodeJS.Signals | null} signal - Signal that terminated the process, or `null`
* @property {Error | undefined} error - Error object if the spawn failed
*/
export interface SpawnSyncReturns<T> {
pid: number;
output: Array<T | null>;
stdout: T;
stderr: T;
status: number | null;
signal: NodeJS.Signals | null;
error?: Error | undefined;
}
/**
* Check if a value is a spawn error with expected error properties.
* Tests for common error properties from child process failures.
*
* @param {unknown} value - Value to check
* @returns {boolean} `true` if the value has spawn error properties
*
* @example
* try {
* await spawn('nonexistent-command')
* } catch (error) {
* if (isSpawnError(error)) {
* console.error(`Spawn failed: ${error.code}`)
* }
* }
*/
/*@__NO_SIDE_EFFECTS__*/
export declare function isSpawnError(value: unknown): value is SpawnError;
/**
* Check if stdio configuration matches a specific type.
* When called with one argument, validates if it's a valid stdio type.
* When called with two arguments, checks if the stdio config matches the specified type.
*
* @param {string | string[]} stdio - Stdio configuration to check
* @param {StdioType | undefined} type - Expected stdio type (optional)
* @returns {boolean} `true` if stdio matches the type or is valid
*
* @example
* // Check if valid stdio type
* isStdioType('pipe') // true
* isStdioType('invalid') // false
*
* @example
* // Check if stdio matches specific type
* isStdioType('pipe', 'pipe') // true
* isStdioType(['pipe', 'pipe', 'pipe'], 'pipe') // true
* isStdioType('ignore', 'pipe') // false
*/
/*@__NO_SIDE_EFFECTS__*/
export declare function isStdioType(stdio: string | string[], type?: StdioType | undefined): boolean;
/*@__NO_SIDE_EFFECTS__*/
// Duplicated from Node.js child_process.SpawnOptions
// These are the options passed to child_process.spawn()
interface NodeSpawnOptions {
cwd?: string | URL | undefined;
env?: NodeJS.ProcessEnv | undefined;
argv0?: string | undefined;
stdio?: any;
detached?: boolean | undefined;
uid?: number | undefined;
gid?: number | undefined;
serialization?: 'json' | 'advanced' | undefined;
shell?: boolean | string | undefined;
windowsVerbatimArguments?: boolean | undefined;
windowsHide?: boolean | undefined;
signal?: AbortSignal | undefined;
timeout?: number | undefined;
killSignal?: NodeJS.Signals | number | undefined;
}
// Duplicated from Node.js child_process.ChildProcess
// This represents a spawned child process
interface ChildProcessType {
stdin: NodeJS.WritableStream | null;
stdout: NodeJS.ReadableStream | null;
stderr: NodeJS.ReadableStream | null;
readonly channel?: any;
readonly stdio: [
NodeJS.WritableStream | null,
NodeJS.ReadableStream | null,
NodeJS.ReadableStream | null,
NodeJS.ReadableStream | NodeJS.WritableStream | null | undefined,
NodeJS.ReadableStream | NodeJS.WritableStream | null | undefined
];
readonly killed: boolean;
readonly pid?: number | undefined;
readonly connected: boolean;
readonly exitCode: number | null;
readonly signalCode: NodeJS.Signals | null;
readonly spawnargs: string[];
readonly spawnfile: string;
kill(signal?: NodeJS.Signals | number): boolean;
send(message: any, callback?: (error: Error | null) => void): boolean;
send(message: any, sendHandle?: any | undefined, callback?: (error: Error | null) => void): boolean;
send(message: any, sendHandle?: any | undefined, options?: any | undefined, callback?: (error: Error | null) => void): boolean;
disconnect(): void;
unref(): void;
ref(): void;
}
// Duplicated from Node.js stream.Writable
interface WritableStreamType {
writable: boolean;
writableEnded: boolean;
writableFinished: boolean;
writableHighWaterMark: number;
writableLength: number;
writableObjectMode: boolean;
writableCorked: number;
destroyed: boolean;
write(chunk: any, encoding?: BufferEncoding | undefined, callback?: (error?: Error | null) => void): boolean;
write(chunk: any, callback?: (error?: Error | null) => void): boolean;
end(cb?: () => void): this;
end(chunk: any, cb?: () => void): this;
end(chunk: any, encoding?: BufferEncoding | undefined, cb?: () => void): this;
cork(): void;
uncork(): void;
destroy(error?: Error | undefined): this;
}
/**
* Options for spawning a child process with {@link spawn}.
* Extends Node.js spawn options with additional Socket-specific functionality.
*
* @property {import('./spinner').Spinner | undefined} spinner - Spinner instance to pause during execution
* @property {boolean | undefined} stdioString - Convert output to strings (default: `true`)
* @property {boolean | undefined} stripAnsi - Remove ANSI codes from output (default: `true`)
* @property {string | URL | undefined} cwd - Current working directory
* @property {NodeJS.ProcessEnv | undefined} env - Environment variables
* @property {StdioType | undefined} stdio - Stdio configuration
* @property {boolean | string | undefined} shell - Run command in shell
* @property {number | undefined} timeout - Timeout in milliseconds
* @property {AbortSignal | undefined} signal - Abort signal
* @property {number | undefined} uid - User identity (POSIX)
* @property {number | undefined} gid - Group identity (POSIX)
*/
export type SpawnOptions = import('./objects').Remap<NodeSpawnOptions & {
spinner?: import('./spinner').Spinner | undefined;
stdioString?: boolean;
stripAnsi?: boolean;
}>;
export type SpawnResult = PromiseSpawnResult;
/**
* Result object returned when a spawned process completes.
*
* @property {string} cmd - Command that was executed
* @property {string[] | readonly string[]} args - Arguments passed to the command
* @property {number} code - Process exit code
* @property {NodeJS.Signals | null} signal - Signal that terminated the process, if any
* @property {string | Buffer} stdout - Standard output (string if `stdioString: true`, Buffer otherwise)
* @property {string | Buffer} stderr - Standard error (string if `stdioString: true`, Buffer otherwise)
*/
export type SpawnStdioResult = {
cmd: string;
args: string[] | readonly string[];
code: number;
signal: NodeJS.Signals | null;
stdout: string | Buffer;
stderr: string | Buffer;
};
/**
* Spawn a child process and return a promise that resolves when it completes.
* Provides enhanced error handling, output capture, and cross-platform support.
*
* SECURITY: This function uses array-based arguments which prevent command injection.
* Arguments in the `args` array are passed directly to the OS without shell
* interpretation. Shell metacharacters (;|&$()`) are treated as literal strings,
* not as commands or operators. This is the PRIMARY SECURITY DEFENSE.
*
* Even when shell: true is used (on Windows for .cmd/.bat execution), the array-based
* approach remains secure because Node.js properly escapes each argument before passing
* to the shell.
*
* @param {string} cmd - Command to execute (not user-controlled)
* @param {string[] | readonly string[] | undefined} args - Array of arguments (safe even with user input)
* @param {SpawnOptions | undefined} options - Spawn options for process configuration
* @param {SpawnExtra | undefined} extra - Extra options for promise-spawn
* @returns {SpawnResult} Promise that resolves with process exit information
*
* @throws {SpawnError} When the process exits with non-zero code or is terminated by signal
*
* @example
* // Basic usage - spawn and wait for completion
* const result = await spawn('git', ['status'])
* console.log(result.stdout)
*
* @example
* // With options - set working directory and environment
* const result = await spawn('npm', ['install'], {
* cwd: '/path/to/project',
* env: { NODE_ENV: 'production' }
* })
*
* @example
* // ✔ DO THIS - Array-based arguments (safe)
* spawn('git', ['commit', '-m', userMessage])
* // Each argument is properly escaped, even if userMessage = "foo; rm -rf /"
*
* @example
* // ✖ NEVER DO THIS - String concatenation (vulnerable)
* spawn(`git commit -m "${userMessage}"`, { shell: true })
* // Vulnerable to injection if userMessage = '"; rm -rf / #'
*
* @example
* // Access stdin for interactive processes
* const result = spawn('cat', [])
* result.stdin?.write('Hello\n')
* result.stdin?.end()
* const { stdout } = await result
* console.log(stdout) // 'Hello'
*
* @example
* // Handle errors with exit codes
* try {
* await spawn('exit', ['1'])
* } catch (error) {
* if (isSpawnError(error)) {
* console.error(`Failed with code ${error.code}`)
* console.error(error.stderr)
* }
* }
*/
export declare function spawn(cmd: string, args?: string[] | readonly string[], options?: SpawnOptions | undefined, extra?: SpawnExtra | undefined): SpawnResult;
/*@__NO_SIDE_EFFECTS__*/
/**
* Options for synchronously spawning a child process with {@link spawnSync}.
* Same as {@link SpawnOptions} but excludes the `spinner` property (not applicable for synchronous execution).
*/
export type SpawnSyncOptions = Omit<SpawnOptions, 'spinner'>;
/**
* Synchronously spawn a child process and wait for it to complete.
* Blocks execution until the process exits, returning all output and exit information.
*
* WARNING: This function blocks the event loop. Use {@link spawn} for async operations.
*
* @param {string} cmd - Command to execute
* @param {string[] | readonly string[] | undefined} args - Array of arguments
* @param {SpawnSyncOptions | undefined} options - Spawn options for process configuration
* @returns {SpawnSyncReturns<string | Buffer>} Process result with exit code and captured output
*
* @example
* // Basic synchronous spawn
* const result = spawnSync('git', ['status'])
* console.log(result.stdout)
* console.log(result.status) // exit code
*
* @example
* // With options
* const result = spawnSync('npm', ['install'], {
* cwd: '/path/to/project',
* stdioString: true
* })
* if (result.status !== 0) {
* console.error(result.stderr)
* }
*
* @example
* // Get raw buffer output
* const result = spawnSync('cat', ['binary-file'], {
* stdioString: false
* })
* console.log(result.stdout) // Buffer
*
* @example
* // Handle process errors
* const result = spawnSync('nonexistent-command')
* if (result.error) {
* console.error('Failed to spawn:', result.error)
* }
*/
export declare function spawnSync(cmd: string, args?: string[] | readonly string[], options?: SpawnSyncOptions | undefined): SpawnSyncReturns<string | Buffer>;
export {};