From 4a9daf704d28231c7966612eb591d2cd85d9024e Mon Sep 17 00:00:00 2001 From: Henry Park Date: Fri, 27 Mar 2026 12:14:24 -0700 Subject: [PATCH] Hash routine verification fingerprints --- src/agent/routine.rs | 41 ++++++++++++++++++++++++++++++++++-- src/tools/builtin/routine.rs | 3 +-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/agent/routine.rs b/src/agent/routine.rs index 85a4a0f0..08e23f3d 100644 --- a/src/agent/routine.rs +++ b/src/agent/routine.rs @@ -25,6 +25,7 @@ use std::time::Duration; use chrono::{DateTime, Utc}; use serde::{Deserialize, Serialize}; use serde_json::{Map, Value}; +use sha2::{Digest, Sha256}; use uuid::Uuid; use crate::error::RoutineError; @@ -591,7 +592,7 @@ fn write_routine_verification_record( } pub fn routine_verification_fingerprint(routine: &Routine) -> String { - serde_json::json!({ + let canonical = serde_json::json!({ "trigger_type": routine.trigger.type_tag(), "trigger": routine.trigger.to_config_json(), "action_type": routine.action.type_tag(), @@ -602,7 +603,10 @@ pub fn routine_verification_fingerprint(routine: &Routine) -> String { "dedup_window_secs": routine.guardrails.dedup_window.map(|d| d.as_secs()), }, }) - .to_string() + .to_string(); + let mut hasher = Sha256::new(); + hasher.update(canonical.as_bytes()); + hex::encode(hasher.finalize()) } pub fn reset_routine_verification_state( @@ -1022,6 +1026,39 @@ mod tests { assert_ne!(h1, h3); } + #[test] + fn test_verification_fingerprint_is_digest_not_prompt_content() { + let routine = Routine { + id: Uuid::new_v4(), + name: "hashed".to_string(), + description: "hash test".to_string(), + user_id: "test-user".to_string(), + enabled: true, + trigger: Trigger::Manual, + action: RoutineAction::Lightweight { + prompt: "super-secret-routine-prompt".to_string(), + context_paths: Vec::new(), + max_tokens: 256, + use_tools: false, + max_tool_rounds: 1, + }, + guardrails: RoutineGuardrails::default(), + notify: NotifyConfig::default(), + last_run_at: None, + next_fire_at: None, + run_count: 0, + consecutive_failures: 0, + state: serde_json::json!({}), + created_at: Utc::now(), + updated_at: Utc::now(), + }; + + let fingerprint = routine_verification_fingerprint(&routine); + + assert_eq!(fingerprint.len(), 64); + assert!(!fingerprint.contains("super-secret-routine-prompt")); + } + #[test] fn test_next_cron_fire_valid() { // Every minute should always have a next fire diff --git a/src/tools/builtin/routine.rs b/src/tools/builtin/routine.rs index 71d8a368..fe29f2da 100644 --- a/src/tools/builtin/routine.rs +++ b/src/tools/builtin/routine.rs @@ -434,8 +434,7 @@ fn verification_result_payload(routine: &Routine, verification_reset: bool) -> V "The current routine configuration has already been verified with a successful run." } else { "The routine has been saved, but it has not been verified yet. Offer to test it now." - }, - "verification_fingerprint": routine_verification_fingerprint(routine), + } }) }