HubSpot Block Deal Closure
Blocks HubSpot CRM-object calls that move a deal into a closed stage (closedwon or closedlost). Both create and update requests are inspected.
- Direction
- ingress
- Rego package
hubspot.ingress.no_close_deal- App
- hubspot
- Bundle
- crm
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:610b7a15cb9dfc53c0eef487b0bd786f47de31f4528493d2697b77913e50ea5e
hubspotdealsaccess-controlgovernanceingress
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny on match, allow otherwise
Package: hubspot.ingress.no_close_deal
What it does
Blocks HubSpot CRM-object calls that move a deal into a closed stage
(closedwon or closedlost). Both create and update requests are inspected.
Every other deal change — and every other HubSpot tool — passes through
unchanged.
Why ingress
Closing a deal is a write with permanent side effects on the CRM (revenue reporting, workflow automation, downstream syncs). The violation is fully determined by the request payload, so denying at ingress prevents the stage change from ever reaching HubSpot.
How it matches
Two conditions must both hold for a call to be denied:
- Tool match. The (lowercased) tool name ends with
-manage-crm-objects— the HubSpot MCP tool that creates and updates CRM records. Suffix matching keeps the policy portable regardless of the MCP server name the gateway adds as a prefix. - Closing a deal. Within the call's
createRequest.objectsorupdateRequest.objects, an object whoseobjectTypeisdealssetsproperties.dealstagetoclosedwonorclosedlost(case-insensitive).
Tool naming on the gateway
DTwo prefixes tool names with the MCP server name configured on the gateway, so
a HubSpot server registered as hubspot surfaces hubspot-manage-crm-objects
while one registered as hubspot-mcp surfaces hubspot-mcp-manage-crm-objects.
This policy matches on the suffix (-manage-crm-objects) so it stays
portable across naming conventions. Confirm the exact tool name with the
dump-input debug technique before deploying.
Configuring closed stages
The blocked stages live in closed_stages at the top of the Rego
(closedwon, closedlost). If your pipeline uses custom closed-stage internal
names, add them there.
Examples
Allowed (non-closing update)
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "hubspot-manage-crm-objects", "type": "tool" },
"payload": {
"name": "hubspot-manage-crm-objects",
"args": {
"updateRequest": {
"objects": [
{ "objectType": "deals", "id": "12345",
"properties": { "dealstage": "qualifiedtobuy" } }
]
}
}
}
}
}
allow = true, no reason.
Denied (closing a deal)
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "hubspot-manage-crm-objects", "type": "tool" },
"payload": {
"name": "hubspot-manage-crm-objects",
"args": {
"updateRequest": {
"objects": [
{ "objectType": "deals", "id": "12345",
"properties": { "dealstage": "closedwon" } }
]
}
}
}
}
}
allow = false, reason = "Closing deals is not allowed. Contact your InfoSec team to get access.".
Known limitations
- Suffix tool-name match. The policy matches any tool ending in
-manage-crm-objects. If a non-HubSpot MCP server happened to expose a tool with that same suffix, it would also be inspected — narrow the match if that is a concern in your environment. - Custom stage names. Only
closedwon/closedlostare blocked by default; custom closed-stage internal names must be added toclosed_stages. - No identity-based exemptions. All callers are treated the same. To allow a
break-glass role to close deals, add an
allow ifbranch gated oninput.subject.claims.
Policy source (Rego)
package hubspot.ingress.no_close_deal
default allow := false
closed_stages := {"closedwon", "closedlost"}
allow if {
not is_closing_deal
}
is_closing_deal if {
endswith(lower(input.resource.name), "-manage-crm-objects")
some obj in object.get(object.get(input.payload.args, "updateRequest", {}), "objects", [])
lower(object.get(obj, "objectType", "")) == "deals"
stage := lower(object.get(object.get(obj, "properties", {}), "dealstage", ""))
closed_stages[stage]
}
is_closing_deal if {
endswith(lower(input.resource.name), "-manage-crm-objects")
some obj in object.get(object.get(input.payload.args, "createRequest", {}), "objects", [])
lower(object.get(obj, "objectType", "")) == "deals"
stage := lower(object.get(object.get(obj, "properties", {}), "dealstage", ""))
closed_stages[stage]
}
reasons contains "Closing deals is not allowed. Contact your InfoSec team to get access." if {
is_closing_deal
}
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)
Used in these guides
Related policies
HubSpot Protect Associations
Blocks HubSpot CRM-object calls that create or change associations between objects (deal↔company, contact↔company, etc.).
HubSpot Protect Deal Owner
Blocks HubSpot CRM-object update calls that set or change a deal's owner.
HubSpot Protect Lifecycle Stage
Blocks HubSpot CRM-object calls that set or change a contact's lifecycle stage.
HubSpot Read-Only
Makes the HubSpot connection read-only by blocking the write tool.
Salesforce Protect Contact Fields
Blocks Salesforce Contact updates that modify protected fields — ownership, account linkage, contact PII, name, and consent flags.