Skip to content

fix(agent): don't prompt for approval when the session permission layer will refuse the call - #7699

Open
akushonkamen wants to merge 1 commit into
tinyhumansai:mainfrom
akushonkamen:fix/7649-approval-before-permission
Open

akushonkamen wants to merge 1 commit into
tinyhumansai:mainfrom
akushonkamen:fix/7649-approval-before-permission

Conversation

@akushonkamen

@akushonkamen akushonkamen commented Oct 13, 2026 •

Copy link
Copy Markdown

Summary

  • Stops the approval flow from prompting the user for tool calls that the session's permission layer is already going to refuse. A session-denied call now goes straight to the permission denial, with no approval card.
  • The session permission decision (ToolPolicyMiddleware::channel_permission_block) is exposed to the approval middleware through a shared borrow view (SessionPermissionView), keeping the permission logic in one place (single source of truth) with zero predicate duplication.
  • The no-session-snapshot path (CLI / subagents) is unchanged.

Problem

Issue #7649: OpenHuman may ask the user to approve a tool action even when the session's permissions will refuse that action afterward. The user approves, and is then denied anyway — a spurious interactive prompt that interrupts the workflow.

Root cause: the approval middleware is registered as the outermost tool wrap (ApprovalGateMiddleware at crates/openhuman-core/src/agent/tinyagents/harness_assembly.rs:562, same order in live_harness.rs:95), and ApprovalSecurityMiddleware::requires_approval (crates/openhuman-core/src/agent/tinyagents/middleware/approval.rs:102) only checks whether the tool declares an external effect. The session's channel-permission ceiling (ToolPolicyMiddleware::channel_permission_block, tool_policy.rs:236) runs later, on the inner wrap, so a call the session must deny still triggers an approval prompt first.

Solution

  • The permission decision logic stays where it is, in ToolPolicyMiddleware, and is shared with the approval layer through a borrowed view SessionPermissionView (tool_policy.rs) instead of copying predicates — the repo's single-source-of-truth convention.
  • Both assembly points inject the ToolPolicyEnforcement session snapshot into the approval middleware via ApprovalSecurityMiddleware::with_session_permission (harness_assembly.rs, live_harness.rs).
  • requires_approval now pre-checks with the same channel_permission_block before showing a card: if the session will refuse the call, the approval prompt is skipped and the permission layer produces the denial directly.
  • Without a session snapshot (CLI / subagents) the decision is byte-for-byte the old one, covered by a dedicated control test.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) per Testing Strategy: middleware_approval_guard_tests.rs gains 4 cases — a_session_denied_external_effect_tool_is_not_held_for_approval, a_call_above_the_session_permission_ceiling_is_not_held_for_approval (both red without the fix: the prompt fired anyway), and controls without_a_session_snapshot_the_prompt_decision_is_unchanged, a_permitted_external_effect_tool_still_requires_approval (both green before and after).
  • Diff coverage ≥ 80%: all new/changed lines are exercised by the 4 new cases plus the existing suites below; full-repo coverage not run locally (CI gate will measure it).
  • Coverage matrix updated: N/A — behaviour-only change, no feature rows added/removed.
  • All affected feature IDs from the matrix are listed under ## Related: N/A — no feature IDs affected.
  • No new external network dependencies introduced.
  • Manual smoke checklist: N/A — does not touch release-cut surfaces.
  • Linked issue closed via closing keyword: Fixes #7649.

