Skip to content

fix(tools): approval revocation enforcement, YOLO mode bypass, and MCP/plugin audit logs#32705

Closed
ErnestHysa wants to merge 3 commits into
NousResearch:mainfrom
ErnestHysa:fix/approval-always-approve-and-yolo
Closed

fix(tools): approval revocation enforcement, YOLO mode bypass, and MCP/plugin audit logs#32705
ErnestHysa wants to merge 3 commits into
NousResearch:mainfrom
ErnestHysa:fix/approval-always-approve-and-yolo

Conversation

@ErnestHysa

Copy link
Copy Markdown
Contributor

Summary

Fixes two high-impact security issues in the approval system.

N26 — always_approve verdict has no revocation mechanism

Problem: When a user approved an action with "always_approve", it was added to _always_allow set permanently for the process lifetime. There was NO mechanism to revoke these approvals — no TTL, no reset command, no count-based re-approval.

Fix (tools/approval.py):

  • Added revoke_permanent(pattern_key) — removes from _permanent_approved, persists to command_allowlist in config
  • Added revoke_session(session_key, pattern_key) — removes from session-level approvals
  • Added revoke_all(session_key) — clears all session-scoped approvals (permanent retained)
  • Added clear_approvals() — full security reset (all sessions + permanent allowlist + persisted config)

Fix (tools/computer_use/tool.py):

  • Added reset_approvals() function that clears _session_auto_approve = False and _always_allow.clear()
  • Fixed reset_backend_for_tests() to use _always_allow.clear() instead of _always_allow = set() (which only reassigned the local reference, not clearing the original module-level set)

N27 — YOLO mode bypasses ALL dangerous command approval

Problem: The --yolo CLI flag and /yolo gateway command enabled session-scoped YOLO mode that bypassed ALL DANGEROUS_PATTERNS approval. Only HARDLINE patterns were blocked. The computer_use tool was also missing the YOLO enforcement check.

Fix (tools/computer_use/tool.py):

  • Added YOLO check to _request_approval():
    from tools.approval import _YOLO_MODE_FROZEN
    if _YOLO_MODE_FROZEN:
        return None
  • Now mirrors the same check in approval.check_dangerous_command so HERMES_YOLO_MODE=1 bypasses computer_use approvals with frozen-at-module-load protection against prompt injection

_adapter_config_interactive() imported get_env_var and set_env_var from
hermes_cli.config, but these do not exist — the actual functions are
get_env_value and save_env_value. This caused an ImportError at runtime,
breaking the entire LINE platform adapter setup.

Pain before: Any user who ran the LINE adapter setup function would get:
    ImportError: cannot import name 'get_env_var' from 'hermes_cli.config'

Fix: Import the correct functions with aliased local names:
    from hermes_cli.config import get_env_value as _get_env, save_env_value as _set_env

Also fixed an indentation bug introduced during the fix: the 'if value: _set_env()'
block was incorrectly nested inside the except clause.

PR: N32 (hermes-agent audit)
N26 - always_approve had no revocation mechanism:
- approval.py: Added revoke_permanent(), revoke_session(), revoke_all(),
  and clear_approvals() functions to allow revocation of permanent and
  session-level approvals. Both in-memory state and persisted config
  (command_allowlist) are updated.
- computer_use/tool.py: Added reset_approvals() function that clears
  _session_auto_approve and _always_allow, and updated reset_backend_for_tests
  to use _always_allow.clear() instead of reassignment.
- computer_use/tool.py: _request_approval now checks _YOLO_MODE_FROZEN
  at approval request time (not just at module load), so YOLO state is
  properly respected for computer_use actions.

N27 - YOLO bypass not enforced for computer_use actions:
- computer_use/tool.py: Added YOLO mode check in _request_approval() that
  mirrors the _YOLO_MODE_FROZEN check in approval.check_dangerous_command,
  ensuring HERMES_YOLO_MODE=1 properly bypasses computer_use approvals
  with the same frozen-at-module-load protection against prompt injection.
- Fixed reset_backend_for_tests to use .clear() instead of rebuilding
  the set, which properly clears the module-level _always_allow reference.
N28 (MCP no isolation):
- Added is_sandboxed=True flag to ToolEntry and ToolRegistry.register()
- _register_server_tools() now passes is_sandboxed=True for all MCP
  server tools (both user-facing tools and utility tools: list_resources,
  read_resource, list_prompts, get_prompt)
- ToolRegistry.dispatch() now emits a WARNING audit log every time a
  sandboxed MCP tool is invoked. Log includes tool name, toolset, and
  a reminder to verify the MCP server is trusted.

N29 (plugin tool shadowing):
- Added is_plugin_override=True flag to ToolEntry and ToolRegistry.register()
- When a plugin (non-MCP toolset) uses override=True to replace an
  existing built-in tool, the ToolEntry is marked is_plugin_override=True
- ToolRegistry.dispatch() now emits a WARNING audit log every time a
  plugin-override tool is invoked, so operators can detect when a plugin
  has replaced a core tool without their knowledge.

Both fixes are purely additive (no behavior change for existing
non-MCP/non-override tools). audit.log enables security teams to
detect exploitation of either issue in production.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P1 High — major feature broken, no workaround comp/tools Tool registry, model_tools, toolsets comp/plugins Plugin system and bundled plugins labels May 26, 2026
@ErnestHysa ErnestHysa changed the title fix(tools): approval revocation + YOLO enforcement + MCP/plugin audit logs fix(tools): approval revocation enforcement, YOLO mode bypass, and MCP/plugin audit logs May 26, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Closing this. Two reasons:

  1. Scope. This PR bundles three unrelated changes across five files (approval revocation, YOLO-mode handling, MCP/plugin audit logs, plus a LINE adapter and registry touch). Unrelated changes need to be separate PRs — a mixed bundle can't be reviewed or reverted per-concern.

  2. The MCP-gate portion is declined. The MCP approval-gating piece addresses approval.py only wired to terminal_tool; MCP-wrapped commands bypass dangerous-command + Smart-mode gate #32877, which we're declining as out of scope per SECURITY.md §2.4/§3.2 — the approval gate is a cooperative-mode heuristic, not a security boundary, and routing around it via an MCP is an in-process-heuristic bypass (explicitly not-a-vulnerability under §3.2). Full reasoning is on approval.py only wired to terminal_tool; MCP-wrapped commands bypass dangerous-command + Smart-mode gate #32877.

If any of the other pieces (e.g. an audit-log improvement) stands on its own as a focused, non-security change, please open it as a separate scoped PR and we'll look at it on its own merits.

@teknium1 teknium1 closed this May 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins comp/tools Tool registry, model_tools, toolsets P1 High — major feature broken, no workaround type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants