feat: enhance safety and crash resilience in file operations; add fsync to critical writes and checks for path traversal

This commit is contained in:
asepharyana
2026-07-12 11:49:33 +07:00
parent 8767beef39
commit 87d0aac596
9 changed files with 79 additions and 26 deletions
+14 -6
View File
@@ -18,23 +18,31 @@ pub struct Harness;
impl Harness {
/// Decide whether a tool call is allowed to execute.
///
/// Flow: if the tool isn't flagged risky, allow immediately → otherwise
/// defer to `classify`.
/// Flow: if the tool isn't flagged risky, allow immediately → basic
/// content checks (path traversal) → defer to `classify`.
///
/// Why: `_args` and `_workspace_roots` are accepted for a future
/// content-aware classifier but currently unused — `classify` is a
/// stub that always allows.
/// Why: `classify` is currently a stub that always allows; the basic
/// checks here serve as defense-in-depth alongside the shell filters
/// and `resolve_path` in the tool modules.
///
/// Return: `Verdict::Allow` or `Verdict::Block(reason)`.
pub fn gate_tool_call(
tool_name: &str,
_args: &serde_json::Value,
args: &serde_json::Value,
_workspace_roots: &[&std::path::Path],
) -> Verdict {
if !crate::tool::tool_is_risky(tool_name) {
return Verdict::Allow;
}
// Basic path traversal check for file-mutating tools.
if matches!(tool_name, "write" | "edit" | "delete") {
if let Some(path) = args.get("path").and_then(|v| v.as_str()) {
if path.contains("..") {
return Verdict::Block("path traversal detected in 'path' argument".to_string());
}
}
}
Self::classify(tool_name)
}
+6 -2
View File
@@ -101,9 +101,13 @@ pub fn run_subagent(ctx: SubagentContext, tx: mpsc::Sender<SubagentEvent>) -> an
let (tools, tdefs) = build_subagent_tools(&ctx.allowed_tools);
let tdefs_opt: Option<Vec<ToolDef>> = if tdefs.is_empty() { None } else { Some(tdefs) };
// Cache provider config once before the loop instead of re-resolving
// from disk on every step (Settings::load + AppConfig::load each parse
// JSON files, and the config cannot change between steps).
let (api_key, model, base_url) = resolve_provider_config();
let client = crate::service::provider::LlmClient::new(api_key, model, base_url);
for step in 0..ctx.max_steps {
let (api_key, model, base_url) = resolve_provider_config();
let client = crate::service::provider::LlmClient::new(api_key, model, base_url);
// Use the structured tool-calling API so the LLM can request tools with
// proper arguments, exactly like the main agent does.