Gates run (after rebase onto origin/main 9dbf8f0568, toolchain 1.96.1):

  • Red (fix reverted, tests present, as recorded during development): scripts/ci-cancel-aware.sh cargo test -p openhuman --lib approval_guard → 5 passed / 2 failed — the two new cases red, the two controls green. On the clean rebased base the new cases do not exist (3 passed / 0 failed); dropping only the new test file onto the base fails to compile with E0599: no method named with_session_permission (×3), i.e. the tests require the new API and cannot pass without the fix.
  • Green: scripts/ci-cancel-aware.sh cargo test -p openhuman --lib approval_guard → 7 passed / 0 failed (9567 filtered out).
  • Full approval sweep: cargo test -p openhuman --lib approval → 256 passed / 1 failed. The single failure is channels::host::channel_events::tests::approval_prompts_reach_every_chat_channel, panicking at crates/openhuman-core/src/channels/module_fixture_tests.rs:21 (tinychannels-module fixture unavailable: blocked on tinybus 0.1.4); re-run standalone on the clean base commit 9dbf8f0568 and it panics identically — pre-existing, unrelated to this PR. (The suite total moved from 264 to 256 because main dropped tests between the two runs, not because of this change.)
  • Related subsets: cargo test -p openhuman --lib middleware → 210 passed / 0 failed / 1 ignored; --lib harness_assembly → 6 passed; --lib security_gate → 29 passed; --features voice --lib live_harness → 3 passed (module is voice-gated, not compiled under default features).
  • cargo check -p openhuman --lib --features voice → exit 0.
  • cargo fmt --check on the 5 changed files → clean.
  • cargo clippy -p openhuman --lib --all-targets → exit 0; 31 warnings, none from the changed files (verified by grep over clippy output).

Note: the ticket's verification command names package -p openhuman-core, which does not exist; the crate at crates/openhuman-core is the package openhuman, so all commands above use -p openhuman.

Impact

  • Runtime: desktop + any session-backed surface. Tool calls the session permission ceiling will deny no longer raise an approval card; they are denied directly with the permission layer's reason. CLI / subagent (no session snapshot) behaviour is unchanged.
  • Security: no permission decision is weakened — the approval pre-check delegates to the same channel_permission_block that enforces the ceiling, so approvals cannot bypass it.
  • No migration, performance or compatibility implications.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

Commit & Branch

  • Branch: fix/7649-approval-before-permission
  • Commit SHA: 7f4e63ae7c8973bbdf414e3ab8927fb4638bd7a9

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: change touches only crates/openhuman-core (Rust), no frontend code.
  • pnpm typecheck — N/A: no TypeScript changed.
  • Focused tests: scripts/ci-cancel-aware.sh cargo test -p openhuman --lib approval_guard → 7 passed / 0 failed; subsets and full sweep as listed under Gates run.
  • Rust fmt/check (if changed): cargo fmt --check on the changed files → clean; cargo check -p openhuman --lib --features voice → exit 0.
  • Tauri fmt/check (if changed): N/A — no Tauri code changed.

Validation Blocked

  • command: none
  • error: none
  • impact: none

Behavior Changes

  • Intended behavior change: a tool call that the session permission ceiling denies is no longer held for approval; the permission layer denies it directly.
  • User-visible effect: no more approve-then-denied approval cards for session-forbidden tools; fewer workflow interruptions.

Parity Contract

  • Legacy behavior preserved: yes — with no session snapshot the prompt decision is unchanged (control test without_a_session_snapshot_the_prompt_decision_is_unchanged).
  • Guard/fallback/dispatch parity checks: a_permitted_external_effect_tool_still_requires_approval asserts permitted external-effect tools still prompt; approval-identity scoping suite unchanged and green (210 middleware tests).

Duplicate / Superseded PR Handling

  • Duplicate PR(s): N/A
  • Canonical PR: this one
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • Bug Fixes
    • Approval prompts are no longer shown for tool calls that the session’s permissions will block. These calls now proceed directly to the permission check, which returns the applicable denial reason.
    • Approval is still requested for calls permitted by the session, and calls without session permission details retain their existing approval behavior.

…er will refuse the call

ApprovalSecurityMiddleware is the outermost tool wrap while the session's
channel-permission ceiling lives on the inner ToolPolicyMiddleware, so a call
the session refuses anyway raised an approval card first and was denied only
after the human answered. Host the permission predicates on a borrowed
SessionPermissionView shared by both gates, thread the session snapshot into
the approval middleware at both assembly sites (turn harness + live/voice),
and consult it in requires_approval: a refused verdict skips the prompt and
the permission layer answers with its own rendered reason. Paths without a
session snapshot (CLI / sub-agent) prompt exactly as before.

