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
- 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 '-'
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
- 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
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
- Dream Skin image is not a file
- Dream Skin image is empty
- Dream Skin source image exceeds 50 MiB
- prepared Dream Skin image is empty
- prepared Dream Skin image exceeds 16 MiB
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@f2074595a2 (2026-08-23).
Data as JSON: /api/errors/77a346cfda1642a4.
Report an issue: GitHub.