{"record":{"id":"2e474a6f66dc3ba7","repo":"Hmbown/CodeWhale","slug":"external-credential-path-was-redirected-while-opening","errorCode":null,"errorMessage":"external credential path was redirected while opening","messagePattern":"external credential path was redirected while opening","errorType":"validation","errorClass":"io::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/external_credentials.rs","lineNumber":315,"sourceCode":"    // the required UTF-16 buffer length.\n    let needed = unsafe { GetFinalPathNameByHandleW(handle, std::ptr::null_mut(), 0, flags) };\n    if needed == 0 {\n        return Err(io::Error::last_os_error());\n    }\n    let mut buffer = vec![0u16; needed as usize + 1];\n    // SAFETY: `buffer` is writable for its declared length and `handle` is\n    // valid for the duration of the call.\n    let written = unsafe {\n        GetFinalPathNameByHandleW(handle, buffer.as_mut_ptr(), buffer.len() as u32, flags)\n    };\n    if written == 0 || written as usize >= buffer.len() {\n        return Err(io::Error::last_os_error());\n    }\n    let final_path = OsString::from_wide(&buffer[..written as usize]);\n    let actual = normalize_windows_path_for_comparison(Path::new(&final_path))?;\n    let expected = normalize_windows_path_for_comparison(path)?;\n    if actual != expected {\n        return Err(io::Error::new(\n            io::ErrorKind::PermissionDenied,\n            \"external credential path was redirected while opening\",\n        ));\n    }\n    if require_owner_only {\n        use windows_sys::Win32::Storage::FileSystem::{\n            BY_HANDLE_FILE_INFORMATION, GetFileInformationByHandle,\n        };\n        let mut information = BY_HANDLE_FILE_INFORMATION::default();\n        // SAFETY: the opened credential handle and output pointer remain valid\n        // for the duration of the call.\n        if unsafe { GetFileInformationByHandle(handle, &mut information) } == 0 {\n            return Err(io::Error::last_os_error());\n        }\n        if information.nNumberOfLinks != 1 {\n            return Err(io::Error::new(\n                io::ErrorKind::PermissionDenied,\n                \"Codewhale-owned credential file must be singly linked\",","sourceCodeStart":297,"sourceCodeEnd":333,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/external_credentials.rs#L297-L333","documentation":"After opening, the library resolves the final path of the opened handle (GetFinalPathNameByHandle-style) and compares it with the requested path; a mismatch means the filesystem redirected the open (component replaced between validation and open, or a mapped drive/device path divergence). It throws PermissionDenied to guarantee the file read is exactly the one validated.","triggerScenarios":"A path component is swapped or a reparse point is introduced between the pre-open checks and the final open (TOCTOU race); a mapped network drive or SUBST drive resolves to a different canonical path than requested; Windows short (8.3) names or different device prefixes cause the normalized comparison to diverge unexpectedly.","commonSituations":"Another process (sync tool, antivirus quarantine/restore, installer) rewrote the credential path mid-open; the credential lives on a mapped drive (Z:\\) whose canonical \\\\server\\share form differs; SUBST aliases are in play.","solutions":["Re-run the read; if it is a race, eliminate the process modifying the path (pause sync tooling) and keep the credential file stable.","Use the physical path: replace mapped drive letters with the UNC physical path or a local path under the user profile.","Avoid SUBST/mapped-drive aliases for credential storage; store on a fixed local path.","Check for 8.3 short names (PROGRA~1) in the configured path and use the long form."],"exampleFix":"// before\nlet creds = read_codewhale_owned_to_string(Path::new(\"Z:\\\\secrets\\\\token.json\"))?; // mapped drive\n// after\nlet creds = read_codewhale_owned_to_string(Path::new(\"C:\\\\Users\\\\me\\\\.codewhale\\\\token.json\"))?;","handlingStrategy":"retry","validationCode":"// Ensure the configured path is a local physical path, not a mapped/SUBST drive:\n// net use   (look for the drive letter) and subst   (list aliases) before configuring.","typeGuard":null,"tryCatchPattern":"match read_codewhale_owned_to_string(&path) {\n    Ok(creds) => use(creds),\n    Err(e) if e.to_string().contains(\"redirected while opening\") => {\n        // transient TOCTOU: stop whatever rewrites the path, then retry once\n        eprintln!(\"credential path changed during open; retrying once\");\n        read_codewhale_owned_to_string(&path).map(use_rest)\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["Use physical local paths (C:\\...) or UNC paths, not mapped drives or SUBST aliases.","Keep the credential path free of tools that rewrite/replace it (sync clients, AV quarantine).","Avoid 8.3 short names in configured paths; use the full long form.","Retry once on this error; persistent failures mean something is actively modifying the path."],"tags":["windows","security","race-condition","filesystem","credentials"],"backgroundTag":"permission-denied","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}