Merge pull request #61 from strazzere/fix/refactor_shell_commands

fix: ensure exec usage does not allow poisoning
This commit is contained in:
Abhigyan Patwari 2026-02-28 07:13:25 +05:30 • committed by GitHub
commit 2852a23b24
4 changed files with 31 additions and 24 deletions

View file

@ -7,7 +7,7 @@
import path from 'path';
import readline from 'readline';
import { execSync } from 'child_process';
import { execSync, execFileSync } from 'child_process';
import cliProgress from 'cli-progress';
import { getGitRoot, isGitRepo } from '../storage/git.js';
import { getStoragePaths, loadMeta, loadCLIConfig, saveCLIConfig } from '../storage/repo-manager.js';
@ -343,10 +343,11 @@ function hasGhCLI(): boolean {
function publishGist(htmlPath: string): { url: string; rawUrl: string } | null {
try {
const output = execSync(
`gh gist create "${htmlPath}" --desc "Repository Wiki — generated by GitNexus" --public`,
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] },
).trim();
const output = execFileSync('gh', [
'gist', 'create', htmlPath,
'--desc', 'Repository Wiki — generated by GitNexus',
'--public',
], { encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
// gh gist create prints the gist URL as the last line
const lines = output.split('\n');

View file

@ -12,7 +12,7 @@
import fs from 'fs/promises';
import path from 'path';
import { execSync } from 'child_process';
import { execSync, execFileSync } from 'child_process';
import {
initWikiDb,
@ -712,8 +712,8 @@ export class WikiGenerator {
private getChangedFiles(fromCommit: string, toCommit: string): string[] {
try {
const output = execSync(
`git diff ${fromCommit}..${toCommit} --name-only`,
const output = execFileSync(
'git', ['diff', `${fromCommit}..${toCommit}`, '--name-only'],
{ cwd: this.repoPath },
).toString().trim();
return output ? output.split('\n').filter(Boolean) : [];

View file

@ -1029,30 +1029,30 @@ export class LocalBackend {
await this.ensureInitialized(repo.id);
const scope = params.scope || 'unstaged';
const { execSync } = await import('child_process');
// Build git diff command based on scope
let diffCmd: string;
const { execFileSync } = await import('child_process');
// Build git diff args based on scope (using execFileSync to avoid shell injection)
let diffArgs: string[];
switch (scope) {
case 'staged':
diffCmd = 'git diff --staged --name-only';
diffArgs = ['diff', '--staged', '--name-only'];
break;
case 'all':
diffCmd = 'git diff HEAD --name-only';
diffArgs = ['diff', 'HEAD', '--name-only'];
break;
case 'compare':
if (!params.base_ref) return { error: 'base_ref is required for "compare" scope' };
diffCmd = `git diff ${params.base_ref} --name-only`;
diffArgs = ['diff', params.base_ref, '--name-only'];
break;
case 'unstaged':
default:
diffCmd = 'git diff --name-only';
diffArgs = ['diff', '--name-only'];
break;
}
let changedFiles: string[];
try {
const output = execSync(diffCmd, { cwd: repo.repoPath, encoding: 'utf-8' });
const output = execFileSync('git', diffArgs, { cwd: repo.repoPath, encoding: 'utf-8' });
changedFiles = output.trim().split('\n').filter(f => f.length > 0);
} catch (err: any) {
return { error: `Git diff failed: ${err.message}` };
@ -1226,9 +1226,15 @@ export class LocalBackend {
// Simple text search across the repo for the old name (in files not already covered by graph)
try {
const { execSync } = await import('child_process');
const rgCmd = `rg -l --type-add "code:*.{ts,tsx,js,jsx,py,go,rs,java}" -t code "\\b${oldName}\\b" .`;
const output = execSync(rgCmd, { cwd: repo.repoPath, encoding: 'utf-8', timeout: 5000 });
const { execFileSync } = await import('child_process');
const rgArgs = [
'-l',
'--type-add', 'code:*.{ts,tsx,js,jsx,py,go,rs,java}',
'-t', 'code',
`\\b${oldName}\\b`,
'.',
];
const output = execFileSync('rg', rgArgs, { cwd: repo.repoPath, encoding: 'utf-8', timeout: 5000 });
const files = output.trim().split('\n').filter(f => f.length > 0);
for (const file of files) {

View file

@ -5,7 +5,7 @@
* Returns a hint for the LLM to call analyze if stale.
*/
import { execSync } from 'child_process';
import { execFileSync } from 'child_process';
import path from 'path';
export interface StalenessInfo {
@ -20,8 +20,8 @@ export interface StalenessInfo {
export function checkStaleness(repoPath: string, lastCommit: string): StalenessInfo {
try {
// Get count of commits between lastCommit and HEAD
const result = execSync(
`git rev-list --count ${lastCommit}..HEAD`,
const result = execFileSync(
'git', ['rev-list', '--count', `${lastCommit}..HEAD`],
{ cwd: repoPath, encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe'] }
).trim();