aboutsummaryrefslogtreecommitdiff
path: root/src/detect
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-25 01:04:58 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-25 01:05:14 +0300
commit5f1957c27bd23f73b339265d3acd32f4a21878a3 (patch)
tree3a89bcc452130df0b18becb7aa9ec4c6008262ba /src/detect
parent38d2b0780c50e4773d6948660873386261cde71d (diff)
fix: harden hermes lifecycle detection
Diffstat (limited to 'src/detect')
-rw-r--r--src/detect/manifests/hermes.toml94
-rw-r--r--src/detect/mod.rs12
2 files changed, 94 insertions, 12 deletions
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)
// ---------------------------------------------------------------------------
@@ -768,6 +771,13 @@ mod tests {
}
#[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);
assert_eq!(identify_agent("zsh"), None);