{"record":{"id":"abd0df50bce8db47","repo":"Hmbown/CodeWhale","slug":"kind-must-be-a-single-path-component-task-manager","errorCode":null,"errorMessage":"{kind} must be a single path component","messagePattern":"(.+?) must be a single path component","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/task_manager.rs","lineNumber":3391,"sourceCode":"            out.push_str(\"...\");\n            return out;\n        }\n        if ch.is_control() && ch != '\\n' && ch != '\\t' {\n            continue;\n        }\n        out.push(ch);\n        count += 1;\n    }\n    out\n}\n\nfn ensure_safe_storage_id(kind: &str, value: &str) -> Result<()> {\n    let mut components = Path::new(value).components();\n    let Some(component) = components.next() else {\n        bail!(\"{kind} must not be empty\");\n    };\n    if components.next().is_some() || !matches!(component, std::path::Component::Normal(_)) {\n        bail!(\"{kind} must be a single path component\");\n    }\n    Ok(())\n}\n\nfn sanitize_filename(input: &str) -> String {\n    let mut out = String::new();\n    for ch in input.chars() {\n        if ch.is_ascii_alphanumeric() || ch == '_' || ch == '-' {\n            out.push(ch);\n        } else {\n            out.push('_');\n        }\n    }\n    if out.is_empty() {\n        \"artifact\".to_string()\n    } else {\n        out\n    }","sourceCodeStart":3373,"sourceCodeEnd":3409,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/task_manager.rs#L3373-L3409","documentation":"ensure_safe_storage_id rejects values that are not exactly one normal path component — containing '/', '\\\\', '..' or similar — to prevent path traversal or nested storage paths. It throws '{kind} must be a single path component' when the value has multiple components or a non-Normal component (RootDir, CurDir, ParentDir, Prefix).","triggerScenarios":"Passing an id containing a path separator or '..' (e.g. \"../escape\", \"a/b\", \"C:\\\\x\") into a storage-backed task-manager call.","commonSituations":"User-supplied ids echoed into storage keys; unsanitized external input; constructing ids by joining strings with '/' by mistake; attempted path-traversal via crafted task ids.","solutions":["Sanitize the id: strip or replace path separators and reject '..' before calling the API (see sanitize_filename in the same file).","Generate ids internally (UUID/ULID) instead of accepting raw external strings.","Keep this rejection — do not work around it; it is a deliberate path-traversal guard."],"exampleFix":"// before\nmanager.store_task(&user_input, task).await?; // user_input = \"../evil\"\n// after\nlet safe = sanitize_filename(&user_input);\nmanager.store_task(&safe, task).await?;","handlingStrategy":"validation","validationCode":"fn is_safe_id(value: &str) -> bool {\n    !value.is_empty()\n        && !value.contains(['/', '\\\\'])\n        && value != \".\" && value != \"..\"\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Sanitize or hash user-supplied ids before using them as storage keys.","Generate ids internally rather than accepting raw external strings.","Never bypass this check — it blocks path traversal."],"tags":["validation","path-traversal","security"],"backgroundTag":"path-traversal-blocked","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}