Skip to content
Merged
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
4 changes: 2 additions & 2 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ winapi = { version = "0.3.9", default-features = false, features = ["wincred", "
wiremock = { version = "0.6", default-features = false }
zip = { version = "8", default-features = false, features = ["deflate"] }
serial_test = { version = "3", default-features = false }
sha2 = { version = "0.10.8", default-features = false, features = ["std"] }
sha2 = { version = "0.11", default-features = false }
shell-words = { version = "1", default-features = false, features = ["std"] }
test-case = { version = "3", default-features = false }
url = { version = "2.5.4", default-features = false, features = ["std"] }
Expand Down
2 changes: 1 addition & 1 deletion crates/goose-cli/src/commands/update.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ fn binary_name() -> &'static str {
fn sha256_hex(data: &[u8]) -> String {
let mut hasher = Sha256::new();
hasher.update(data);
format!("{:x}", hasher.finalize())
goose::utils::bytes_to_hex(hasher.finalize())
}

#[derive(serde::Deserialize)]
Expand Down
5 changes: 3 additions & 2 deletions crates/goose/src/goose_apps/cache.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::config::paths::Paths;
use crate::utils::bytes_to_hex;
use sha2::{Digest, Sha256};
use std::fs;
use std::path::PathBuf;
Expand Down Expand Up @@ -36,8 +37,8 @@ impl McpAppCache {

fn cache_key(extension_name: &str, resource_uri: &str) -> String {
let input = format!("{}::{}", extension_name, resource_uri);
let hash = Sha256::digest(input.as_bytes());
format!("{}_{:x}", extension_name, hash)
let hash = bytes_to_hex(Sha256::digest(input.as_bytes()));
format!("{}_{}", extension_name, hash)
}

pub fn list_apps(&self) -> Result<Vec<GooseApp>, std::io::Error> {
Expand Down
3 changes: 2 additions & 1 deletion crates/goose/src/providers/inventory/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::catalog::ProviderSetupCategory;
use crate::config::declarative_providers::{DeclarativeProviderConfig, ProviderEngine};
use crate::config::Config;
use crate::session::session_manager::SessionStorage;
use crate::utils::bytes_to_hex;
use anyhow::{Context, Result};
use chrono::{DateTime, Duration, Utc};
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -126,7 +127,7 @@ impl InventoryIdentityInput {
Ok(InventoryIdentity {
provider_id,
provider_family,
inventory_key: format!("{digest:x}"),
inventory_key: bytes_to_hex(digest),
})
}
}
Expand Down
3 changes: 2 additions & 1 deletion crates/goose/src/providers/oauth.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::config::paths::Paths;
use crate::utils::bytes_to_hex;
use anyhow::Result;
use axum::{extract::Query, response::Html, routing::get, Router};
use base64::Engine;
Expand Down Expand Up @@ -56,7 +57,7 @@ impl TokenCache {
hasher.update(host.as_bytes());
hasher.update(client_id.as_bytes());
hasher.update(scopes.join(",").as_bytes());
let hash = format!("{:x}", hasher.finalize());
let hash = bytes_to_hex(hasher.finalize());

fs::create_dir_all(get_base_path()).unwrap();
let cache_path = get_base_path().join(format!("{}.json", hash));
Expand Down
3 changes: 2 additions & 1 deletion crates/goose/src/providers/testprovider.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use super::base::{MessageStream, Provider, ProviderDef, ProviderMetadata, Provid
use super::errors::ProviderError;
use crate::conversation::message::{Message, ToolResponse};
use crate::model::ModelConfig;
use crate::utils::bytes_to_hex;
use futures::future::BoxFuture;
use rmcp::model::{CallToolResult, Tool};

Expand Down Expand Up @@ -111,7 +112,7 @@ impl TestProvider {
let serialized = serde_json::to_string(&stable_messages).unwrap_or_default();
let mut hasher = Sha256::new();
hasher.update(serialized.as_bytes());
format!("{:x}", hasher.finalize())
bytes_to_hex(hasher.finalize())
}

fn load_records(file_path: &str) -> Result<HashMap<String, TestRecord>> {
Expand Down
24 changes: 24 additions & 0 deletions crates/goose/src/utils.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
use tokio_util::sync::CancellationToken;
use unicode_normalization::UnicodeNormalization;

/// Encode bytes as a lowercase hexadecimal string.
///
/// This avoids relying on digest output types implementing `LowerHex`, which
/// changed in sha2 0.11.
pub fn bytes_to_hex(bytes: impl AsRef<[u8]>) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let bytes = bytes.as_ref();
let mut output = String::with_capacity(bytes.len() * 2);

for &byte in bytes {
output.push(HEX[(byte >> 4) as usize] as char);
output.push(HEX[(byte & 0x0f) as usize] as char);
}

output
}

/// Check if a character is in the Unicode Tags Block range (U+E0000-U+E007F)
/// These characters are invisible and can be used for steganographic attacks
fn is_in_unicode_tag_range(c: char) -> bool {
Expand Down Expand Up @@ -83,6 +100,13 @@ pub fn split_command_args(input: &str) -> anyhow::Result<Vec<String>> {
mod tests {
use super::*;

#[test]
fn test_bytes_to_hex() {
assert_eq!(bytes_to_hex([]), "");
assert_eq!(bytes_to_hex([0x00, 0x0f, 0x10, 0xab, 0xff]), "000f10abff");
assert_eq!(bytes_to_hex(b"hello world"), "68656c6c6f20776f726c64");
}

#[test]
fn test_contains_unicode_tags() {
// Test detection of Unicode Tags Block characters
Expand Down
Loading