A capability's enforcement gate is wired to one caller instead of the executor every caller shares
A check that would deny a consequential tool call exists, is correctly written, and works every time it runs. It runs only because one specific caller invokes it before reaching the shared function that actually performs the action. Any other caller able to reach that same shared function directly inherits none of the restriction, because the check was never placed on the function itself.
Description
This weakness names a specific way an enforcement point can be present, correct and still fail to cover a shared capability. A system built around one orchestration flow can accrete a second, more direct way to reach the same underlying executor, whether a lower level API, a plugin interface or a protocol endpoint added for a different purpose. Where a consequential check, a blocklist, an approval requirement or an authorization gate, is written into the orchestration flow's own handler rather than into the executor both paths call, the check functions correctly for every request that happens to arrive through that one handler and functions not at all for a request that reaches the executor by any other route. This differs from action without applicable policy, AEW-001, where no check exists anywhere in the system for the action at all: here a check exists, is demonstrably correct, and this registry has independently confirmed it works exactly as intended on the one path that calls it. It differs from an operation blocked in one syntactic form being authorized in an equivalent one, AEW-020, which concerns a single check whose own traversal or classification logic fails to recognize an alternate representation of the identical operation or resource it was written to inspect: there the check runs on every path but does not recognize what it is looking at; here the check does not run at all on the paths that bypass its one caller, and the operation's identity is never in question. The authority gap is architectural rather than logical: the check was written to protect the action, not placed where the action is actually performed, so the action outlives its own gate the moment a second caller is added.
The authority gap
The authority presented is whatever reaches the shared executor by any available path. The authority required is a decision made at that executor, common to every path that can reach it, and the two are silently treated as equivalent because one particular path happens to supply the check.
Failure conditions
- A consequential tool or action is reachable through more than one caller or entry point that ultimately invokes the same shared executor function.
- A denial check for that action is written inside one caller's own handler rather than inside the shared executor every caller invokes.
- A second caller can invoke the shared executor directly, without passing through the handler that carries the check.
- Nothing at the shared executor itself re-evaluates whether the action is permitted, treating the caller's own prior handling as sufficient.
Consequences shown by the evidence
- A blocklist or approval gate that works correctly and visibly in the flow it was written for provides no protection at all against the same action reached through a different, newer entry point.
- A security review that exercises only the original orchestration flow concludes the action is gated, because every test it ran through that flow passed.
- Adding a new, more direct way to reach an existing capability silently reopens a restriction the system's own design already settled once, with no code change to the restriction itself.
Detection signals
- A denial check for a named consequential tool appears inside one specific request handler's own code rather than inside the function that tool ultimately calls.
- More than one route through the system reaches the same executor function, and only one of those routes carries a call to the check.
- A remediation for an incident relocates an existing, previously correct check from a caller's handler into the shared function the caller and other paths both invoke, rather than rewriting the check's own logic.
Known examples
- CVE-2026-59726, formally assigned in GHSA-c4hm-4h84-2cf3, documents Ruflo's own isBlockedTool function, which this record confirmed directly by reading the pre-fix source, correctly matching a terminal_execute pattern and blocking it wherever it ran. It ran in exactly one place, inside the request handler for Ruflo's autopilot mode. The two direct MCP endpoints, POST /mcp and POST /mcp/:group, called executeTool without ever calling isBlockedTool first, so an unauthenticated caller reaching either endpoint directly could invoke terminal_execute with none of the restriction autopilot enforced. Remediation commit d00a0a40cd8bdbca877ac7f675f416bdc69accd1, merged in pull request 2521 as version 3.16.3, moves the gate into executeTool itself; the pull request's own description states directly that the sole call site of isBlockedTool had been the autopilot handler, and that the fix gives every path that reaches executeTool one shared denial gate.
- CVE-2026-86996, formally assigned in GHSA-7hgx-277f-7vmg and published 2 September 2026, documents n8n's own "This workflow can be called by" sub-workflow caller policy being enforced only through the Execute Workflow node's own request path. The identical restricted workflow, attached instead as a tool to an AI Agent node, reached execution and returned its output with no equivalent check on that path, letting a user with Agent-building capability, whether or not named as an approved caller, invoke a restricted workflow and read what it returned. Fixed in 2.38.2 and 2.37.7, the latter independently confirmed by this session against n8n's own GitHub releases page as published the same date the advisory itself carries; the advisory's own patch description is one sentence, that the fix applies the sub-workflow caller policy on the Agent tool path, naming no specific commit or pull request this session could locate. Held at a lower confidence than the known example above: this session verified the advisory's own stated facts through two independent fetches but could not read the affected source directly the way Ruflo's own pre-fix and post-fix code was read, so the specific code location of the gap, though the advisory's own architecture matches this weakness precisely, is confirmed only at the level the advisory's own prose states it.
Recorded instances
- AEV-2026-0038 Ruflo's MCP bridge reached an unblocked terminal_execute and a writable AgentDB with no authentication (CVE-2026-59726) AESS 8.5 high Confirmed
Protocol evidence
Published authority architectures whose artifacts bear on the requirement this weakness names: which satisfy it, which expose a way past it, and which leave it unanswered.
- Supports requirement
Agent Action Decision Protocol (AADP)
Shamik Saha, individual submission to the IETF
Requirement A Policy Decision Point owns authorization state and evidence; PEPs enforce it
AADP's separation of a Policy Decision Point that must issue a permit before a Policy Enforcement Point performs an action names, in the abstract, exactly the placement question Ruflo's own pre-fix source answers concretely and badly: this session confirmed directly that isBlockedTool, a correctly written decision function, was invoked only from inside Ruflo's autopilot handler, never from the shared executeTool function that handler and two unauthenticated MCP endpoints all ultimately call. A decision point wired into one caller's own code, rather than into the enforcement point every caller shares, protects exactly the callers that happen to invoke it and none of the others, which is the specific failure mode AADP's own separation of roles is built to prevent by keeping the decision, not the caller, authoritative over whether the action proceeds.
- Supports requirement
Agent Control Standard (ACS)
OWASP GenAI Security Project, originally Zenity
Requirement The specification does not guarantee complete mediation of every consequential action a host can take
ACS's own conformance document states directly that the specification does not guarantee complete mediation of every consequential action a host can take, and that a primitive bypassing its own toolCallRequest hook is invisible to policy. Ruflo's own pre-fix source is concrete evidence for the same admission in a different architecture: a mediation point, isBlockedTool, was present, correctly implemented, and independently confirmed by this session to work wherever it actually ran, while two direct MCP endpoints reached the identical underlying executor, executeTool, through a path that mediation point had never been wired into. This entry's own direct reading of the affected source before and after the fix supports the requirement that mediation coverage be verified against every caller able to reach a shared executor, not only the orchestration flow a mediation point's own author originally wrote it for.
