Kuberwastaken/claurst · error
session_id contains illegal characters
Error message
session_id contains illegal characters
What it means
Security validation guard (issue #204): the session_id passed to todos_path contains '/', '\\', or '..' and is rejected to prevent directory traversal — a crafted session_id could otherwise write the todo file outside the session storage directory.
Solutions
- Use a plain identifier (UUID, alphanumeric slug) as session_id
- Sanitize session ids at their source before persisting todos
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at src-rust/crates/tools/src/todo_write.rs:25 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of Kuberwastaken/claurst@b0637c97ec (2026-09-10).
Data as JSON: /api/errors/fdc1657c0d6b19ff.
Report an issue: GitHub.
Appendix: source
Thrown at src-rust/crates/tools/src/todo_write.rs:25
use std::path::{Path, PathBuf};
use tracing::debug;
// ---------------------------------------------------------------------------
// Session-aware persistence helpers
// ---------------------------------------------------------------------------
/// Validate that `session_id` is a plain filename — no path separators or
/// `..` components that could be used for directory traversal (issue #204).
fn validate_session_id(session_id: &str) -> Result<(), String> {
if session_id.contains('/') || session_id.contains('\\') || session_id.contains("..") {
return Err("session_id contains illegal characters".into());
}
Ok(())
}
/// Returns the path to the persisted todo list for `session_id`.
pub fn todos_path(session_id: &str) -> anyhow::Result<PathBuf> {
validate_session_id(session_id).map_err(|e| anyhow::anyhow!(e))?;
Ok(todos_dir().join(format!("{}.json", session_id)))
}
/// Directory holding persisted todo lists (`<claurst home>/todos`).
fn todos_dir() -> PathBuf {
claurst_core::config::Settings::config_dir().join("todos")
}
/// Load the persisted todo list for `session_id`. Returns an empty vec if the
/// file does not exist, cannot be parsed, or if `session_id` contains illegal
/// path characters (issue #204).
pub fn load_todos(session_id: &str) -> Vec<Value> {
load_todos_in(&todos_dir(), session_id)
}
/// Like [`load_todos`] but reads from an explicit todos directory. Lets tests
/// run hermetically without depending on a writable HOME.
///
View on GitHub (pinned to b0637c97ec)