Hmbown/CodeWhale · error

state subdir must not be an absolute path

Error message

state subdir must not be an absolute path: {subdir}

What it means

State subdirectories must be relative to the state root; an absolute subdir (e.g. `/tmp/x`, `C:\x`) would escape the state root entirely. `ensure_safe_state_subdir` rejects any subdir string that `Path::is_absolute()` accepts.

Solutions

  1. Pass only the relative component (e.g. `"sessions"`), not a full path
  2. Convert the absolute path to a path relative to the state root before passing it
  3. Use the API that accepts an explicit state-root override instead of smuggling it via subdir

Example fix

// before
let dir = state_dir_in("/home/u/.codewhale/sessions")?;
// after
let dir = state_dir_in("sessions")?;
Defensive patterns

Strategy: validation

Validate before calling

use std::path::Path;
fn is_relative_subdir(subdir: &str) -> bool {
    !subdir.is_empty() && !Path::new(subdir).is_absolute()
}

Type guard

fn safe_subdir(subdir: &str) -> Option<&str> {
    (!subdir.is_empty() && !Path::new(subdir).is_absolute()).then_some(subdir)
}

Try / catch

match state_dir_in(subdir) {
    Err(e) if e.to_string().contains("absolute path") => {
        // recompute as path relative to state root and retry
    }
    result => result,
}

Prevention

When it happens

Trigger: Calling a state-path helper with `subdir` starting with `/` (Unix) or containing a Windows drive/UNC prefix.

Common situations: Users setting a state-dir-like env var to an absolute path and passing it as the subdir; joining a configured absolute path into the subdir slot; copying a full path from another tool.

Understand the failure class

Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/5c6368dde7261282. Report an issue: GitHub.

Appendix: source

Thrown at crates/config/src/lib.rs:5727

/// Always returns the legacy path regardless of whether it exists.
pub fn legacy_deepseek_home() -> Result<PathBuf> {
    codewhale_paths::legacy_deepseek_home().context("failed to resolve home directory")
}

/// Reject state subdirs that could escape the state root via path injection.
///
/// `ensure_state_dir` / `resolve_state_dir` are public APIs taking an arbitrary
/// subdir string; every in-tree caller passes a hardcoded single component
/// (e.g. `"sessions"`, `"."`). This validates defensively so a future caller
/// can never traverse out of the state root via `..` components or an absolute
/// path. Nested relative paths such as `"a/b"` are permitted.
fn ensure_safe_state_subdir(subdir: &str) -> Result<()> {
    if subdir.is_empty() {
        bail!("state subdir must not be empty");
    }
    let path = std::path::Path::new(subdir);
    if path.is_absolute() {
        bail!("state subdir must not be an absolute path: {subdir}");
    }
    if path.components().any(|c| {
        matches!(
            c,
            std::path::Component::RootDir | std::path::Component::Prefix(_)
        )
    }) {
        bail!("state subdir must not contain a root or prefix: {subdir}");
    }
    if path
        .components()
        .any(|c| matches!(c, std::path::Component::ParentDir))
    {
        bail!("state subdir must not contain parent-dir (..) components: {subdir}");
    }
    Ok(())
}

View on GitHub (pinned to 73e0f67d83)