From 5f1957c27bd23f73b339265d3acd32f4a21878a3 Mon Sep 17 00:00:00 2001 From: Ogulcan Celik Date: Sat, 25 Jul 2026 01:04:58 +0300 Subject: fix: harden hermes lifecycle detection --- src/detect/manifests/hermes.toml | 94 +++++++++++++++++++++++++++++++++++----- src/detect/mod.rs | 12 ++++- 2 files changed, 94 insertions(+), 12 deletions(-) (limited to 'src/detect') diff --git a/src/detect/manifests/hermes.toml b/src/detect/manifests/hermes.toml index d45fe32a..17542184 100644 --- a/src/detect/manifests/hermes.toml +++ b/src/detect/manifests/hermes.toml @@ -1,30 +1,102 @@ id = "hermes" -version = "2026.06.10.1" -min_engine_version = 1 -updated_at = "2026-06-10T00:00:00Z" +version = "2026.07.24.1" +min_engine_version = 2 +updated_at = "2026-07-24T19:12:54Z" aliases = ["hermes-agent"] +[[rules]] +id = "osc_title_blocked" +state = "blocked" +priority = 1100 +region = "osc_title" +visible_blocker = true +regex = ['^⚠[\u{fe0e}\u{fe0f}]?(?:\s|$)'] + +[[rules]] +id = "osc_title_working" +state = "working" +priority = 1050 +region = "osc_title" +visible_working = true +regex = ['^⏳[\u{fe0e}\u{fe0f}]?(?:\s|$)'] + [[rules]] id = "dangerous_command_approval" state = "blocked" -priority = 300 -region = "whole_recent" +priority = 900 +region = "bottom_non_empty_lines(14)" +visible_blocker = true +any = [ + { contains = ["dangerous"] }, + { contains = ["approval"] }, + { contains = ["allow once", "deny"] }, + { line_regex = ['(?i)^\s*[▸>]?\s*1\.\s*allow'] }, +] +all = [ + { any = [{ contains = ["enter confirm"] }, { contains = ["enter to confirm"] }, { contains = ["↑/↓ to select"] }, { contains = ["show full command"] }] }, +] + +[[rules]] +id = "clarification_prompt" +state = "blocked" +priority = 900 +region = "bottom_non_empty_lines(14)" +visible_blocker = true +any = [ + { contains = ["hermes needs your"] }, + { line_regex = ['^\s*ask\s+\S'] }, + { contains = ["type your answer"] }, +] +all = [ + { any = [{ contains = ["enter confirm"] }, { contains = ["enter to confirm"] }, { contains = ["enter send"] }, { contains = ["press enter"] }, { contains = ["↑/↓ select"] }, { contains = ["↑/↓ to select"] }, { contains = ["other (type"] }] }, +] + +[[rules]] +id = "credential_prompt" +state = "blocked" +priority = 900 +region = "bottom_non_empty_lines(14)" visible_blocker = true any = [ - { contains = ["dangerous command"] }, - { contains = ["allow once", "allow for this session", "deny"] }, + { contains = ["sudo password"] }, + { contains = ["skill setup"] }, + { contains = ["🔑", "for "] }, ] + +[[rules]] +id = "confirmation_prompt" +state = "blocked" +priority = 900 +region = "bottom_non_empty_lines(14)" +visible_blocker = true all = [ - { any = [{ contains = ["enter to confirm"] }, { contains = ["↑/↓ to select"] }, { contains = ["show full command"] }] }, + { any = [{ contains = ["approve once", "cancel"] }, { contains = ["start a new session", "keep going"] }] }, + { any = [{ contains = ["enter to confirm"] }, { contains = ["enter confirm"] }, { contains = ["type 1/2/3"] }, { contains = ["y/n quick"] }] }, ] [[rules]] id = "interrupt_status_working" state = "working" -priority = 100 -region = "whole_recent" +priority = 950 +region = "bottom_non_empty_lines(5)" visible_working = true any = [ { contains = ["msg=interrupt"] }, - { contains = ["ctrl+c cancel"] }, + { contains = ["ctrl+c to interrupt"] }, ] + +[[rules]] +id = "classic_cancel_working" +state = "working" +priority = 500 +region = "bottom_non_empty_lines(5)" +visible_working = true +contains = ["ctrl+c cancel"] + +[[rules]] +id = "osc_title_idle" +state = "idle" +priority = 100 +region = "osc_title" +visible_idle = true +regex = ['^✓[\u{fe0e}\u{fe0f}]?(?:\s|$)'] diff --git a/src/detect/mod.rs b/src/detect/mod.rs index b8eb8f60..33c4361c 100644 --- a/src/detect/mod.rs +++ b/src/detect/mod.rs @@ -286,13 +286,16 @@ pub(crate) fn full_lifecycle_hook_authority(source: &str, agent_label: &str) -> ("herdr:pi", "pi") | ("herdr:omp", "omp") | ("herdr:mastracode", "mastracode") - | ("herdr:hermes", "hermes") | ("herdr:opencode", "opencode") | ("herdr:kilo", "kilo") | ("herdr:kimi", "kimi") ) } +pub(crate) fn session_identity_only_integration(source: &str, agent_label: &str) -> bool { + (source, agent_label) == ("herdr:hermes", "hermes") +} + // --------------------------------------------------------------------------- // Process identification (platform-specific) // --------------------------------------------------------------------------- @@ -767,6 +770,13 @@ mod tests { assert!(!Agent::SCREEN_MANIFEST_AGENTS.contains(&Agent::Mastracode)); } + #[test] + fn hermes_session_integration_leaves_state_to_screen_detection() { + assert!(!full_lifecycle_hook_authority("herdr:hermes", "hermes")); + assert!(session_identity_only_integration("herdr:hermes", "hermes")); + assert!(Agent::SCREEN_MANIFEST_AGENTS.contains(&Agent::Hermes)); + } + #[test] fn identify_unknown_processes() { assert_eq!(identify_agent("bash"), None); -- cgit v1.2.3