fix(security): fix P0 critical bugs in agent selection and command injection
P0-1: Fix Agent Selection Bug (decide.ts) - Move wildcard agent (conductor) to end of AVAILABLE_AGENTS array - Update selectAgent() to prioritize specific intents over wildcard - Previously, wildcard matched first, causing all intents to select conductor P0-2: Fix Command Injection Vulnerability (codex-reviewer.ts) - Add escapeShellString() function to sanitize user content - Escape backslashes, quotes, $, backticks, newlines, carriage returns - Apply escaping in execTmux() and buildCodexCommand() - Previously, unescaped user content could execute arbitrary shell commands Security: Prevents attackers from injecting malicious commands via user-controlled content (code review input) that gets passed to tmux.
This commit is contained in:
parent
68a9b16c2b
commit
da1c5b26e0
@ -29,6 +29,24 @@ const DEFAULT_TIMEOUT = 5 * 60 * 1000;
|
||||
/** デフォルトtmuxターゲット (MacBook用) */
|
||||
const DEFAULT_TMUX_TARGET = "%2"; // カエデ (CodeGen) のペイン
|
||||
|
||||
/**
|
||||
* シェルコマンド用に文字列をエスケープ
|
||||
* tmux send-keys に安全に渡すためのエスケープ処理
|
||||
*
|
||||
* @param str - エスケープする文字列
|
||||
* @returns エスケープされた文字列
|
||||
*/
|
||||
function escapeShellString(str: string): string {
|
||||
// シェル特殊文字をエスケープ
|
||||
return str
|
||||
.replace(/\\/g, "\\\\") // バックスラッシュ
|
||||
.replace(/"/g, '\\"') // ダブルクォート
|
||||
.replace(/\$/g, "\\$") // ドル記号
|
||||
.replace(/`/g, "\\`") // バッククォート
|
||||
.replace(/\n/g, "\\n") // 改行
|
||||
.replace(/\r/g, "\\r"); // キャリッジリターン
|
||||
}
|
||||
|
||||
/**
|
||||
* tmuxコマンドを実行
|
||||
*
|
||||
@ -44,8 +62,9 @@ async function execTmux(
|
||||
): Promise<TmuxResult> {
|
||||
const { timeout = DEFAULT_TIMEOUT, env = {} } = options;
|
||||
|
||||
// tmux send-keys でコマンドを送信
|
||||
const sendCommand = `tmux send-keys -t ${target} "${command}" Enter`;
|
||||
// tmux send-keys でコマンドを送信 (コマンドインジェクション対策)
|
||||
const escapedCommand = escapeShellString(command);
|
||||
const sendCommand = `tmux send-keys -t ${target} "${escapedCommand}" Enter`;
|
||||
|
||||
try {
|
||||
// タイムアウト付きで実行
|
||||
@ -138,8 +157,9 @@ function buildCodexCommand(content: string, options: ReviewOptions): string {
|
||||
|
||||
if (useFile) {
|
||||
// TODO: 一時ファイルを使用する場合
|
||||
// 今回は簡易的に引数渡し
|
||||
return `codex review "${content.replace(/"/g, '\\"')}"`;
|
||||
// 今回は簡易的に引数渡し (コマンドインジェクション対策)
|
||||
const escapedContent = escapeShellString(content);
|
||||
return `codex review "${escapedContent}"`;
|
||||
}
|
||||
|
||||
// オプションを付与
|
||||
@ -157,7 +177,9 @@ function buildCodexCommand(content: string, options: ReviewOptions): string {
|
||||
opts.push("--verbose");
|
||||
}
|
||||
|
||||
const cmd = `codex review ${opts.join(" ")} ${content}`;
|
||||
// コマンドインジェクション対策: contentをエスケープしてクォート
|
||||
const escapedContent = escapeShellString(content);
|
||||
const cmd = `codex review ${opts.join(" ")} "${escapedContent}"`;
|
||||
return cmd;
|
||||
}
|
||||
|
||||
|
||||
@ -31,10 +31,12 @@ interface AgentDefinition {
|
||||
|
||||
/** 利用可能なエージェント一覧 */
|
||||
const AVAILABLE_AGENTS: AgentDefinition[] = [
|
||||
{ id: "conductor", intents: ["*"], capacity: 10 },
|
||||
// 具体的な意図を持つエージェントを先に配置
|
||||
{ id: "codegen", intents: ["create", "update"], capacity: 5 },
|
||||
{ id: "review", intents: ["verify", "check"], capacity: 3 },
|
||||
{ id: "deploy", intents: ["deploy", "release"], capacity: 2 },
|
||||
// ワイルドカードエージェントは最後に配置 (fallbackとして機能)
|
||||
{ id: "conductor", intents: ["*"], capacity: 10 },
|
||||
];
|
||||
|
||||
/**
|
||||
@ -179,10 +181,21 @@ function selectStrategy(intent: string): string {
|
||||
|
||||
/**
|
||||
* 意図に基づいてエージェントを選択する
|
||||
*
|
||||
* 具体的な意図へのマッチを優先し、ワイルドカードはfallbackとして使用
|
||||
*/
|
||||
function selectAgent(intent: string): string | undefined {
|
||||
const agent = AVAILABLE_AGENTS.find((a) => a.intents.includes(intent) || a.intents.includes("*"));
|
||||
return agent?.id;
|
||||
// まず具体的な意図を探す
|
||||
const specificAgent = AVAILABLE_AGENTS.find(
|
||||
(a) => a.intents.includes(intent) && !a.intents.includes("*"),
|
||||
);
|
||||
if (specificAgent) {
|
||||
return specificAgent.id;
|
||||
}
|
||||
|
||||
// 具体的なマッチがなければワイルドカードを探す
|
||||
const wildcardAgent = AVAILABLE_AGENTS.find((a) => a.intents.includes("*"));
|
||||
return wildcardAgent?.id;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
Reference in New Issue
Block a user