dtwo Policy Store

Freeze Standing Automation & AI Agents in monday

Denies the monday tools that install side effects which outlive the governed MCP session. Two classes of tool are blocked:

Direction
ingress
Rego package
monday.ingress.freeze_standing_automation
App
monday
Bundle
soc2
Published
Minimum gateway
1.0.0b24
Schema version
1.0.0
Checksum
sha256:08b9177f9aa3e60d9e719d13179c81b36802708b2407ecf9ca4be3daf9f221db

mondayconstrain-aggregatoringresssoc2

What this policy does

Direction: ingress (tool_pre_invoke) Default: deny the persistence tools, allow everything else Package: monday.ingress.freeze_standing_automation

What it does

Denies the monday tools that install side effects which outlive the governed MCP session. Two classes of tool are blocked:

  • Standing automations / workflowscreate_automation, manage_automations, create_workflow, update_workflow, plan_workflow, publish_workflow. These create rule-based automations and workflows that keep firing on monday's servers after the agent's session is over.
  • Autonomous monday AI agentsmanage_agent, manage_agent_triggers, manage_agent_skills, manage_agent_knowledge. These create and configure monday's own AI agents, their triggers, their skills, and the knowledge they act on — agents that keep acting on their own after this session ends.

Per the monday landscape note, both classes are a persistence mechanism that is invisible to per-call governance: once installed, they act on their own, and no subsequent tool call passes through the gateway for the gateway to inspect. A prompt-injected agent that can install an automation or an autonomous agent has effectively escaped the session boundary. So this policy freezes that self-expanding surface at ingress — neither an agent nor an injected prompt can create one.

A designated platform-admin IdP group may optionally be allow-listed as the legitimate automation author: callers in that group are permitted to use these tools. The allow-list is read from input.subject.claims.groups and fails closed — if the claim is missing, empty, or malformed, the caller is treated as not privileged and the persistence tools are denied. Every tool this policy does not target passes through untouched.

Compliance alignment

  • SOC 2 CC6.6 — supports boundary protection against external threats: a self-expanding surface (agent-installed automations, autonomous AI agents) that would let the agent channel keep acting outside the gateway's per-call boundary is frozen, so a prompt-injection or a runaway agent cannot plant a persistent foothold.
  • SOC 2 CC6.8 — supports the prevention of unauthorized software: an autonomous monday AI agent or a standing automation is, in effect, new software running in the account; the agent path may not install it without an explicit platform-admin allow-list entry.
  • SOC 2 CC9.2 — supports vendor / business-partner risk management: the monday automations and AI agents installed over the agent channel become part of the account's ongoing processing surface; freezing them keeps that surface to what a human deliberately created.

Tool name matching

monday's official server exposes these tools unprefixed and in bare snake_case: create_automation, manage_automations, create_workflow, update_workflow, plan_workflow, publish_workflow, manage_agent, manage_agent_triggers, manage_agent_skills, manage_agent_knowledge.

Behind the DTwo gateway a tool appears as <configured-server-name><sep><tool> and the server-name prefix is not standardized across deployments. The policy therefore matches case-insensitively on lower(input.resource.name) as either the exact bare name or a - / _-separated suffix, so it tolerates any gateway prefix joined to the tool by a - or _ (monday-mcp-create_automation, monday_mcp_create_automation) — the two separators DTwo actually emits. A prefix joined by some other character (., :, /) is not matched and passes through; see Known limitations. Requiring a separator before the suffix avoids gluing false positives — the real write tool link_board_items_workflow ends in _workflow but does not end in -create_workflow / _plan_workflow / any targeted suffix, so it is not denied.

These persistence/agent tools are part of the official monday MCP registry; the community sakce/mcp-server-monday does not expose them. Verify the exact names your gateway sends with the dump-input debug technique before relying on this in production.

Argument shape

This policy inspects only the tool name (input.resource.name) and the caller's groups claim. It reads no tool arguments, so it is insensitive to argument-shape differences and to the fact that several of these tools' exact argument schemas were not verified against source (see Known limitations). A missing resource/resource.name resolves to "" via object.get and matches nothing; a non-string name is coerced to "" rather than handed to lower() (which would raise a built-in type error).

Identity

The optional allow-list is keyed on the caller's IdP group claim:

  • input.subject.claims.groups is read via object.get chains, defaulting to [] — a missing subject, claims, or groups yields no groups.
  • The placeholder admin group is platform-admin. Group names are compared case-insensitively.
  • A missing or malformed (non-array) groups claim never grants the exemption: the caller is not privileged and the persistence tools are denied (fail closed).

Examples

Allowed — a non-persistence tool

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "monday-mcp-get_board_items_page", "type": "tool" },
    "payload": {
      "name": "monday-mcp-get_board_items_page",
      "args": { "boardId": 12345 }
    }
  }
}

allow = true, no reason.

Allowed — a platform-admin creating an automation

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "monday-mcp-create_automation", "type": "tool" },
    "subject": { "sub": "google-apps|ops@example.com", "claims": { "groups": ["platform-admin"] } },
    "payload": { "name": "monday-mcp-create_automation", "args": {} }
  }
}

allow = true, no reason.

Denied — an agent installing a standing automation

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "monday-mcp-create_automation", "type": "tool" },
    "subject": { "sub": "google-apps|bot@example.com", "claims": { "groups": ["engineering"] } },
    "payload": { "name": "monday-mcp-create_automation", "args": {} }
  }
}

allow = false, automation reason.

Denied — configuring an autonomous AI agent

{
  "input": {
    "action": "tool_pre_invoke",
    "resource": { "name": "monday-mcp-manage_agent", "type": "tool" },
    "payload": { "name": "monday-mcp-manage_agent", "args": {} }
  }
}

allow = false, agent reason (no groups claim → fails closed).

Composition

This policy is single-purpose: it freezes the persistence/agent surface. Useful companions:

  • apps/monday/default-deny-unknown-tools — the outer allowlist gate. If a tenant runs default-deny, these tools are already off the allowlist; keep this policy attached so that even if a write tool is later allowlisted, the persistence tools stay frozen for non-admin callers.
  • apps/monday/fence-sensitive-boards — board/workspace IdP-group fencing for the reads and reversible writes this policy leaves untouched.
  • An escape-hatch deny for all_monday_api / all_api_write / manage_tools — without it, an automation could be installed via one raw GraphQL query string that this name-based policy never sees (see Known limitations).

Known limitations

  • GraphQL escape hatch bypasses this policy. all_monday_api / all_api_write reduce every mutation — including installing an automation or an agent — to one opaque GraphQL string with no tool name this policy targets. Attach the escape-hatch deny companion, or the freeze is defeatable.
  • Name-based, argument-agnostic. The policy trusts the tool name, not the behavior behind it. A tool renamed upstream to something not on the suffix list, or a new persistence tool, is not covered until added; pair with default-deny-unknown-tools so unaudited names fail closed instead. The exact argument schemas of several of these tools were not verified against source — the policy does not depend on them, but a companion that inspects arguments should confirm shapes with dump-input first.
  • endswith trusts the suffix with a required separator. Matching accepts the exact bare name or a -/_-separated suffix. A tool literally named <prefix>-create_automation (any prefix glued with a separator) matches — the intended portability behavior. A tool that glued a targeted suffix on with no separator would not match; no monday tool does this. Only - and _ count as separators. If a gateway ever joined the server-name prefix to the tool with a different character (e.g. monday.mcp.create_automation, monday:create_automation, monday/create_automation), the suffix branch would not fire and the call would pass through — a residual, not a block. DTwo emits -/_ (verified against the model deployments), so this only bites an unusual custom naming scheme; confirm your gateway's actual separator with dump-input before relying on the freeze. A bare, unprefixed name is always caught by the exact-match branch.
  • groups claim must be an array of strings under input.subject.claims.groups. Any other shape fails closed: a string-valued claim, an object/map claim (even one whose values spell platform-admin, e.g. {"role":"platform-admin"}), or an array containing no matching string element all leave the caller not-admin, so the persistence tools deny. The caller_is_admin rule guards with is_array before iterating and is_string on each element, so a non-string array element is skipped rather than raising a type error. If your IdP emits groups under a different claim name (e.g. a namespaced custom claim), update admin_groups and caller_groups in the Rego.
  • Placeholder group name. platform-admin is a placeholder — replace it with your IdP's actual automation-author group name at import time. If you want no exemption at all (freeze for everyone including admins), remove the caller_is_admin allow branch.
  • Exact upstream suffixes unverified for your gateway. The bare names are verified from mondaycom/mcp source, but the string your gateway sends depends on the configured server name. Confirm with dump-input before relying on it.

