BigPizzaV3/CodexPlusPlus · error · anyhow::Error

invalid Dream Skin destination name

Error message

invalid Dream Skin destination name

What it means

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.

Source

Thrown at crates/codex-plus-core/src/dream_skin.rs:48

pub fn import_dream_skin_image(source: &Path, state_dir: &Path) -> anyhow::Result<PathBuf> {
    let managed_dir = state_dir.join(MANAGED_THEME_DIR);
    let destination = prepare_dream_skin_image_for_directory(source, &managed_dir, "current")?;
    remove_other_managed_images(&managed_dir, &destination)?;
    Ok(destination)
}

pub(crate) fn prepare_dream_skin_image_for_directory(
    source: &Path,
    destination_dir: &Path,
    destination_stem: &str,
) -> anyhow::Result<PathBuf> {
    if destination_stem.is_empty()
        || !destination_stem
            .bytes()
            .all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'-' | b'_'))
    {
        bail!("invalid Dream Skin destination name");
    }
    let metadata = std::fs::symlink_metadata(source)
        .with_context(|| format!("failed to read image metadata {}", source.display()))?;
    if !metadata.file_type().is_file() || metadata.file_type().is_symlink() {
        bail!("Dream Skin image is not a file");
    }
    if metadata.len() == 0 {
        bail!("Dream Skin image is empty");
    }
    if metadata.len() > DREAM_SKIN_SOURCE_LIMIT {
        bail!("Dream Skin source image exceeds 50 MiB");
    }

    let extension = supported_image_extension(source)?;
    std::fs::create_dir_all(destination_dir).with_context(|| {
        format!(
            "failed to create Dream Skin theme directory {}",
            destination_dir.display()

View on GitHub (pinned to f2074595a2)

Solutions

  1. Sanitize the stem to [A-Za-z0-9_-] before calling import_dream_skin_image
  2. Fall back to a fixed stem such as 'imported' when sanitization empties the string
  3. Strip the extension first, then replace every disallowed byte with '-'

Example fix

// before
let stem = file_stem_of(user_selection);
prepare_dream_skin_image_for_directory(dir, &stem, source)?;

// after
let stem: String = file_stem_of(user_selection)
    .bytes()
    .map(|b| if b.is_ascii_alphanumeric() { b } else { b'-' })
    .collect();
let stem = if stem.is_empty() { "imported".to_string() } else { stem };
prepare_dream_skin_image_for_directory(dir, &stem, source)?;
Defensive patterns

Strategy: validation

Validate before calling

fn is_valid_dream_skin_stem(stem: &str) -> bool {
    !stem.is_empty()
        && stem.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
}

Type guard

fn is_valid_dream_skin_stem(stem: &str) -> bool {
    !stem.is_empty()
        && stem.bytes().all(|b| b.is_ascii_alphanumeric() || b == b'-' || b == b'_')
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@f2074595a2 (2026-08-23). Data as JSON: /api/errors/77a346cfda1642a4. Report an issue: GitHub.