diff --git a/app/src-tauri/Cargo.lock b/app/src-tauri/Cargo.lock index f30f0b8..610dca3 100644 --- a/app/src-tauri/Cargo.lock +++ b/app/src-tauri/Cargo.lock @@ -4681,6 +4681,7 @@ dependencies = [ "reqwest 0.12.28", "serde", "serde_json", + "sha2", "tar", "tauri", "tauri-build", diff --git a/app/src-tauri/Cargo.toml b/app/src-tauri/Cargo.toml index ad2ba45..2bb596b 100644 --- a/app/src-tauri/Cargo.toml +++ b/app/src-tauri/Cargo.toml @@ -30,6 +30,7 @@ fern = { version = "0.7", features = ["date-based"] } tar = "0.4" reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] } iana-time-zone = "0.1" +sha2 = "0.10" [build-dependencies] tauri-build = { version = "2", features = [] } diff --git a/app/src-tauri/src/docker/container.rs b/app/src-tauri/src/docker/container.rs index 7a2bed7..b44e8ec 100644 --- a/app/src-tauri/src/docker/container.rs +++ b/app/src-tauri/src/docker/container.rs @@ -5,8 +5,7 @@ use bollard::container::{ use bollard::image::{CommitContainerOptions, RemoveImageOptions}; use bollard::models::{ContainerSummary, HostConfig, Mount, MountTypeEnum, PortBinding}; use std::collections::HashMap; -use std::collections::hash_map::DefaultHasher; -use std::hash::{Hash, Hasher}; +use sha2::{Sha256, Digest}; use super::client::get_docker; use crate::models::{AuthMode, BedrockAuthMethod, ContainerInfo, EnvVar, GlobalAwsSettings, McpServer, McpTransportType, PortMapping, Project, ProjectPath}; @@ -129,20 +128,28 @@ fn merge_claude_instructions( } } +/// Hash a string with SHA-256 and return the hex digest. +fn sha256_hex(input: &str) -> String { + let mut hasher = Sha256::new(); + hasher.update(input.as_bytes()); + format!("{:x}", hasher.finalize()) +} + /// Compute a fingerprint for the Bedrock configuration so we can detect changes. fn compute_bedrock_fingerprint(project: &Project) -> String { if let Some(ref bedrock) = project.bedrock_config { - let mut hasher = DefaultHasher::new(); - format!("{:?}", bedrock.auth_method).hash(&mut hasher); - bedrock.aws_region.hash(&mut hasher); - bedrock.aws_access_key_id.hash(&mut hasher); - bedrock.aws_secret_access_key.hash(&mut hasher); - bedrock.aws_session_token.hash(&mut hasher); - bedrock.aws_profile.hash(&mut hasher); - bedrock.aws_bearer_token.hash(&mut hasher); - bedrock.model_id.hash(&mut hasher); - bedrock.disable_prompt_caching.hash(&mut hasher); - format!("{:x}", hasher.finish()) + let parts = vec![ + format!("{:?}", bedrock.auth_method), + bedrock.aws_region.clone(), + bedrock.aws_access_key_id.as_deref().unwrap_or("").to_string(), + bedrock.aws_secret_access_key.as_deref().unwrap_or("").to_string(), + bedrock.aws_session_token.as_deref().unwrap_or("").to_string(), + bedrock.aws_profile.as_deref().unwrap_or("").to_string(), + bedrock.aws_bearer_token.as_deref().unwrap_or("").to_string(), + bedrock.model_id.as_deref().unwrap_or("").to_string(), + format!("{}", bedrock.disable_prompt_caching), + ]; + sha256_hex(&parts.join("|")) } else { String::new() } @@ -157,9 +164,7 @@ fn compute_paths_fingerprint(paths: &[ProjectPath]) -> String { .collect(); parts.sort(); let joined = parts.join(","); - let mut hasher = DefaultHasher::new(); - joined.hash(&mut hasher); - format!("{:x}", hasher.finish()) + sha256_hex(&joined) } /// Compute a fingerprint for port mappings so we can detect changes. @@ -171,9 +176,7 @@ fn compute_ports_fingerprint(port_mappings: &[PortMapping]) -> String { .collect(); parts.sort(); let joined = parts.join(","); - let mut hasher = DefaultHasher::new(); - joined.hash(&mut hasher); - format!("{:x}", hasher.finish()) + sha256_hex(&joined) } /// Build the JSON value for MCP servers config to be injected into ~/.claude.json. @@ -250,9 +253,7 @@ fn compute_mcp_fingerprint(servers: &[McpServer]) -> String { return String::new(); } let json = build_mcp_servers_json(servers); - let mut hasher = DefaultHasher::new(); - json.hash(&mut hasher); - format!("{:x}", hasher.finish()) + sha256_hex(&json) } pub async fn find_existing_container(project: &Project) -> Result, String> {