mirror of
https://github.com/codewhale-hq/Codewhale.git
synced 2026-11-08 08:04:25 -10:00
fix(subagent): never write the state .gitignore through a link
Hardens491d3df(keep .codewhale/state out of the user's git status). That commit writes the file with `if !ignore.exists() { let _ = fs::write(..) }`. exists() is false for a dangling symlink, and fs::write follows it. A cloned repository could ship .codewhale/state/.gitignore as a link to a path outside the workspace, such as ~/.config/git/ignore. Opening it in Codewhale would then create that file containing "*", and git would ignore every new file the person makes in every repository. The write now goes through the module's own guard: reject_root_relative_symlinks refuses a linked leaf, and OpenOptions::create_new (O_EXCL) never follows a link that appears after the check. AlreadyExists is a no-op, as before. 491d3df's choice that a failed write never fails the coordination lock is kept, but the failure is now logged as a warning on the subagent target instead of discarded. Evidence, from `cargo build -p codewhale-cli --bin codewhale` at this commit, starting `codewhale app-server --http` with an isolated HOME, CODEWHALE_HOME and runtime dir: - Clean git repo: .codewhale/state holds .gitignore ("*") and subagents.v1.lock, and `git status --short` prints nothing. - Repo that commits .codewhale/state/.gitignore as a dangling link to <tmp>/outside/ignore: subagents.v1.lock is created and the coordination lock is taken, and <tmp>/outside stays empty. git then lists the lock, because the repo replaced the ignore file with a link. - The491d3dfhole at the OS level: for a dangling link, exists() is False, and an open-for-write through the link creates the target with "*\n" (python3 -I check). Not run: the codewhale-tui lib test binary is OOM-killed in this container (memcg, 13.8 GB RSS), so the coordination tests are left to CI. cargo fmt --check passes. The warning is not visible in app-server stderr, because no subscriber prints the subagent target there. Refs CW-7214 Co-Authored-By: Claude Opus 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_01JQ5oApYVLjkFD3kNAc3yRt
This commit is contained in:
1 file changed
+13
-2
@@ -3669,8 +3669,19 @@ impl CoordinationProcessLock {
|
||||
reject_root_relative_symlinks(&requested_root, &lock_dir.join(SUBAGENT_STATE_LOCK_FILE))?;
|
||||
fs::create_dir_all(&lock_dir)?;
|
||||
let ignore = lock_dir.join(".gitignore");
|
||||
if !ignore.exists() {
|
||||
let _ = fs::write(&ignore, "*\n");
|
||||
let ignored = reject_root_relative_symlinks(&requested_root, &ignore).and_then(|()| {
|
||||
match fs::OpenOptions::new()
|
||||
.write(true)
|
||||
.create_new(true)
|
||||
.open(&ignore)
|
||||
{
|
||||
Ok(mut file) => file.write_all(b"*\n").map_err(Into::into),
|
||||
Err(error) if error.kind() == std::io::ErrorKind::AlreadyExists => Ok(()),
|
||||
Err(error) => Err(error.into()),
|
||||
}
|
||||
});
|
||||
if let Err(error) = ignored {
|
||||
tracing::warn!(target: "subagent", %error, "could not write {}", ignore.display());
|
||||
}
|
||||
// Creating a missing root can change its canonical spelling on
|
||||
// Windows (for example by adding a `\\?\` prefix). Re-resolve both
|
||||
|
||||
Reference in new issue
Block a user