Compliance note. This policy supports alignment with the cited framework controls on the MCP path only. No policy or bundle makes an organization compliant with any framework; web-UI, native-API, and in-app access are outside the gateway's reach by design. Validate against your own compliance program before relying on it.

Policy source (Rego)

package monday.ingress.freeze_standing_automation

# Deny-by-default: the persistence/agent tools below are blocked unless the
# caller is in an allow-listed platform-admin group. Every tool this policy does
# not target is explicitly allowed through.
default allow := false

# ---------------------------------------------------------------------------
# Targeted tools.
#
# Standing automations / workflows — install rule-based side effects that keep
# firing on monday after this session ends.
automation_suffixes := [
    "create_automation",
    "manage_automations",
    "create_workflow",
    "update_workflow",
    "plan_workflow",
    "publish_workflow",
]

# Autonomous monday AI agents — created/configured here, then act on their own
# after this session ends. manage_agent is listed alongside its sub-tools; the
# separator-suffix match keeps them distinct (manage_agent does not match
# manage_agent_triggers and vice versa).
agent_suffixes := [
    "manage_agent",
    "manage_agent_triggers",
    "manage_agent_skills",
    "manage_agent_knowledge",
]

# ---------------------------------------------------------------------------
# Identity — placeholder platform-admin group, replace at import time. Read via
# object.get chains so a missing subject/claims/groups fails closed (not admin).

# Lowercased allow-listed group names (compared case-insensitively).
admin_groups := {"platform-admin"}

caller_groups := object.get(object.get(object.get(input, "subject", {}), "claims", {}), "groups", [])

# True when the caller holds an allow-listed group. Fails closed on any
# malformed groups claim: the claim must be an *array* (an object would let
# `some g in ...` iterate its values and spoof admin via a value like
# {"role":"platform-admin"}), and each element must be a string before it is
# lowercased and checked. A string/object/number groups claim, or an array with
# no matching string element, yields not-admin -> deny.
caller_is_admin if {
    is_array(caller_groups)
    some g in caller_groups
    is_string(g)
    admin_groups[lower(g)]
}

# ---------------------------------------------------------------------------
# Tool matching. The gateway prefixes tool names with the configured MCP server
# name (separator not standardized), so match the exact name or a `-`/`_`-
# separated suffix, case-insensitively. A non-string name is coerced to "" so
# lower() is never handed a non-string (which would raise a type error and leave
# allow/reason undefined).

raw_tool_name := object.get(object.get(input, "resource", {}), "name", "")

tool_name := lower(raw_tool_name) if is_string(raw_tool_name)

tool_name := "" if not is_string(raw_tool_name)

tool_matches(suffix) if {
    tool_name == suffix
}

tool_matches(suffix) if {
    endswith(tool_name, sprintf("-%s", [suffix]))
}

tool_matches(suffix) if {
    endswith(tool_name, sprintf("_%s", [suffix]))
}

is_automation_tool if {
    some suffix in automation_suffixes
    tool_matches(suffix)
}

is_agent_tool if {
    some suffix in agent_suffixes
    tool_matches(suffix)
}

is_persistence_tool if is_automation_tool

is_persistence_tool if is_agent_tool

# ---------------------------------------------------------------------------
# Allow rules.

# Any tool this policy does not target passes through untouched.
allow if {
    not is_persistence_tool
}

# A platform-admin (allow-listed group) may create automations / agents.
allow if {
    is_persistence_tool
    caller_is_admin
}

# ---------------------------------------------------------------------------
# Deny reasons.

reasons contains "This monday tool installs a standing automation or workflow that keeps running after this session ends, which per-call governance cannot see or stop, so the gateway blocks agent and prompt-driven callers from creating one. Have a human set up the automation directly in the monday UI instead. If you are a designated automation author, ask your admin to add your IdP group to this policy's allow-list." if {
    is_automation_tool
    not caller_is_admin
}

reasons contains "This monday tool manages an autonomous monday AI agent that keeps acting after this session ends, which per-call governance cannot see or stop, so the gateway blocks agent and prompt-driven callers from creating or configuring one. Have a human set up the agent directly in the monday UI instead. If you are a designated automation author, ask your admin to add your IdP group to this policy's allow-list." if {
    is_agent_tool
    not caller_is_admin
}

reason := joined if {
    count(reasons) > 0
    reason_list := sort([r | some r in reasons])
    joined := concat("; ", reason_list)
}

Canonical source: policy.md on GitHub · raw · raw on this site (.md)