Skip to content

ScopedToolExecutor does not forward requires_confirmation, defaulting to false for the outermost wrapper #5906

Description

@bug-ops

Description

Same defect class as #5900's CompositeExecutor gap, found while auditing ScopedToolExecutor for the checkpoint-forwarding issue (#5905).

ScopedToolExecutor<E> (crates/zeph-tools/src/scope.rs:435-614) forwards set_skill_env, set_effective_trust, is_tool_retryable, and is_tool_speculatable to self.inner, but has no override for requires_confirmation (crates/zeph-tools/src/executor.rs:813-822). Grepping scope.rs for requires_confirmation returns zero matches.

Because it isn't overridden, the blanket impl<T: ToolExecutor> ErasedToolExecutor for T routes requires_confirmation_erased calls to the base trait default, which unconditionally returns false — discarding whatever the wrapped chain (e.g. TrustGateExecutor, which does override it at crates/zeph-tools/src/trust_gate.rs:304) would actually report.

ScopedToolExecutor is documented as the outermost wrapper in production wiring (its own module doc, confirmed in src/runner.rs:2680-2727), so whenever capability_scopes is configured, the speculative-dispatch engine (crates/zeph-core/src/agent/speculative/mod.rs) queries requires_confirmation on the fully composed top-level executor and always gets false back for tools wrapped by ScopedToolExecutor, regardless of the real policy underneath.

By contrast, ShadowProbeExecutor (audited in the same pass) correctly forwards requires_confirmation to self.inner (crates/zeph-tools/src/shadow_probe.rs:388-390) — the gap is specific to ScopedToolExecutor.

Impact / Current Exploitability

Per #5900's own note, no leaf executor currently sets is_tool_speculatable(tool_id) == true outside test code, so the speculative-dispatch path this feeds is not yet live for any real tool — this is currently dormant, not an active incident. However, capability_scopes is a documented, spec-driven feature (spec 050) meant for production use, and the trait's own doc for requires_confirmation states callers "must not treat None/default as authoritative bypass" in spirit — silently losing the confirmation requirement at the outermost layer defeats the purpose of the metadata query as soon as any tool opts into speculative dispatch.

Expected Behavior

ScopedToolExecutor::requires_confirmation should delegate: self.inner.requires_confirmation(call).

Actual Behavior

Always returns false (the trait default) regardless of what the wrapped chain reports.

Environment

  • HEAD: 2616f7b (zeph-tools rotation sweep, CI cycle 1285)
  • Crate: zeph-toolsscope.rs

Suggested Fix

fn requires_confirmation(&self, call: &ToolCall) -> bool {
    self.inner.requires_confirmation(call)
}

Add a regression test mirroring the pattern used for #5900, asserting the call reaches self.inner through ScopedToolExecutor.

Related: #5900 (same class, CompositeExecutor/AdversarialPolicyGateExecutor), #5905 (checkpoint-forwarding gap found in the same audit pass on scope.rs/shadow_probe.rs), #3869 (original defect class).

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2High value, medium complexitybugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions