Two related gaps in tools/approval.py (verified on current main and on a v0.18.0 install):
1. Non-interactive, non-gateway, non-cron contexts auto-approve dangerous commands with no fail-closed option
In check_dangerous_command, when the context is neither interactive CLI (HERMES_INTERACTIVE / ACP contextvar), nor gateway (HERMES_GATEWAY_SESSION / session platform), nor cron (HERMES_CRON_SESSION), flagged dangerous commands are approved with only a log warning:
AUTO-APPROVED dangerous command in non-interactive non-gateway context (pattern: %s): %s
There is no config option to make this branch deny instead. approvals.cron_mode covers only the cron case. Any scripted/headless invocation of the agent (test harnesses, CI, embedding hermes-agent as a library) silently loses the dangerous-command gate.
Proposal: an approvals.noninteractive_mode: deny | approve knob mirroring cron_mode. Ideally defaulting to deny (fail closed), but even defaulting to the current behavior would let operators opt in to safety.
2. Permanent command_allowlist pattern keys bypass approvals.cron_mode: deny
In check_dangerous_command, the permanent-allowlist checks (_command_matches_permanent_allowlist, then is_approved(session_key, pattern_key)) run before the HERMES_CRON_SESSION cron-deny branch.
Pattern-key entries in command_allowlist are written when a user picks "Always" on an interactive/gateway approval prompt (e.g. a Telegram button). That single tap stores a pattern class key such as recursive delete — which then approves every command matching that class in cron jobs too, silently overriding approvals.cron_mode: deny.
Observed in practice: cron jobs on our install were correctly BLOCKED on shell command via -c/-lc flag until a Telegram "Always" tap added that key to command_allowlist; from then on cron ran such commands ungated despite cron_mode: deny.
Note that check_execute_code_guard already orders these checks correctly — cron deny is evaluated before the session/permanent approval check. The terminal-command guard should match.
Proposal: in check_dangerous_command, evaluate the HERMES_CRON_SESSION + cron_mode branch before consulting is_approved/permanent allowlist (or add a separate approvals.cron_respects_allowlist toggle). An "Always" answered from an interactive surface shouldn't become a standing grant to unattended cron sessions.
🤖 Generated with Claude Code
Two related gaps in
tools/approval.py(verified on currentmainand on a v0.18.0 install):1. Non-interactive, non-gateway, non-cron contexts auto-approve dangerous commands with no fail-closed option
In
check_dangerous_command, when the context is neither interactive CLI (HERMES_INTERACTIVE/ ACP contextvar), nor gateway (HERMES_GATEWAY_SESSION/ session platform), nor cron (HERMES_CRON_SESSION), flagged dangerous commands are approved with only a log warning:There is no config option to make this branch deny instead.
approvals.cron_modecovers only the cron case. Any scripted/headless invocation of the agent (test harnesses, CI, embedding hermes-agent as a library) silently loses the dangerous-command gate.Proposal: an
approvals.noninteractive_mode: deny | approveknob mirroringcron_mode. Ideally defaulting todeny(fail closed), but even defaulting to the current behavior would let operators opt in to safety.2. Permanent
command_allowlistpattern keys bypassapprovals.cron_mode: denyIn
check_dangerous_command, the permanent-allowlist checks (_command_matches_permanent_allowlist, thenis_approved(session_key, pattern_key)) run before theHERMES_CRON_SESSIONcron-deny branch.Pattern-key entries in
command_allowlistare written when a user picks "Always" on an interactive/gateway approval prompt (e.g. a Telegram button). That single tap stores a pattern class key such asrecursive delete— which then approves every command matching that class in cron jobs too, silently overridingapprovals.cron_mode: deny.Observed in practice: cron jobs on our install were correctly BLOCKED on
shell command via -c/-lc flaguntil a Telegram "Always" tap added that key tocommand_allowlist; from then on cron ran such commands ungated despitecron_mode: deny.Note that
check_execute_code_guardalready orders these checks correctly — cron deny is evaluated before the session/permanent approval check. The terminal-command guard should match.Proposal: in
check_dangerous_command, evaluate theHERMES_CRON_SESSION+cron_modebranch before consultingis_approved/permanent allowlist (or add a separateapprovals.cron_respects_allowlisttoggle). An "Always" answered from an interactive surface shouldn't become a standing grant to unattended cron sessions.🤖 Generated with Claude Code