Block Irreversible Docusign Void and Workflow Kills
Denies the irreversible destructive operations on the Docusign agent path:
- Direction
- ingress
- Rego package
docusign.ingress.freeze_destructive_ops- App
- docusign
- Bundle
- soc2
- Published
- Minimum gateway
- 1.0.0b24
- Schema version
- 1.0.0
- Checksum
sha256:f6a8b10e38a3096a1f8dcd1b83a5d59f645b680015160eda7711e4d05effe476
docusignfreeze-destructive-opsingresssoc2
What this policy does
Direction: ingress (tool_pre_invoke)
Default: deny on match, allow otherwise
Package: docusign.ingress.freeze_destructive_ops
What it does
Denies the irreversible destructive operations on the Docusign agent path:
updateEnvelopevoid attempts — any call whose body carriesstatus: "voided", or that carries avoidedReasonfield at all (that field only exists on void requests, so its presence signals void intent even when thestatuskey is missing or obfuscated). Voiding a sent envelope permanently invalidates a legally significant signed record. Non-voidupdateEnvelopecalls (sending a draft, editing the email subject or blurb) pass through.cancelWorkflowInstance— kills a running Maestro workflow instance. Denied outright.pauseNewWorkflowInstances— suspends a workflow for the whole account. The blast radius is far beyond any single agent task. Denied outright.
Callers whose input.subject.claims.groups include contract-ops are exempt from all three blocks, so a designated remediation team can still void an erroneous envelope or stop a runaway workflow through the agent path. Everyone else — including callers with no identity claims at all — is denied (the exemption fails closed).
All other tool calls, on Docusign or any other server behind the same gateway, pass through unchanged.
Compliance alignment
- SOC 2 PI1.5 — supports integrity of stored records: signed envelopes and running workflow state survive agent error or prompt injection. A voided envelope is a destroyed record of an executed agreement, and a killed or account-wide-paused workflow disrupts the state that keeps those records accurate; blocking agent-initiated voids and workflow kills keeps them intact on the MCP path.
Why ingress and not egress
Voiding an envelope and killing a workflow instance are irreversible server-side state changes. Once the call reaches Docusign the record is invalidated and counterparties may already have been notified. Egress inspection would only see the confirmation; ingress denial is the only placement that actually prevents the destruction.
Tool name matching
The policy matches tool names by suffix, case-insensitively, on input.resource.name:
*updateenvelope— the official server'supdateEnvelope(Envelopes:update). Suffix matching deliberately does not catchupdateEnvelopeRecipients, which is a different tool with a different risk profile (see Composition).*cancelworkflowinstance— the official server'scancelWorkflowInstance.*pausenewworkflowinstances— the official server'spauseNewWorkflowInstances.
The DTwo gateway prefixes tool names with the configured MCP server name (e.g. docusign-updateEnvelope for a server named docusign), and that prefix is not standardized, so the policy matches suffixes to stay portable. Verify the exact names your gateway sends with the dump-input debug technique before relying on this in production.
These names come from the official Docusign MCP Server catalog (camelCase, verified against the developer docs as of 2026-02). The community luthersystems/mcp-server-docusign server exposes no void or workflow tools, so it has no surface for this policy to guard.
Argument shape
updateEnvelope mirrors the eSignature Envelopes:update REST body: envelopeId plus body fields such as status ("voided" to void, "sent" to send a draft), voidedReason, emailSubject, emailBlurb. The policy anchors on two signals, both matched case-insensitively against the top-level keys of input.payload.args:
- a
statuskey whose value (trimmed, lowercased) isvoided; - the presence of a
voidedReasonkey with any value, including empty — a legitimate void requires a non-emptyvoidedReason, so a request carrying that key is a void attempt regardless of what thestatusfield says.
The workflow tools are denied by name alone; their arguments are not inspected.
Examples
Allowed — sending a draft envelope
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "docusign-updateEnvelope", "type": "tool" },
"payload": {
"name": "docusign-updateEnvelope",
"args": { "envelopeId": "abc-123", "status": "sent" }
}
}
}
allow = true, no reason — not a void attempt.
Denied — voiding a sent envelope
{
"input": {
"action": "tool_pre_invoke",
"resource": { "name": "docusign-updateEnvelope", "type": "tool" },
"payload": {
"name": "docusign-updateEnvelope",
"args": { "envelopeId": "abc-123", "status": "voided", "voidedReason": "sent to wrong signer" }
},
"subject": { "sub": "auth0|agent", "claims": { "groups": ["engineering"] } }
}
}
allow = false, reason = "Voiding a Docusign envelope permanently invalidates a legally significant signed record...".
Allowed — contract-ops remediation
The same void request with "claims": { "groups": ["contract-ops"] } is allowed.
Composition
This policy is single-purpose. Useful companions on the Docusign path:
- An ingress transform that rewrites
createEnvelopestatus: "sent"to"created"so agents prepare drafts and humans dispatch them. - An ingress deny on
updateEnvelopeRecipients/ envelope creation when a signer email is outside your counterparty allowlist. - An ingress allowlist for
triggerWorkflowpinned to approved workflow IDs. - An egress redaction policy on
listRecipients/getEnvelope/getAgreementDetailsfor tab values and extracted terms.
Known limitations
- Group names are placeholders — replace
contract-opswith your IdP's group name at import time. The exemption readsinput.subject.claims.groupsand expects an array; a missing, empty, or string-valuedgroupsclaim means no exemption (fail closed). - Official-server argument shapes are inferred from the mapped REST endpoints, not an MCP schema dump — Docusign does not publish per-tool JSON schemas on a static page. Verify against a live
tools/listor the dump-input technique before relying on exact field names. - Only top-level
argskeys are inspected for void signals. If your MCP server nests the envelope body under a wrapper key (none is documented for the official server), a void could evade the argument check — the two workflow tools are still blocked by name. Extendis_void_attemptif you observe a nested shape. (Covered by a test asserting the current pass-through behaviour.) - Void signals assume string-typed values. Signal 1 fires only when the
statusvalue equalsvoidedafter lowercasing and trimming, so a non-string value (e.g.status: ["voided"]) or a non-objectargspayload evades Signal 1. This is not an exploitable bypass: the Docusign eSignature REST body requiresstatusto be a string, so a malformed shape is rejected server-side and no void occurs, and any well-formed void must carry a non-emptyvoidedReasonthat Signal 2 detects by key presence regardless of thestatusvalue's type. If a future server variant coerces such shapes into a real void, broaden Signal 1 accordingly. (Covered by a test asserting the current pass-through behaviour.) - Void is the only destructive
updateEnvelopeshape this policy detects. The mapped Envelopes:update endpoint also accepts a document-retentionpurgeStatefield (e.g.documents_and_metadata_queued) that irreversibly purges envelope documents — an irreversible destruction that also falls under the PF-06 anti-destruction scope this policy supports. That field is not listed in the verified Docusign landscape note'supdateEnvelopeargument shape, so this policy does not key on it (marking it unverified rather than inventing enforcement): apurgeStatepurge carrying nostatus: "voided"and novoidedReasoncurrently passes through. If a livetools/listconfirmsupdateEnvelopeexposespurgeState, add a third void-detection signal that denies its presence. (Covered by a test asserting the current pass-through behaviour.) - Non-void
updateEnvelopeedits pass through, includingstatus: "sent"(dispatching a draft emails real recipients). If that is too permissive for your environment, pair with the force-drafts companion above. - Web UI, native API, Connect webhooks, and admin console are out of reach — this policy governs only the MCP agent path.
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 docusign.ingress.freeze_destructive_ops
# Deny-by-default: only the explicit allow rules below permit the request.
default allow := false
# --- Tool matching ---
# The gateway prefixes tool names with the configured MCP server name
# (e.g. `docusign-updateEnvelope`), so we match on the suffix to stay
# portable. Suffix matching deliberately excludes `updateEnvelopeRecipients`,
# which is a different tool. Verify exact names on your gateway with the
# dump-input debug technique before relying on this in production.
is_update_envelope if {
endswith(lower(input.resource.name), "updateenvelope")
}
is_cancel_workflow if {
endswith(lower(input.resource.name), "cancelworkflowinstance")
}
is_pause_workflows if {
endswith(lower(input.resource.name), "pausenewworkflowinstances")
}
is_guarded_tool if is_update_envelope
is_guarded_tool if is_cancel_workflow
is_guarded_tool if is_pause_workflows
# --- Identity exemption ---
# `contract-ops` is a placeholder group name — map it to your IdP group at
# import time. Missing subject/claims/groups fails closed: no group, no exemption.
caller_groups := object.get(object.get(object.get(input, "subject", {}), "claims", {}), "groups", [])
is_contract_ops if {
some g in caller_groups
lower(sprintf("%v", [g])) == "contract-ops"
}
# --- Void detection ---
# Top-level tool arguments; keys are matched case-insensitively below.
args := object.get(object.get(input, "payload", {}), "args", {})
# Signal 1: an explicit `status: "voided"` body (the documented Envelopes:update
# void shape), tolerant of key/value casing and stray whitespace.
is_void_attempt if {
some k in object.keys(args)
lower(k) == "status"
lower(trim_space(sprintf("%v", [args[k]]))) == "voided"
}
# Signal 2: a `voidedReason` key with any value (including empty). That field
# only exists on void requests, so its presence marks void intent even when
# the status key is missing or obfuscated.
is_void_attempt if {
some k in object.keys(args)
lower(k) == "voidedreason"
}
# --- Allow rules ---
# Pass through any tool this policy does not guard.
allow if {
not is_guarded_tool
}
# contract-ops members may perform legitimate remediation.
allow if {
is_guarded_tool
is_contract_ops
}
# Non-void updateEnvelope calls (send draft, subject/blurb edits) pass through.
allow if {
is_update_envelope
not is_void_attempt
}
# --- Deny reasons ---
reasons contains "Voiding a Docusign envelope permanently invalidates a legally significant signed record, so agent-initiated voids are blocked. Prepare a correcting envelope instead, or ask a member of the contract-ops group to perform the void. Contact your administrator if this block looks wrong." if {
is_update_envelope
is_void_attempt
not is_contract_ops
}
reasons contains "Cancelling a running Docusign Maestro workflow instance is irreversible, so agent-initiated cancellations are blocked. Ask a member of the contract-ops group to cancel the instance from the Docusign console. Contact your administrator if this block looks wrong." if {
is_cancel_workflow
not is_contract_ops
}
reasons contains "Pausing new Docusign Maestro workflow instances suspends the workflow account-wide, so agent-initiated pauses are blocked. Ask a member of the contract-ops group to pause the workflow from the Docusign console. Contact your administrator if this block looks wrong." if {
is_pause_workflows
not is_contract_ops
}
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)
Related policies
Airtable: Redact PII in Record Reads
Scans the responses of the Airtable record-read tools — the calls that return row fields values — and rewrites high-confidence PII shapes to a fixed…
Asana: Redact PII in Task & Comment Reads
On the Asana MCP read path, this transform scans the free-text business fields that ride back in task, comment/story, and status-update responses — notes,…
BigQuery: Redact PII in Query Results
Scans the content returned by BigQuery's result-returning tools and rewrites high-confidence PII shapes to fixed, non-recoverable redaction tokens before the…
Block Agent Email to External Recipients
Blocks agent-initiated Microsoft 365 email sends when any recipient address falls outside a corporate-domain allowlist.
Block BigQuery Exfiltration and Cross-Project Writes
Inspects the raw GoogleSQL string carried by BigQuery SQL tools and denies any statement that moves data out of the tenant's own project — even when the call…
bigqueryguard-warehouse-exportingresssqlexfiltrationsoc2pci-dssgdpr-ccpa
Block Bulk Export & External Staging (Snowflake)
Blocks Snowflake SQL-execution tool calls whose query text moves whole tables off the Snowflake perimeter — bulk export to cloud storage or a stage, and…
snowflakeguard-warehouse-sqlexportexfiltrationingresssoc2pci-dssgdpr-ccpa