Skip to content

Commit 08c9ab3

Browse files
committed
refactor: extrair execWithSudo() e eliminar shell injection na escalação sudo
Extrai lógica duplicada de sudo re-exec para helper compartilhado, troca execSync com template literal por execFileSync com array de args (elimina shell injection), e propaga bunPath no PreflightResult para evitar chamada redundante a findBunBinary().
1 parent e20b7b6 commit 08c9ab3

6 files changed

Lines changed: 40 additions & 34 deletions

File tree

src/patcher/binary-finder.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@ export function findClaudeBinary(): string {
186186
}
187187

188188
export function findBunBinary(): string {
189+
if (process.env.ANYBUDDY_BUN_PATH && existsSync(process.env.ANYBUDDY_BUN_PATH)) {
190+
return process.env.ANYBUDDY_BUN_PATH;
191+
}
192+
189193
const onPath = which('bun');
190194
if (onPath) return onPath;
191195

src/patcher/preflight.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,12 @@ export function runPreflight({ requireBinary = true } = {}): PreflightResult {
1616

1717
// 1. Check bun is installed (deferred — may not be needed for Node-based installs)
1818
let bunVersion: string | null = null;
19+
let bunPath: string | null = null;
1920
let bunMissing = false;
2021
try {
21-
const bunPath = findBunBinary();
22-
bunVersion = execSync(`"${bunPath}" --version`, { encoding: 'utf-8', timeout: 5000 }).trim();
22+
const resolved = findBunBinary();
23+
bunVersion = execSync(`"${resolved}" --version`, { encoding: 'utf-8', timeout: 5000 }).trim();
24+
bunPath = resolved;
2325
} catch {
2426
bunMissing = true;
2527
}
@@ -176,8 +178,8 @@ export function runPreflight({ requireBinary = true } = {}): PreflightResult {
176178
for (const e of errors) {
177179
console.log(chalk.red(` Error: ${e}\n`));
178180
}
179-
return { ok: false, binaryPath, userId, saltCount, bunVersion, needsSudo };
181+
return { ok: false, binaryPath, userId, saltCount, bunVersion, bunPath, needsSudo };
180182
}
181183

182-
return { ok: true, binaryPath, userId, saltCount, bunVersion, needsSudo };
184+
return { ok: true, binaryPath, userId, saltCount, bunVersion, bunPath, needsSudo };
183185
}

src/patcher/sudo.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { execFileSync } from 'child_process';
2+
import chalk from 'chalk';
3+
import type { PreflightResult } from '@/types.js';
4+
5+
/** Re-exec the current CLI under sudo, forwarding resolved paths as env vars. */
6+
export function execWithSudo(preflight: PreflightResult): never {
7+
const args = process.argv.slice(1);
8+
console.log(chalk.yellow(' Binary requires elevated permissions. Re-running with sudo...\n'));
9+
10+
const env: string[] = [`HOME=${process.env.HOME ?? ''}`];
11+
if (preflight.bunPath) env.push(`ANYBUDDY_BUN_PATH=${preflight.bunPath}`);
12+
if (preflight.binaryPath) env.push(`CLAUDE_BINARY=${preflight.binaryPath}`);
13+
14+
try {
15+
execFileSync('sudo', ['env', ...env, process.execPath, ...args], {
16+
stdio: 'inherit',
17+
});
18+
process.exit(0);
19+
} catch {
20+
console.error(chalk.red(' Sudo failed. Try: sudo any-buddy'));
21+
process.exit(1);
22+
}
23+
}

src/tui/commands/buddies.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import chalk from 'chalk';
2-
import { execSync } from 'child_process';
2+
33
import { select } from '@inquirer/prompts';
44
import { ORIGINAL_SALT, RARITY_STARS } from '@/constants.js';
55
import { roll } from '@/generation/index.js';
66
import { DEFAULT_PERSONALITIES } from '@/personalities.js';
77
import { isNodeRuntime, verifySalt, isClaudeRunning, getMinSaltCount } from '@/patcher/salt-ops.js';
88
import { patchBinary } from '@/patcher/patch.js';
99
import { runPreflight } from '@/patcher/preflight.js';
10+
import { execWithSudo } from '@/patcher/sudo.js';
1011
import {
1112
loadPetConfigV2,
1213
savePetConfigV2,
@@ -53,20 +54,7 @@ export async function runBuddies(): Promise<void> {
5354
}
5455

5556
if (preflight.needsSudo && process.getuid?.() !== 0) {
56-
const args = process.argv.slice(1);
57-
console.log(chalk.yellow(' Binary requires elevated permissions. Re-running with sudo...\n'));
58-
try {
59-
execSync(
60-
`sudo --preserve-env=HOME ${process.execPath} ${args.map((a) => `"${a}"`).join(' ')}`,
61-
{
62-
stdio: 'inherit',
63-
},
64-
);
65-
process.exit(0);
66-
} catch {
67-
console.error(chalk.red(' Sudo failed. Try: sudo any-buddy'));
68-
process.exit(1);
69-
}
57+
execWithSudo(preflight);
7058
}
7159

7260
const config = loadPetConfigV2();

src/tui/commands/interactive.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import chalk from 'chalk';
2-
import { execSync } from 'child_process';
2+
33
import { confirm, input, select } from '@inquirer/prompts';
44
import type { CliFlags, DesiredTraits } from '@/types.js';
55
import { SPECIES, EYES, RARITIES, HATS, STAT_NAMES, ORIGINAL_SALT } from '@/constants.js';
@@ -14,6 +14,7 @@ import {
1414
} from '@/patcher/salt-ops.js';
1515
import { patchBinary } from '@/patcher/patch.js';
1616
import { runPreflight } from '@/patcher/preflight.js';
17+
import { execWithSudo } from '@/patcher/sudo.js';
1718
import {
1819
loadPetConfig,
1920
loadPetConfigV2,
@@ -174,20 +175,7 @@ function runSetup(): SetupResult {
174175
}
175176

176177
if (preflight.needsSudo && process.getuid?.() !== 0) {
177-
const args = process.argv.slice(1);
178-
console.log(chalk.yellow(' Binary requires elevated permissions. Re-running with sudo...\n'));
179-
try {
180-
execSync(
181-
`sudo --preserve-env=HOME ${process.execPath} ${args.map((a) => `"${a}"`).join(' ')}`,
182-
{
183-
stdio: 'inherit',
184-
},
185-
);
186-
process.exit(0);
187-
} catch {
188-
console.error(chalk.red(' Sudo failed. Try: sudo any-buddy'));
189-
process.exit(1);
190-
}
178+
execWithSudo(preflight);
191179
}
192180
const userId = preflight.userId;
193181
const binaryPath = preflight.binaryPath;

src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export interface PreflightResult {
9696
userId: string;
9797
saltCount: number;
9898
bunVersion: string | null;
99+
bunPath: string | null;
99100
needsSudo: boolean;
100101
}
101102

0 commit comments

Comments
 (0)