Skip to content

[bug] Tool policies written in the documented integration.connection.tool form never match and fail silently #2047

Description

@mmarabel

Executor version

Executor Cloud (hosted, executor.sh), reproduced 2026-09-18.

Summary

policies.create documents its pattern as "a tool address tail (integration.connection.tool)", but policies are matched against integration.owner.connection.tool by a head-anchored, length-exact matcher. A pattern written in the documented three-segment form is accepted by isValidPattern, stored, and listed as an active policy — and can never match anything.

The result is a silently dead policy. There is no warning at create time, and policies.list shows it as active, so the only way to notice is to observe that the gate never changes behaviour.

For an approve policy this looks like "auto-approval doesn't work". For a block policy this is a security problem: an operator can believe a tool is blocked when every call is still dispatched.

Reproduction

  1. Pick any connection-backed MCP tool, e.g. <integration>.<owner>.<connection>.<tool>.
  2. Create a user policy using the form shown in the policies.create description — omitting the owner segment:
    pattern: "<integration>.<connection>.<tool>"
    action:  "approve"
    
  3. policies.list reports it as an active approve policy.
  4. Call the tool.

Expected

Either the policy matches and the tool auto-approves, or creation is rejected with an explanation that the pattern cannot match any tool address.

Actual

The tool still requires approval on every call. The policy never fires.

Changing the pattern to include the owner segment fixes it immediately:

"<integration>.*.<connection>.<tool>"     # works
"<integration>.<connection>.<tool>"       # accepted, never matches

Where the mismatch is

packages/core/sdk/src/executor.ts builds the id that policies are matched against with four segments:

const normalizedPolicyId = (tool: Tool): string =>
  tool.static
    ? String(tool.address)
    : `${tool.integration}.${tool.owner}.${tool.connection}.${tool.name}`;

packages/core/sdk/src/policies.ts matches from segment 0 and requires equal length for a pattern with no trailing *:

if (i >= toolSegments.length || toolSegments[i] !== seg) return false;
...
return patternSegments.length === toolSegments.length;

So a three-segment pattern fails at index 1, where <connection> is compared against <owner>.

Two pieces of guidance point at the three-segment form:

  • the public policies.create description: "pattern matches a tool address tail (integration.connection.tool)" — it is not a tail match, it is head-anchored
  • the comment above normalizedPolicyId: "Tool policies gate by tool identity (<integration>.<tool>)" — inconsistent with the four-segment id constructed directly beneath it

isValidPattern only checks syntax (no empty segments, no partial wildcards, no leading *), so a structurally unmatchable pattern passes.

Suggested fix

Any one of these would have prevented it, roughly in order of value:

  1. Validate at create/update time that the pattern can match at least one known tool address, or at minimum that its segment count is compatible, and reject with a message naming the expected form.
  2. Correct the policies.create description and the normalizedPolicyId comment to state the real grammar, integration.owner.connection.tool.
  3. Surface a match count in policies.list (e.g. matches: 0), so a dead policy is visible without having to test the gate.

(1) and (3) matter most for block policies, where silence is mistaken for enforcement.

Related

Not a duplicate of #1752 (workspace policies bypassed on toolkit-scoped endpoints). This is the inverse: a policy on the base endpoint that is never evaluated because the pattern cannot match.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions