{"record":{"id":"b27c4ada7c296bc0","repo":"Hmbown/CodeWhale","slug":"invalid-session-id-session-id","errorCode":null,"errorMessage":"Invalid session id '{session_id}'","messagePattern":"Invalid session id '(.+?)'","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/approval_log.rs","lineNumber":174,"sourceCode":"\nimpl ApprovalReceiptStore {\n    pub(crate) fn new(sessions_dir: PathBuf) -> Self {\n        Self { sessions_dir }\n    }\n\n    #[cfg_attr(test, allow(dead_code))]\n    pub(crate) fn default_location() -> io::Result<Self> {\n        crate::session_manager::default_sessions_dir().map(Self::new)\n    }\n\n    fn validated_session_id(session_id: &str) -> io::Result<&str> {\n        let trimmed = session_id.trim();\n        if trimmed.is_empty()\n            || !trimmed\n                .chars()\n                .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')\n        {\n            return Err(io::Error::new(\n                io::ErrorKind::InvalidInput,\n                format!(\"Invalid session id '{session_id}'\"),\n            ));\n        }\n        Ok(trimmed)\n    }\n\n    fn log_path(&self, session_id: &str) -> io::Result<PathBuf> {\n        let session_id = Self::validated_session_id(session_id)?;\n        Ok(self.sessions_dir.join(session_id).join(APPROVAL_LOG_FILE))\n    }\n\n    fn lock_path(&self, session_id: &str) -> io::Result<PathBuf> {\n        let session_id = Self::validated_session_id(session_id)?;\n        Ok(self.sessions_dir.join(session_id).join(APPROVAL_LOCK_FILE))\n    }\n\n    fn open_lock_file(&self, session_id: &str) -> io::Result<File> {","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/approval_log.rs#L156-L192","documentation":"ApprovalLog::validated_session_id guards log_path() before it joins <sessions_dir>/<session_id>/approval_receipts.jsonl. It trims the id, then rejects it if it is empty or contains any character outside ASCII letters, digits, '-', and '_'. This is a path-safety guard: spaces, dots, slashes, or '..' in a session id could escape the per-session directory, so malformed ids fail closed with InvalidInput before any filesystem access.","triggerScenarios":"Calling approval_log.load(session_id) / log_path(session_id) (directly or via replay/resume flows) with values like \"session 2\", \"../other-session\", \"id:1234\", \"séance\", or an empty/whitespace-only string. Leading/trailing whitespace is tolerated because the id is trimmed; any interior invalid character is not.","commonSituations":"Passing a user-typed session name containing spaces instead of the generated session id; passing a filesystem path or URL slug as an id; ids containing ':', '.', '/', or unicode copied from another tool.","solutions":["Use the session id produced by the session manager (generated [A-Za-z0-9_-] form), not a display name","Sanitize before calling: trim, then replace or reject characters outside [A-Za-z0-9_-]","Validate the id at the input boundary (CLI arg, config, recorded session index) and fail with a user-facing message before touching approval logs"],"exampleFix":"// before\nlet receipts = approval_log.load(\"session 42\");? // InvalidInput: Invalid session id 'session 42'\n\n// after\nlet id = \"session 42\".chars().map(|c| if c.is_ascii_alphanumeric() || c == '-' || c == '_' { c } else { '_' }).collect::<String>();\nlet receipts = approval_log.load(&id);","handlingStrategy":"type-guard","validationCode":"let id = session_id.trim();\nassert!(!id.is_empty(), \"session id required\");","typeGuard":"fn is_valid_session_id(session_id: &str) -> bool {\n    !session_id.is_empty()\n        && session_id\n            .chars()\n            .all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')\n}","tryCatchPattern":"match approval_log.load(sid) {\n    Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains(\"session id\") => {\n        // reject/sanitize the id at the caller; do not retry the same value\n    }\n    other => other?,\n}","preventionTips":["Only pass session-manager-generated ids to approval-log APIs","Validate ids at the input boundary with the same [A-Za-z0-9_-] rule","Never build log paths by joining raw user strings"],"tags":["validation","session-id","path-traversal","approval-log"],"backgroundTag":"input-validation-failed","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}