Every config.toml writer wrote a tmp beside the path and renamed it over the path. With config.toml symlinked into a dotfiles repo, the rename replaced the link with a regular file: the repo copy went stale and the link was gone. `config_write_dest` resolves the destination before a writer names its tmp. For the user's own config path a symlink resolves to its referent, so the tmp sits on the referent's filesystem and the rename stays atomic. A dangling link is an error, not a clobber. All eight writers call it: `save_config`, the four MCP writers, and the three import writers. Only the user's config path is followed, and only while `user_kigi_home()` resolves. `kigi mcp add --scope project` and the Claude project import write `.kigi/config.toml` inside the repo; a hostile repo could ship that file as a symlink to any file the user owns. With no home, `kigi_home()` falls back to `./.kigi`, which is that same project tree, so nothing is followed then. Untrusted paths come back unchanged and the link itself is replaced, as before. AGENTS.md records the rule. Tests (unix; run on Linux under WSL): helper cases including the no-home case, a project-scope save that spares the link's target, and an end-to-end save under KIGI_SHARE_DIR. The end-to-end test fails with following disabled. Windows symlinks were not exercised: the host lacks the privilege. Ported from upstream (grok-build config/fs_atomic.rs LeafSymlink::Follow, 1.0.35): the rule, not the 1375-line module.
35 lines
984 B
Rust
35 lines
984 B
Rust
// `McpOAuthConfig` / `McpOAuthConfigMap` re-exported via `mcp` (see `mcp.rs`).
|
|
|
|
mod campaigns;
|
|
mod hints;
|
|
mod load;
|
|
mod mcp;
|
|
mod permissions;
|
|
mod persist;
|
|
mod resolve;
|
|
mod settings_writes;
|
|
mod tips;
|
|
mod worktree;
|
|
mod write_dest;
|
|
|
|
pub use campaigns::{
|
|
load_effective_config, load_effective_config_disk_only, persist_models_default,
|
|
remote_campaigns_from_settings, set_remote_campaigns_from_settings, sync_campaign_fields,
|
|
};
|
|
pub use hints::*;
|
|
pub use load::*;
|
|
pub use mcp::*;
|
|
pub use permissions::*;
|
|
pub use persist::*;
|
|
// `remote` lives in the `kigi-config-types` crate (dependency inversion);
|
|
// re-exported so `crate::util::config::{RemoteSettings, GoalRoleModel}` keep working.
|
|
pub use kigi_config_types::{
|
|
CampaignOverride, ContextualHintsRemote, DisplayRefreshSettings, DoomLoopRecoverySettings,
|
|
GoalRoleModel, RemoteSettings,
|
|
};
|
|
pub use resolve::*;
|
|
pub use settings_writes::*;
|
|
pub use tips::*;
|
|
pub use worktree::*;
|
|
pub(crate) use write_dest::config_write_dest;
|