{"record":{"id":"c5b8ff6049ef40bf","repo":"Hmbown/CodeWhale","slug":"external-credential-path-must-resolve-to-an-absolu","errorCode":null,"errorMessage":"external credential path must resolve to an absolute path: {}","messagePattern":"external credential path must resolve to an absolute path: (.+?)","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/config/src/external_credentials.rs","lineNumber":139,"sourceCode":"    let mut normalized = PathBuf::new();\n    for component in absolute.components() {\n        match component {\n            Component::Prefix(prefix) => normalized.push(prefix.as_os_str()),\n            Component::RootDir => normalized.push(component.as_os_str()),\n            Component::CurDir => {}\n            Component::ParentDir => {\n                if !normalized.pop() {\n                    bail!(\n                        \"external credential path escapes its absolute root: {}\",\n                        quote_os_path(&absolute)\n                    );\n                }\n            }\n            Component::Normal(part) => normalized.push(part),\n        }\n    }\n    if !normalized.is_absolute() {\n        bail!(\n            \"external credential path must resolve to an absolute path: {}\",\n            quote_os_path(&normalized)\n        );\n    }\n    Ok(normalized)\n}\n\n/// The side-effect envelope Codewhale may use for an external credential.\n#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]\n#[serde(rename_all = \"snake_case\")]\npub enum ExternalCredentialAccess {\n    /// Do not inspect or access the external credential store.\n    #[default]\n    Disabled,\n    /// Read the exact selected file without refreshing or rewriting it.\n    ReadOnly,\n    /// Permit a documented preservation adapter to refresh and rewrite it.\n    Managed,","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/config/src/external_credentials.rs#L121-L157","documentation":"After component-wise normalization of a credential path, the result must still be absolute. The check catches prefix forms that are not true absolute paths (e.g. Windows drive-relative 'C:creds' — a prefix with no RootDir) or other malformed inputs whose normalized form loses its root, so the granted capability cannot be a well-defined absolute file.","triggerScenarios":"resolve_external_credential_path() is called with a path whose components rebuild into a relative result — classically a Windows drive-relative path like 'C:keys\\acme.env' (volume prefix present but no root), or unusual prefix combinations that strip the root during normalization.","commonSituations":"Windows paths pasted without the backslash after the colon; tools that emit drive-relative paths based on the process's per-drive current directory; scripted consent creation joining prefix + relative remainder incorrectly.","solutions":["Write the path fully absolute, including the root: 'C:\\\\keys\\\\acme.env' rather than 'C:keys\\\\acme.env'","On Windows, prefer paths produced by canonical absolute joiners (or copy the path from Explorer) when creating the consent"],"exampleFix":"# before\nC:keys/acme.env   # drive-relative, normalizes non-absolute\n\n# after\nC:\\keys\\acme.env   # prefix + root + components","handlingStrategy":"validation","validationCode":"// Require a truly absolute path up front (catches Windows drive-relative forms):\nfn is_true_absolute(p: &std::path::Path) -> bool {\n    p.is_absolute() // on Windows this requires prefix AND root, e.g. C:\\\\...\n}\nassert!(is_true_absolute(&consent.path));","typeGuard":"fn is_absolute_normalized(p: &Path) -> bool {\n    p.is_absolute() && p.components().all(|c| !matches!(c, Component::CurDir | Component::ParentDir))\n}","tryCatchPattern":"match resolve_external_credential_path(&p) {\n    Ok(n) => n,\n    Err(e) if e.to_string().contains(\"must resolve to an absolute path\") => {\n        reject_consent_input(\"rewrite as C:\\\\dir\\\\file or /dir/file\")\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["On Windows, write the backslash after the drive colon (C:\\\\keys, not C:keys)","Expand '~' to $HOME before storing consent paths","Copy paths from a canonical source (Explorer properties, pwd) rather than typing them"],"tags":["security","path-validation","windows","credentials"],"backgroundTag":"relative-path-rejected","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}