From f5bc8582c132dcada3b5c7e15a28f64d74eb50e3 Mon Sep 17 00:00:00 2001 From: Coden Date: Fri, 12 Jun 2026 17:29:55 +0900 Subject: [PATCH] =?UTF-8?q?fix(claude):=20worktree=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=88=EB=8C=80=EC=84=B1=20=EA=B0=80=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=EC=83=81=EB=8C=80=20git=5Fworktree=20FP=20=EC=B0=A8=EB=8B=A8?= =?UTF-8?q?=20(M-2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 결함: derive_git_branch는 is_safe_base_path(traversal 차단)만 호출하고 is_absolute() 검사가 없었다. "repo"/"." 같은 상대경로가 입력되면 canonicalize가 understatus 프로세스 cwd 기준으로 해석해 엉뚱한 repo의 branch를 반환하는 false-positive가 발생했다. 수정(Option A, ralplan 합의): - derive_git_branch에 is_absolute() 가드 추가(is_safe_base_path 직후) - derive_git_branch_from_cwd의 기존 가드에 대칭 주석 1줄 보강 ("한쪽 변경 시 양쪽 동기화" 계약 명시) - 단위 테스트 git_worktree_relative_path_rejected 추가: RawWorkspace를 직접 구성해 cwd-독립으로 상대 입력 즉시 거부를 고정 (통합 테스트는 러너 cwd에 .git이 없으면 false-green 위험) is_safe_base_path 계약·이름은 불변(Option B 폐기). Co-Authored-By: Claude Sonnet 4.6 --- src/claude.rs | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/src/claude.rs b/src/claude.rs index 4b0e1dd..bbf36a6 100644 --- a/src/claude.rs +++ b/src/claude.rs @@ -338,6 +338,12 @@ fn derive_git_branch(workspace: &RawWorkspace) -> Option { if !is_safe_base_path(base_path) { return None; } + // 상대경로 거부: 상대 git_worktree/repo는 understatus 프로세스 cwd 기준으로 해석돼 + // 엉뚱한 repo의 branch를 도출하는 false-positive를 만든다(canonicalize가 cwd-상대 해석). + // 대칭: cwd 경로(derive_git_branch_from_cwd)의 is_absolute 가드와 동일 — 한쪽 변경 시 양쪽 동기화. + if !std::path::Path::new(base_path).is_absolute() { + return None; + } read_branch_from_git_dir(base_path) } @@ -446,6 +452,7 @@ fn derive_git_branch_from_cwd(cwd: &str) -> Option { return None; } // 상대경로 거부: 프로세스 cwd 기준 해석으로 엉뚱한 repo branch를 도출하는 false-positive를 막는다. + // 대칭: worktree 경로(derive_git_branch)의 절대성 가드와 동일 — 한쪽 변경 시 양쪽 동기화. if !std::path::Path::new(cwd).is_absolute() { return None; } @@ -1466,6 +1473,48 @@ mod tests { assert_eq!(derive_git_branch_from_cwd("./x"), None); } + /// M-2 회귀 가드: worktree 경로도 상대 git_worktree/repo를 거부해야 한다(절대성 가드). + /// 상대경로는 프로세스 cwd 기준 해석돼 엉뚱한 repo branch FP를 만들므로 None이어야 한다. + /// derive_git_branch를 직접 호출해 is_absolute 가드가 fs 접근 전 즉시 거부함을 cwd-독립으로 검증 + /// (parse_claude_input 통합 테스트는 러너 cwd에 repo/.git이 없으면 false-green 위험). + #[test] + fn git_worktree_relative_path_rejected() { + // git_worktree 상대경로 → None + let ws_rel = RawWorkspace { + git_worktree: Some("repo".to_string()), + repo: None, + current_dir: None, + project_dir: None, + }; + assert_eq!( + derive_git_branch(&ws_rel), + None, + "상대 git_worktree는 절대성 가드로 거부" + ); + + // "." 상대 → None + let ws_dot = RawWorkspace { + git_worktree: Some(".".to_string()), + repo: None, + current_dir: None, + project_dir: None, + }; + assert_eq!( + derive_git_branch(&ws_dot), + None, + "'.' 상대 git_worktree 거부" + ); + + // repo 분기(.or(repo))도 상대 거부 + let ws_repo = RawWorkspace { + git_worktree: None, + repo: Some("repo".to_string()), + current_dir: None, + project_dir: None, + }; + assert_eq!(derive_git_branch(&ws_repo), None, "상대 repo도 거부"); + } + // === parse_lterm_input (spec §6.2, §10) === /// 정상 lterm JSON: 표시 필드가 정확히 매핑된다. git_branch는 cwd가 실존하지 않는