Closes tinyhumansai#7649
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-13T11:36:22.059353Z 7f4e63a PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 4de0b74e-a994-475b-bb7c-8b9e84b029a0
📥 Commits

Reviewing files that changed from the base of the PR and between 3742750 and 7f4e63a.

📒 Files selected for processing (5)
  • crates/openhuman-core/src/agent/tinyagents/harness_assembly.rs
  • crates/openhuman-core/src/agent/tinyagents/live_harness.rs
  • crates/openhuman-core/src/agent/tinyagents/middleware/approval.rs
  • crates/openhuman-core/src/agent/tinyagents/middleware/tool_policy.rs
  • crates/openhuman-core/src/agent/tinyagents/middleware_approval_guard_tests.rs

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

Approval middleware now checks the session permission snapshot before showing an approval prompt. Harnesses pass the available snapshot to the middleware, and shared session permission logic supports the check.

Changes: Session-Aware Approval Guard

Layer / File(s) Summary
Shared session permission resolution
crates/openhuman-core/src/agent/tinyagents/middleware/tool_policy.rs
A borrowed session permission view centralizes permission resolution and tool lookup. ToolPolicyMiddleware delegates permission-related operations through this view.
Approval pre-check and harness wiring
crates/openhuman-core/src/agent/tinyagents/middleware/approval.rs, crates/openhuman-core/src/agent/tinyagents/harness_assembly.rs, crates/openhuman-core/src/agent/tinyagents/live_harness.rs, crates/openhuman-core/src/agent/tinyagents/middleware_approval_guard_tests.rs
ApprovalSecurityMiddleware skips the prompt when the session permission check will refuse an external-effect call. The harnesses pass an available session and channel snapshot. Tests cover denied calls, calls above the permission ceiling, allowed calls, and calls without a snapshot.

Priority: ⬇️ Low

Estimated code review effort: 3 (Moderate) | ~20 minutes

Change: Bug fix · Severity of issue fixed: Low

Sequence Diagram(s)

sequenceDiagram
  participant ApprovalSecurityMiddleware
  participant SessionPermissionView
  participant ToolPolicyMiddleware
  participant ApprovalPrompt
  ApprovalSecurityMiddleware->>SessionPermissionView: Check session permission
  alt Session permission refuses call
    ApprovalSecurityMiddleware->>ToolPolicyMiddleware: Continue without approval prompt
  else Session permission allows call
    ApprovalSecurityMiddleware->>ApprovalPrompt: Request approval for external-effect call
    ApprovalSecurityMiddleware->>ToolPolicyMiddleware: Continue through inner permission layer
  end
Loading

Suggested reviewers: senamakel

Merge Risk: ⚪ Minimal · up to 7f4e6

Session-denied calls can bypass an unnecessary approval prompt while remaining subject to tool-policy enforcement. No actionable merge risk remains after normal checks.

Security architecture risk: 🔵 Low · up to 7f4e6

The change removes prompts for actions already disallowed while retaining permission enforcement. The inspected code shows no newly granted authority, but the complete execution path and behavior during concurrent configuration changes remain unverified.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • observed — The changed suppression path applies to session-backed non-hosted turn runs and session-backed live runs. Hosted turn approval registration remains excluded by its existing condition, and a missing enforcement context supplies no snapshot to activate the new pre-check.

Trust Boundaries and Controls

  • observed — Approval suppression does not return an authorization grant or invoke a tool. It returns false for prompt eligibility. The inner permission control separately checks the call name, packed-tool reachability and argument-dependent permission ceiling, and blocks before downstream execution.

Resilience and Maintainability Implications

  • observed — The added regression tests cover prompt suppression for explicit denial and an exceeded permission ceiling, with permitted-call and no-snapshot controls. They call the resolver directly and therefore do not establish the composed skip-to-terminal-denial invariant during cancellation, retries or concurrent configuration changes.

Hardening Proposals

  • proposed — Validate the composed turn and live harnesses with an execution-counting tool: a session-denied call must produce a permission error without an approval request or tool execution, including repeated and interrupted attempts.
Pre-merge checks · Passed 4 · Failed 1

