Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ thiserror = "1.0.63"
uuid = { version = "1.11.0", features = ["v4"] }
chrono = { version = "0.4.38", features = ["clock"] }
base64 = "0.22"
sha2 = "0.10"
tokio = { version = "1", features = ["sync", "process"] }
shellexpand = "3.1"
tauri-plugin-updater = "2"
Expand Down
73 changes: 66 additions & 7 deletions src-tauri/src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5417,12 +5417,57 @@ pub async fn remote_refresh_model_catalog(
Ok(collect_model_catalog(&cfg))
}

/// Known SHA256 checksum of the install script for integrity verification.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BS: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 is SHA256("") — the hash of an empty string. Every real install.sh will fail this check, permanently breaking the upgrade command. This needs to be a real hash (or the pinned-hash approach needs to be reconsidered entirely).

/// Update this when the official install script changes.
const INSTALL_SCRIPT_SHA256: &str = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";

#[tauri::command]
pub async fn run_openclaw_upgrade() -> Result<String, String> {
use sha2::{Sha256, Digest};

// Step 1: Download the script to a temp file
let temp_dir = std::env::temp_dir();
let script_path = temp_dir.join("openclaw_install.sh");

let download = Command::new("curl")
.args(["-fsSL", "-o", script_path.to_str().unwrap(), "https://openclaw.ai/install.sh"])
.output()
.map_err(|e| format!("Failed to download install script: {e}"))?;

if !download.status.success() {
return Err(format!(
"Failed to download install script: {}",
String::from_utf8_lossy(&download.stderr)
));
}

// Step 2: Verify checksum
let script_content = std::fs::read(&script_path)
.map_err(|e| format!("Failed to read downloaded script: {e}"))?;

let mut hasher = Sha256::new();
hasher.update(&script_content);
let actual_hash = format!("{:x}", hasher.finalize());

if actual_hash != INSTALL_SCRIPT_SHA256 {
let _ = std::fs::remove_file(&script_path);
return Err(format!(
"Install script checksum mismatch. Expected: {}, Got: {}. \
The script may have been tampered with or updated. \
Please report this issue if the official script was updated.",
INSTALL_SCRIPT_SHA256, actual_hash
));
}

// Step 3: Execute the verified script
let output = Command::new("bash")
.args(["-c", "curl -fsSL https://openclaw.ai/install.sh | bash"])
.arg(&script_path)
.output()
.map_err(|e| format!("Failed to run upgrade: {e}"))?;

// Cleanup
let _ = std::fs::remove_file(&script_path);

let stdout = String::from_utf8_lossy(&output.stdout).to_string();
let stderr = String::from_utf8_lossy(&output.stderr).to_string();
let combined = if stderr.is_empty() {
Expand All @@ -5442,12 +5487,26 @@ pub async fn remote_run_openclaw_upgrade(
pool: State<'_, SshConnectionPool>,
host_id: String,
) -> Result<String, String> {
let result = pool
.exec_login(
&host_id,
"curl -fsSL https://openclaw.ai/install.sh | bash",
)
.await?;
// Download, verify checksum, then execute - all in one remote command
// This prevents MITM attacks on the install script
let install_cmd = format!(
concat!(
"set -e; ",
"SCRIPT=$(mktemp); ",
"curl -fsSL -o \"$SCRIPT\" https://openclaw.ai/install.sh; ",
"HASH=$(sha256sum \"$SCRIPT\" 2>/dev/null || shasum -a 256 \"$SCRIPT\" | cut -d' ' -f1); ",
"if [ \"$HASH\" != \"{expected_hash}\" ]; then ",
" rm -f \"$SCRIPT\"; ",
" echo 'ERROR: Install script checksum mismatch. Expected: {expected_hash}, Got: '\"$HASH\" >&2; ",
" exit 1; ",
"fi; ",
"bash \"$SCRIPT\"; ",
"rm -f \"$SCRIPT\""
),
expected_hash = INSTALL_SCRIPT_SHA256
);

let result = pool.exec_login(&host_id, &install_cmd).await?;
let combined = if result.stderr.is_empty() {
result.stdout.clone()
} else {
Expand Down
1 change: 1 addition & 0 deletions src-tauri/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub mod history;
pub mod logging;
pub mod models;
pub mod recipe;
pub mod path_fix;
pub mod ssh;

pub fn run() {
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

fn main() {
let _ = fix_path_env::fix();
clawpal::path_fix::ensure_tool_paths();
clawpal::run();
}
Loading