{"record":{"id":"77a346cfda1642a4","repo":"BigPizzaV3/CodexPlusPlus","slug":"invalid-dream-skin-destination-name","errorCode":null,"errorMessage":"invalid Dream Skin destination name","messagePattern":"invalid Dream Skin destination name","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/dream_skin.rs","lineNumber":48,"sourceCode":"\npub fn import_dream_skin_image(source: &Path, state_dir: &Path) -> anyhow::Result<PathBuf> {\n    let managed_dir = state_dir.join(MANAGED_THEME_DIR);\n    let destination = prepare_dream_skin_image_for_directory(source, &managed_dir, \"current\")?;\n    remove_other_managed_images(&managed_dir, &destination)?;\n    Ok(destination)\n}\n\npub(crate) fn prepare_dream_skin_image_for_directory(\n    source: &Path,\n    destination_dir: &Path,\n    destination_stem: &str,\n) -> anyhow::Result<PathBuf> {\n    if destination_stem.is_empty()\n        || !destination_stem\n            .bytes()\n            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))\n    {\n        bail!(\"invalid Dream Skin destination name\");\n    }\n    let metadata = std::fs::symlink_metadata(source)\n        .with_context(|| format!(\"failed to read image metadata {}\", source.display()))?;\n    if !metadata.file_type().is_file() || metadata.file_type().is_symlink() {\n        bail!(\"Dream Skin image is not a file\");\n    }\n    if metadata.len() == 0 {\n        bail!(\"Dream Skin image is empty\");\n    }\n    if metadata.len() > DREAM_SKIN_SOURCE_LIMIT {\n        bail!(\"Dream Skin source image exceeds 50 MiB\");\n    }\n\n    let extension = supported_image_extension(source)?;\n    std::fs::create_dir_all(destination_dir).with_context(|| {\n        format!(\n            \"failed to create Dream Skin theme directory {}\",\n            destination_dir.display()","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/f2074595a281bc057525c748175c8eb9805b0673/crates/codex-plus-core/src/dream_skin.rs#L30-L66","documentation":"prepare_dream_skin_image_for_directory validates destination_stem before touching the filesystem: it must be non-empty and contain only ASCII alphanumerics, '-' and '_'. Spaces, CJK characters, dots, slashes, or an empty stem are rejected. The rule keeps managed-theme filenames predictable and blocks path traversal via the destination name.","triggerScenarios":"Calling import_dream_skin_image with a stem taken verbatim from a user filename such as 'my theme', '壁纸.png', 'a.b', '../evil', or an empty string after the extension was stripped twice.","commonSituations":"A UI passing the original filename minus extension without sanitizing; localized (CJK) filenames on user machines; code that strips the extension twice and passes an empty stem.","solutions":["Sanitize the stem to [A-Za-z0-9_-] before calling import_dream_skin_image","Fall back to a fixed stem such as 'imported' when sanitization empties the string","Strip the extension first, then replace every disallowed byte with '-'"],"exampleFix":"// before\nlet stem = file_stem_of(user_selection);\nprepare_dream_skin_image_for_directory(dir, &stem, source)?;\n\n// after\nlet stem: String = file_stem_of(user_selection)\n    .bytes()\n    .map(|b| if b.is_ascii_alphanumeric() { b } else { b'-' })\n    .collect();\nlet stem = if stem.is_empty() { \"imported\".to_string() } else { stem };\nprepare_dream_skin_image_for_directory(dir, &stem, source)?;","handlingStrategy":"validation","validationCode":"fn is_valid_dream_skin_stem(stem: &str) -> bool {\n    !stem.is_empty()\n        && stem.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')\n}","typeGuard":"fn is_valid_dream_skin_stem(stem: &str) -> bool {\n    !stem.is_empty()\n        && stem.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')\n}","tryCatchPattern":null,"preventionTips":["Never feed a raw user filename into the stem - normalize first","Rewrite or reject path separators before deriving a destination name","Test the sanitize path with CJK and spaced filenames"],"tags":["dream-skin","filename-validation","path-traversal","rust"],"backgroundTag":"invalid-filename","analyzedSha":"f2074595a281bc057525c748175c8eb9805b0673","analyzedAt":"2026-08-23T12:52:24.489Z","contentChangedAt":"2026-08-23T12:52:24.489Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}