Failed Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 60.61% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 33 functions across 5 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks (4 passed)
Check name Status Explanation
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed The title clearly and concisely describes the main change: avoiding approval prompts when the session permission layer will deny the call.
Linked Issues check Passed Issue #7649 requires removal of approval prompts for calls that the session permission layer will refuse. The PR injects the session permission snapshot at both harness assembly points. `ApprovalSecur…
Out of Scope Changes check Passed The changes remain connected to issue #7649. The tool_policy.rs changes extract existing permission decisions into SessionPermissionView so the approval pre-check and enforcement use one decision …
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

A rabbit checks the gate with care,
Before an approval card appears.
If session rules say “not this way,”
The prompt stays tucked and calls obey.
Then carrots crunch; the paths are clear.

Comment @coderabbitai help to get the list of available commands.

@tinysweeper

tinysweeper Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Tiny Sweeper review

Tiny Sweeper reviewed this change across 5 lane(s) and found 0 active actionable finding(s). Detailed lane evidence and any incomplete work are listed below.

State: Incomplete
Priority: none
Reviewed head: 7f4e63ae7c89
Updated: 2026-10-13T17:41:17Z

Review snapshot

Change surface Files Review signal Count
Production 4 Active findings 0
Tests 1 Noted findings 0
Documentation 0 Resolved findings 0
Configuration 0 Pending checks/questions 6

Completeness: Incomplete
Test assessment: No supported feature-to-test mapping was available; this does not mean tests are absent or passed.

Features

None identified with supported citations.

Tests

No supported feature-to-test mapping was produced. Test execution is not inferred.

Findings

No active actionable findings.

Could not review: crates/openhuman-core/src/agent/tinyagents/harness_assembly.rs, crates/openhuman-core/src/agent/tinyagents/live_harness.rs, crates/openhuman-core/src/agent/tinyagents/middleware/approval.rs, crates/openhuman-core/src/agent/tinyagents/middleware/tool_policy.rs, crates/openhuman-core/src/agent/tinyagents/middleware_approval_guard_tests.rs, tinysweeper/description

Before merge

  • Complete the critique review for crates/openhuman-core/src/agent/tinyagents/harness_assembly.rs, crates/openhuman-core/src/agent/tinyagents/live_harness.rs, crates/openhuman-core/src/agent/tinyagents/middleware/approval.rs, crates/openhuman-core/src/agent/tinyagents/middleware/tool_policy.rs, crates/openhuman-core/src/agent/tinyagents/middleware_approval_guard_tests.rs.
  • Complete the description review for tinysweeper/description.

How this fits together

flowchart LR
  n0["ToolPolicyMiddleware<br/>changed"]:::changed
  n1["channel_permission_block"]:::impacted
  n2["Box"]:::impacted
  n3["non_owner_middleware"]:::impacted
  n4["assemble_turn_harness"]:::impacted
  n5["wrap_tool"]:::impacted
  n6["non_owner_middleware_at"]:::impacted
  n0 -->|uses| n2
  n3 -->|uses| n0
  n3 -->|calls| n6
  n4 -->|uses| n0
  n4 -->|uses| n2
  n5 -->|calls| n1
  n6 -->|uses| n0
  n6 -->|uses| n2
  classDef changed fill:#0d4429,stroke:#238636,color:#e6edf3
  classDef impacted fill:#161b22,stroke:#6e7681,color:#c9d1d9
  classDef flagged fill:#5a1e02,stroke:#d93f0b,color:#ffffff
  classDef blocking fill:#67060c,stroke:#f85149,color:#ffffff
Loading
Agent review details

critique

  • Conclusion: Neutral
  • Scope reviewed: incomplete; unanswered: crates/openhuman-core/src/agent/tinyagents/harness_assembly.rs, crates/openhuman-core/src/agent/tinyagents/live_harness.rs, crates/openhuman-core/src/agent/tinyagents/middleware/approval.rs, crates/openhuman-core/src/agent/tinyagents/middleware/tool_policy.rs, crates/openhuman-core/src/agent/tinyagents/middleware_approval_guard_tests.rs
  • Lane summary: Reviewed 0 files; 0 findings. 5 files could not be reviewed: crates/openhuman-core/src/agent/tinyagents/harness_assembly.rs, crates/openhuman-core/src/agent/tinyagents/live_harness.rs, crates/openhuman-core/src/agent/tinyagents/middleware/approval.rs, crates/openhuman-core/src/agent/tinyagents/middleware/tool_policy.rs, crates/openhuman-core/src/agent/tinyagents/middleware_approval_guard_tests.rs.

