fix(security): add path canonicalization and actionable permission error in install_ollama_from_bundle

This commit is contained in:
Shaun Arman 2026-04-05 19:34:47 -05:00
parent fc50fe3102
commit dffd26a6fd

View File

@ -145,6 +145,8 @@ pub async fn get_audit_log(
// Security note: the bundled binary's integrity is guaranteed by the CI release pipeline // Security note: the bundled binary's integrity is guaranteed by the CI release pipeline
// which verifies SHA256 checksums against Ollama's published sha256sums.txt before bundling. // which verifies SHA256 checksums against Ollama's published sha256sums.txt before bundling.
// Runtime re-verification is not performed here; the app bundle itself is the trust boundary. // Runtime re-verification is not performed here; the app bundle itself is the trust boundary.
// On Unix, writing to /usr/local/bin requires elevated privileges. If the operation fails with
// PermissionDenied the caller receives an actionable error message.
#[tauri::command] #[tauri::command]
pub async fn install_ollama_from_bundle( pub async fn install_ollama_from_bundle(
app: tauri::AppHandle, app: tauri::AppHandle,
@ -153,10 +155,12 @@ pub async fn install_ollama_from_bundle(
use std::path::PathBuf; use std::path::PathBuf;
use tauri::Manager; use tauri::Manager;
let resource_path = app let resource_dir = app
.path() .path()
.resource_dir() .resource_dir()
.map_err(|e: tauri::Error| e.to_string())? .map_err(|e: tauri::Error| e.to_string())?;
let resource_path = resource_dir
.join("ollama") .join("ollama")
.join(if cfg!(windows) { "ollama.exe" } else { "ollama" }); .join(if cfg!(windows) { "ollama.exe" } else { "ollama" });
@ -164,6 +168,13 @@ pub async fn install_ollama_from_bundle(
return Err("Bundled Ollama not found in resources".to_string()); return Err("Bundled Ollama not found in resources".to_string());
} }
// Defense-in-depth: verify resolved path stays within the resource directory.
let canonical_resource = resource_path.canonicalize().map_err(|e| e.to_string())?;
let canonical_dir = resource_dir.canonicalize().map_err(|e| e.to_string())?;
if !canonical_resource.starts_with(&canonical_dir) {
return Err("Resource path validation failed".to_string());
}
#[cfg(unix)] #[cfg(unix)]
let install_path = PathBuf::from("/usr/local/bin/ollama"); let install_path = PathBuf::from("/usr/local/bin/ollama");
#[cfg(windows)] #[cfg(windows)]
@ -179,7 +190,18 @@ pub async fn install_ollama_from_bundle(
fs::create_dir_all(parent).map_err(|e| e.to_string())?; fs::create_dir_all(parent).map_err(|e| e.to_string())?;
} }
fs::copy(&resource_path, &install_path).map_err(|e| e.to_string())?; fs::copy(&resource_path, &install_path).map_err(|e| {
if e.kind() == std::io::ErrorKind::PermissionDenied {
format!(
"Permission denied writing to {}. On Linux, re-run the app with elevated \
privileges or install manually: sudo cp \"{}\" /usr/local/bin/ollama",
install_path.display(),
resource_path.display()
)
} else {
e.to_string()
}
})?;
#[cfg(unix)] #[cfg(unix)]
{ {