mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-09-24 05:16:19 -07:00
fix(launcher): validate login shell paths before execution
- Fall back to absolute default shells when configured values are relative - Reject invalid shells before spawning them
This commit is contained in:
@@ -1,5 +1,6 @@
|
|||||||
import test from 'node:test';
|
import test from 'node:test';
|
||||||
import assert from 'node:assert/strict';
|
import assert from 'node:assert/strict';
|
||||||
|
import os from 'node:os';
|
||||||
import { applyLoginShellPath, mergePathValues, readLoginShellPath } from './login-shell-path';
|
import { applyLoginShellPath, mergePathValues, readLoginShellPath } from './login-shell-path';
|
||||||
|
|
||||||
const wrap = (value: string) =>
|
const wrap = (value: string) =>
|
||||||
@@ -24,6 +25,51 @@ test('readLoginShellPath returns null when markers are missing', async () => {
|
|||||||
assert.equal(value, null);
|
assert.equal(value, null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('readLoginShellPath rejects relative explicit shells before execution', async () => {
|
||||||
|
for (const shell of ['zsh', './zsh', '']) {
|
||||||
|
await assert.rejects(
|
||||||
|
readLoginShellPath({
|
||||||
|
env: { SHELL: '/bin/zsh' },
|
||||||
|
shell,
|
||||||
|
runShell: async () => assert.fail('Invalid shell must not execute'),
|
||||||
|
}),
|
||||||
|
/Login shell must be an absolute path/,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('readLoginShellPath falls back through absolute default shells', async () => {
|
||||||
|
const originalUserInfo = os.userInfo;
|
||||||
|
try {
|
||||||
|
for (const shell of ['/bin/bash', 'bash', '', null]) {
|
||||||
|
Object.defineProperty(os, 'userInfo', {
|
||||||
|
value: () => ({ shell, username: 'test', uid: 1000, gid: 1000, homedir: '/Users/test' }),
|
||||||
|
});
|
||||||
|
await readLoginShellPath({
|
||||||
|
env: { SHELL: './zsh' },
|
||||||
|
runShell: async (selectedShell) => {
|
||||||
|
assert.equal(selectedShell, shell === '/bin/bash' ? '/bin/bash' : '/bin/zsh');
|
||||||
|
return wrap('/usr/bin');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Object.defineProperty(os, 'userInfo', {
|
||||||
|
value: () => {
|
||||||
|
throw new Error('User lookup failed');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await readLoginShellPath({
|
||||||
|
env: { SHELL: 'zsh' },
|
||||||
|
runShell: async (shell) => {
|
||||||
|
assert.equal(shell, '/bin/zsh');
|
||||||
|
return wrap('/usr/bin');
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
os.userInfo = originalUserInfo;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test('mergePathValues puts login entries first and keeps process-only entries', () => {
|
test('mergePathValues puts login entries first and keeps process-only entries', () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
mergePathValues('/Users/me/.local/bin:/usr/bin:/bin', '/usr/bin:/bin:/usr/sbin:/sbin'),
|
mergePathValues('/Users/me/.local/bin:/usr/bin:/bin', '/usr/bin:/bin:/usr/sbin:/sbin'),
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import { execFile } from 'node:child_process';
|
import { execFile } from 'node:child_process';
|
||||||
import os from 'node:os';
|
import os from 'node:os';
|
||||||
|
import { isAbsolute } from 'node:path';
|
||||||
|
|
||||||
const MARKER = '__SUBMINER_LOGIN_PATH__';
|
const MARKER = '__SUBMINER_LOGIN_PATH__';
|
||||||
const DEFAULT_TIMEOUT_MS = 5000;
|
const DEFAULT_TIMEOUT_MS = 5000;
|
||||||
@@ -29,9 +30,10 @@ const runShellDefault: RunShell = (shell, args, timeoutMs) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
function defaultShell(env: NodeJS.ProcessEnv): string {
|
function defaultShell(env: NodeJS.ProcessEnv): string {
|
||||||
if (env.SHELL) return env.SHELL;
|
if (env.SHELL && isAbsolute(env.SHELL)) return env.SHELL;
|
||||||
try {
|
try {
|
||||||
return os.userInfo().shell || '/bin/zsh';
|
const shell = os.userInfo().shell;
|
||||||
|
return shell && isAbsolute(shell) ? shell : '/bin/zsh';
|
||||||
} catch {
|
} catch {
|
||||||
return '/bin/zsh';
|
return '/bin/zsh';
|
||||||
}
|
}
|
||||||
@@ -43,6 +45,7 @@ function defaultShell(env: NodeJS.ProcessEnv): string {
|
|||||||
*/
|
*/
|
||||||
export async function readLoginShellPath(options: LoginShellPathOptions): Promise<string | null> {
|
export async function readLoginShellPath(options: LoginShellPathOptions): Promise<string | null> {
|
||||||
const shell = options.shell ?? defaultShell(options.env);
|
const shell = options.shell ?? defaultShell(options.env);
|
||||||
|
if (!isAbsolute(shell)) throw new Error('Login shell must be an absolute path');
|
||||||
const run = options.runShell ?? runShellDefault;
|
const run = options.runShell ?? runShellDefault;
|
||||||
// printenv keeps this shell-agnostic (fish exposes $PATH as a list).
|
// printenv keeps this shell-agnostic (fish exposes $PATH as a list).
|
||||||
const command = `printf '%s' '${MARKER}'; /usr/bin/printenv PATH; printf '%s' '${MARKER}'`;
|
const command = `printf '%s' '${MARKER}'; /usr/bin/printenv PATH; printf '%s' '${MARKER}'`;
|
||||||
|
|||||||
Reference in New Issue
Block a user