{"record":{"id":"b671f7b1f9d81766","repo":"Hmbown/CodeWhale","slug":"refusing-a-symlinked-settings-lock-at","errorCode":null,"errorMessage":"Refusing a symlinked settings lock at {}","messagePattern":"Refusing a symlinked settings lock at (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/settings.rs","lineNumber":2166,"sourceCode":"    {\n        anyhow::bail!(\n            \"Refusing a non-regular settings lock at {}\",\n            lock_path.display()\n        );\n    }\n\n    let mut lock = fd_lock::RwLock::new(lock_file);\n    let _guard = lock\n        .write()\n        .with_context(|| format!(\"Failed to acquire settings lock at {}\", lock_path.display()))?;\n    operation()\n}\n\n/// Refuse to lock through a symlink: a planted `settings.toml.lock -> …` would\n/// otherwise let an attacker pick which file we create with our permissions.\nfn reject_settings_lock_symlink(lock_path: &Path) -> Result<()> {\n    match std::fs::symlink_metadata(lock_path) {\n        Ok(metadata) if metadata.file_type().is_symlink() => anyhow::bail!(\n            \"Refusing a symlinked settings lock at {}\",\n            lock_path.display()\n        ),\n        Ok(_) | Err(_) => Ok(()),\n    }\n}\n\n/// Replace `path` with `body` by writing an adjacent temporary file and\n/// renaming it into place.\n///\n/// A direct `fs::write` truncates first, so any concurrent reader — another\n/// Codewhale process, an editor, a `cat` — can observe a half-written file and\n/// parse it as truncated TOML, silently losing every key past the tear. A\n/// same-directory temp file plus the platform's replace primitive makes the\n/// swap atomic for readers: they see either the whole previous file or the\n/// whole new one.\n///\n/// The temp file inherits the existing file's permission bits when there is one","sourceCodeStart":2148,"sourceCodeEnd":2184,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/settings.rs#L2148-L2184","documentation":"Settings lock hardening: symlink_metadata showed settings.toml.lock is a symlink. Locking through it would let whoever planted the link choose which file gets created/locked with the process's owner-only permissions, so the lock is refused and the path named.","triggerScenarios":"Thrown at crates/tui/src/settings.rs:2166 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Remove the symlinked settings.toml.lock","Retry the settings operation"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}