#6315·openhuman

Approval gate wraps outside the tool-policy check: a refused `use_skill` still raises an approval prompt

Author: M3gA-MindCreated Sep 15, 2026Updated Sep 15, 2026
Labelspriority: p2

Summary

This issue describes an ordering and its consequence. No behaviour change is proposed here. The approval gate is a security boundary; any change to when it runs is a decision for a maintainer, not a side effect of a feature fix. Recording it so the behaviour is known and can be discussed deliberately.

The ordering

In crates/openhuman-core/src/agent/tinyagents/harness_assembly.rs, ApprovalSecurityMiddleware is pushed before ToolPolicyMiddleware. The wrap onion runs tool_middlewares head-first (ToolHandler::runsplit_first), so a middleware pushed earlier wraps outside one pushed later. The approval gate therefore runs before the session's tool-policy check.

The consequence

UseSkillTool::permission_level_with_args reports the inner tool's permission level. So a use_skill naming a write-level tool that the session will refuse still raises a real approval prompt first:

  • The user is asked to approve work the agent is not permitted to do.
  • Approving changes nothing — the policy check then refuses it.
  • In a live turn, the turn parks on a prompt with no useful outcome either way.

Observed while testing #6302: use_skill { "skill": "skills", "tool": "skill_registry_install" } raises Run use_skill — use_skill (95 bytes of arguments) and is refused after approval. The MCP twin (mcp_registry_tool_call on a read-only echo) raises no prompt, which is why the two behave differently for what looks like the same refusal.

What is NOT being proposed

Reordering the two middlewares. CLAUDE.md states plainly that approval behaviour must not be weakened to make a feature work, and that rule is correct here: "refuse earlier so we prompt less" is still a change to when a security gate runs, and it deserves review on its own terms rather than riding along with a routing fix.

Current state

The refusal itself is unaffected — a refused tool does not execute, with or without the prompt. #6304's e2e test pins the stronger property by approving the prompt and asserting the raw tool is still refused with nothing written to disk.

Found during #6302. Parked for a later discussion at the user's request.