--- name: Freeze Standing Automation & AI Agents in monday tags: - monday - constrain-aggregator - ingress - soc2 publishedAt: 2026-07-12 description: | # monday / freeze-standing-automation **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 / workflows** — `create_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 agents** — `manage_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 `` 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 ```jsonc { "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 ```jsonc { "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 ```jsonc { "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 ```jsonc { "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 `-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. direction: ingress apps: - monday industries: [] bundles: - soc2 experimental: true schemaVersion: 1.0.0 minimumGatewayVersion: 1.0.0b24 --- ```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) } ```