security

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: Reviewed 5 files; 0 findings. _The code index is behind this pull request (indexed at `fb18dbecc6b7`), so retrieved context may be out of date._

tests

  • Conclusion: Success
  • Scope reviewed: all assigned evidence
  • Lane summary: The pre-check reuses `SessionPermissionView::channel_permission_block`, which `ToolPolicyMiddleware::wrap_tool` re-evaluates identically before its own prompt, so skipping the card here cannot let a permission-refused call slip through — the inner layer still denies with its own rendered reason (or prompts, unchanged). The four new tests in `middleware_approval_guard_tests.rs` pin the deny case, the ceiling case, the permitted control, and the no-snapshot path, so the new branch is covered in both directions. I found no defect worth reporting. _The code index is behind this pull request (indexed at `fb18dbecc6b7`), so retrieved context may be out of date._

commits

  • Conclusion: Neutral
  • Scope reviewed: all assigned evidence
  • Lane summary: Nothing sensitive found in what this pull request commits.

description

  • Conclusion: Neutral
  • Scope reviewed: incomplete; unanswered: tinysweeper/description
  • Lane summary: No reviewer produced a usable response.
Evidence and run details
  • Models: ladder/vectors-oai3, deepseek-v4.1-flash
  • Spend: $0.002046
  • Tokens: 343863 input · 27364 output · 56320 cached · 677 embedding
Head State Pass summary
7f4e63ae7c89 incomplete 0 active finding(s), 0 resolved finding(s) (at 2026-10-13T17:41:17Z)

tinysweeper 0.1.2

@tinysweeper tinysweeper Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tinysweeper found nothing blocking, but could not review everything, so this is not an approval: crates/openhuman-core/src/agent/tinyagents/harness_assembly.rs, crates/openhuman-core/src/agent/tinyagents/live_harness.rs, crates/openhuman-core/src/agent/tinyagents/middleware/approval.rs, crates/openhuman-core/src/agent/tinyagents/middleware/tool_policy.rs, crates/openhuman-core/src/agent/tinyagents/middleware_approval_guard_tests.rs, tinysweeper/description.

             $0.0020 · 343,863 in / 27,364 out · 56,320 cached (16%) · ladder/vectors-oai3, deepseek-v4.1-flash · 677 embedded
critique:    $0.0007 · 62,714 in  / 9,350 out  · 18,432 cached (29%) · deepseek-v4.1-flash
security:    $0.0009 · 196,298 in / 11,319 out · 27,904 cached (14%) · deepseek-v4.1-flash
tests:       $0.0000 · 45,456 in  / 532 out    · 5,888 cached (13%)  · deepseek-v4.1-flash
description: $0.0002 · 30,375 in  / 2,768 out  · 4,096 cached (13%)  · deepseek-v4.1-flash

@tinysweeper tinysweeper Bot added the priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect. label Oct 13, 2026
AmirrezaFarnamTaheri added a commit to AmirrezaFarnamTaheri/openhuman that referenced this pull request Oct 13, 2026
AmirrezaFarnamTaheri added a commit to AmirrezaFarnamTaheri/openhuman that referenced this pull request Oct 13, 2026
AmirrezaFarnamTaheri added a commit to AmirrezaFarnamTaheri/openhuman that referenced this pull request Oct 13, 2026
AmirrezaFarnamTaheri added a commit to AmirrezaFarnamTaheri/openhuman that referenced this pull request Oct 13, 2026
AmirrezaFarnamTaheri added a commit to AmirrezaFarnamTaheri/openhuman that referenced this pull request Oct 13, 2026

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p3 Whenever. Cosmetic, a nicety, or a cleanup with no user visible effect.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Approval prompt appears even when a tool is not permitted

1 participant