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

  1. Use a plain identifier (UUID, alphanumeric slug) as session_id
  2. 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)