{"record":{"id":"18f20e522068da8b","repo":"sigoden/aichat","slug":"invalid-document-path-path","errorCode":null,"errorMessage":"Invalid document path: '{path}'","messagePattern":"Invalid document path: '(.+?)'","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"src/config/agent.rs","lineNumber":93,"sourceCode":"        };\n\n        let rag = if rag_path.exists() {\n            Some(Arc::new(Rag::load(config, DEFAULT_AGENT_NAME, &rag_path)?))\n        } else if !definition.documents.is_empty() && !config.read().info_flag {\n            let mut ans = false;\n            if *IS_STDOUT_TERMINAL {\n                ans = Confirm::new(\"The agent has the documents, init RAG?\")\n                    .with_default(true)\n                    .prompt()?;\n            }\n            if ans {\n                let mut document_paths = vec![];\n                for path in &definition.documents {\n                    if is_url(path) {\n                        document_paths.push(path.to_string());\n                    } else {\n                        let new_path = safe_join_path(&functions_dir, path)\n                            .ok_or_else(|| anyhow!(\"Invalid document path: '{path}'\"))?;\n                        document_paths.push(new_path.display().to_string())\n                    }\n                }\n                let rag =\n                    Rag::init(config, \"rag\", &rag_path, &document_paths, abort_signal).await?;\n                Some(Arc::new(rag))\n            } else {\n                None\n            }\n        } else {\n            None\n        };\n\n        Ok(Self {\n            name: name.to_string(),\n            config: agent_config,\n            definition,\n            shared_variables: Default::default(),","sourceCodeStart":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/sigoden/aichat/blob/82976d349ad97ac9aae0655ad631dace5e2a6385/src/config/agent.rs#L75-L111","documentation":"In src/config/agent.rs `init`, each document path from an agent definition is either a URL (kept as-is) or joined with the agent's `functions_dir` via `safe_join_path`. When the join fails — the path is unsafe (e.g. absolute or escaping the functions dir) — the library throws `Invalid document path: '{path}'` rather than resolving a path outside the allowed directory.","triggerScenarios":"An agent definition lists a `documents` entry that is not a URL and cannot be safely joined with functions_dir: absolute paths like `/etc/passwd`, `../` traversal outside the functions dir, or empty/odd path segments.","commonSituations":"Copying an agent config from another machine with absolute document paths; accidentally writing `../..` in a document entry; symlinks or path normalization surprises.","solutions":["Use a path relative to the agent's functions directory (e.g. `docs/manual.md`).","If the document is remote, pass a proper URL (`https://...`) so the is_url branch accepts it.","Remove any absolute path or `..` traversal from the documents list.","Move the document file into the functions directory and reference it by relative name."],"exampleFix":"// before (config.yaml)\ndocuments:\n  - /home/me/notes/spec.md\n\n// after\ndocuments:\n  - spec.md   # file placed inside the agent's functions dir","handlingStrategy":"validation","validationCode":"// Rust\nfn valid_doc_path(p: &str, functions_dir: &Path) -> bool {\n    p.starts_with(\"http://\") || p.starts_with(\"https://\")\n        || safe_join_path(functions_dir, std::path::Path::new(p)).is_some()\n}","typeGuard":"fn is_url(s: &str) -> bool { s.starts_with(\"http://\") || s.starts_with(\"https://\") }","tryCatchPattern":null,"preventionTips":["Keep document paths relative to the functions directory.","Never use absolute paths or `..` in agent document lists.","Use URLs for remote documents.","Validate agent definitions after copying them between machines."],"tags":["path","security","config","agent"],"backgroundTag":"path-traversal-blocked","analyzedSha":"82976d349ad97ac9aae0655ad631dace5e2a6385","analyzedAt":"2026-09-09T18:33:06.139Z","contentChangedAt":"2026-09-09T18:33:06.139Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}