---
name: "Databricks: Role-Gate Compute & Job Control"
tags:
  - databricks
  - role-gate-writes
  - access-control
  - least-privilege
  - ingress
  - soc2
publishedAt: 2026-07-12
description: |
  # databricks / role-gate-compute-ops

  **Direction:** ingress (`tool_pre_invoke`)
  **Default:** deny the compute/job-control tools unless the caller is in the platform group; allow everything else (including read-only inventory tools)
  **Package:** `databricks.ingress.role_gate_compute_ops`

  ## What it does

  The community `JustTryAI/databricks-mcp-server` exposes cluster and job control — `create_cluster`,
  `start_cluster`, `terminate_cluster`, `run_job`, and `export_notebook` — under a single Databricks
  PAT (`DATABRICKS_TOKEN`) that bypasses per-user Unity Catalog identity entirely: the token's
  privileges, not the calling user's, gate everything upstream. This policy re-imposes least privilege
  at the gateway by denying those five tools (matched by suffix) for any caller whose IdP `groups`
  claim does **not** include the placeholder group `platform-engineering`.

  Each gated tool is a distinct risk: `terminate_cluster` kills shared compute (availability impact),
  `run_job` fires pipelines with external side effects, `create_cluster` spends money, and
  `export_notebook` exfiltrates source code that frequently embeds credentials. Analyst and Cowork
  users have no business driving compute over the agent channel, so this enforces least privilege on
  that surface.

  Read-only inventory tools — `list_clusters`, `get_cluster`, `list_jobs`, `list_notebooks`,
  `list_files` — are unaffected and continue to pass through for everyone, as does any other tool that
  is not one of the five gated compute/job verbs.

  The check runs at ingress, before the call reaches the Databricks MCP server, so a denied compute
  action never executes and has no side effects (no cluster spun up, no job fired, no notebook
  exported).

  ## Compliance alignment

  - **SOC 2 CC6.1** — supports logical access security over protected assets: shared Databricks
    compute and job pipelines cannot be driven over the agent channel without an explicit role grant.
    **CC6.3** — supports role-based access and least privilege (and the initiate/approve separation
    of duties behind it): compute/job-control capability is tied to a live IdP group, and removing the
    group in the IdP removes the capability on the next call.
  - **SOX ITGC (access to programs and data)** — supports least-privilege access to the compute and
    job-orchestration plane where Databricks runs financially relevant pipelines: starting, creating,
    or terminating clusters and firing jobs requires membership in a controlled group, and the
    initiate-vs-approve separation of duties (COSO Principle 10) it backs is enforced on the MCP path.

  ## Tool name matching

  The DTwo gateway prefixes tool names with the configured MCP server name (e.g.
  `databricks-terminate_cluster`), and the prefix is not standardized — so matching is
  case-insensitive (`lower(...)`) and by **suffix** (`endswith`) to stay portable across server names.
  The five gated suffixes are the verified `JustTryAI` write/destructive tool names:

  - `create_cluster`, `start_cluster`, `terminate_cluster` (compute control)
  - `run_job` (job control)
  - `export_notebook` (source-code exfiltration)

  Suffix matching is deliberately conservative: the read-only inventory tools do **not** match any
  gated suffix — `list_clusters` / `get_cluster` do not end in `create_cluster` / `start_cluster` /
  `terminate_cluster`, `list_jobs` does not end in `run_job` (plural), and `list_notebooks` does not
  end in `export_notebook`. A hypothetical `restart_cluster` *would* match `start_cluster` and be
  gated too — an intentional, safe over-match (a restart is still a compute-control action).

  Verify the exact names your gateway sends with the dump-input debug technique before relying on this
  in production; if your Databricks server exposes an additional compute/job-control tool, add its
  suffix to `compute_op_suffixes` in `policy.md`.

  ## Argument shape

  The decision uses only the tool name (`input.resource.name`) and the caller's identity
  (`input.subject.claims.groups`). Tool arguments are not inspected, so the policy cannot be bypassed
  by unusual argument keys, nesting, or encodings — and it works identically whether or not a tool's
  argument schema is documented.

  ## Identity

  Group membership is read fail-closed via
  `object.get(object.get(object.get(input, "subject", {}), "claims", {}), "groups", [])`: a missing
  subject, missing claims, a missing `groups` claim, or a `groups` claim that is not an array all mean
  "not in the platform group", and every gated compute call is denied. Reads (and all non-gated tools)
  are unaffected by identity. The `is_array` guard is load-bearing — `some group in caller_groups`
  iterates the *values* of an object, so a `groups` claim shaped as `{"role": "platform-engineering"}`
  would otherwise match and fail **open**; requiring an array keeps every non-array shape (object,
  string, number) fail-closed.

  ## Examples

  ### Allowed — read-only inventory tool, no identity required

  ```jsonc
  {
    "input": {
      "action": "tool_pre_invoke",
      "resource": { "name": "databricks-list_clusters", "type": "tool" },
      "payload": { "name": "databricks-list_clusters", "args": {} }
    }
  }
  ```

  `allow = true`, no reason.

  ### Allowed — compute tool by a platform engineer

  ```jsonc
  {
    "input": {
      "action": "tool_pre_invoke",
      "resource": { "name": "databricks-terminate_cluster", "type": "tool" },
      "subject": { "sub": "auth0|alice", "claims": { "groups": ["platform-engineering"] } },
      "payload": { "name": "databricks-terminate_cluster", "args": { "cluster_id": "0921-abc" } }
    }
  }
  ```

  `allow = true`, no reason.

  ### Denied — compute tool, caller not in the platform group

  ```jsonc
  {
    "input": {
      "action": "tool_pre_invoke",
      "resource": { "name": "databricks-run_job", "type": "tool" },
      "subject": { "sub": "auth0|analyst", "claims": { "groups": ["analyst"] } },
      "payload": { "name": "databricks-run_job", "args": { "job_id": 42 } }
    }
  }
  ```

  `allow = false`, `reason = "Databricks compute and job-control tools ... are restricted to members of the 'platform-engineering' group ..."`.

  ## Composition

  This policy gates *who* may drive Databricks compute; it does not inspect *what* the job or cluster
  does. Useful companions:

  - [`apps/databricks/guard-warehouse-sql`](../guard-warehouse-sql/policy.md) — denies DML/DDL/GRANT
    and export constructs in SQL arguments to the `execute_sql*` tools, a separate surface this policy
    does not touch.
  - [`apps/databricks/default-deny-unknown-tools`](../default-deny-unknown-tools/policy.md) — allowlists
    audited tool names so a *renamed* or *new* upstream compute tool that this policy's suffix list
    does not yet cover fails closed instead of slipping through.
  - An egress PII/PHI redaction policy on the read path
    ([`apps/databricks/redact-pii-egress`](../redact-pii-egress/policy.md)), since this policy leaves
    the inventory/read tools open to everyone.

  ## Known limitations

  - **Group names are placeholders** — replace `platform-engineering` with your IdP's group name at
    import time. The policy expects `groups` to be an array claim in the caller's JWT; if your IdP
    emits roles under a different or namespaced claim (e.g. `https://acme.com/groups`), update
    `caller_groups` in `policy.md`.
  - **PAT bypass is upstream, not fixed here.** The underlying risk — the `JustTryAI` server acting
    under a shared PAT that bypasses per-user Unity Catalog identity — is not removed by this policy;
    it is *fenced* at the gateway. Any path to that PAT that does not traverse the gateway (a local
    stdio client, a direct API call) is out of scope by design.
  - **Suffix list is scoped to the verified `JustTryAI` names.** Other Databricks MCP servers name
    their compute/job tools differently (or, like the managed servers, do not expose cluster/job
    control at all). A compute tool whose name ends in none of the five gated suffixes slips through
    as "not a compute op" — pair with `default-deny-unknown-tools` if your deployment is
    allowlist-first, and add any additional verified suffixes to `compute_op_suffixes`. Concretely,
    the maximalist `pramodbhatofficial/databricks-mcp-server` (~263 SDK-generated tools) names its
    verbs noun-first — e.g. `clusters_create`, `clusters_delete`, `jobs_run_now` — and **none** of
    those end in a gated suffix, so this policy allows them for everyone (`clusters_create` does not
    end in `create_cluster`, `jobs_run_now` does not end in `run_job`). This fail-open residual is
    codified in `tests.yaml` (the `clusters_create` / `jobs_run_now` cases). On any server that does
    not use the `JustTryAI` verb-first names, this policy must be paired with
    `default-deny-unknown-tools` (allowlist-first) or have its `compute_op_suffixes` extended with the
    verified names your gateway actually emits — do not rely on suffix matching alone.
  - **A server named with a gated verb** (e.g. an MCP server configured as `run_job-runner`) could
    make unrelated tools match a suffix and require the platform group — a fail-closed false positive;
    rename the server or narrow the suffix.
  - **Reads are open to everyone**, including `export`-adjacent inventory (`list_notebooks`) and
    content-returning reads. This policy only gates the five write/destructive verbs; add a read fence
    or egress redaction if your Databricks workspace holds regulated content.

  > **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:
  - databricks
industries: []
bundles:
  - soc2
experimental: true
schemaVersion: 1.0.0
minimumGatewayVersion: 1.0.0b24
---

```rego
package databricks.ingress.role_gate_compute_ops

# Deny-by-default: the compute/job-control tools require the platform group;
# every other tool (including read-only inventory tools) is explicitly allowed
# by the `not is_compute_op` branch below.
default allow := false

# Placeholder IdP group permitted to drive Databricks compute and job control.
# Replace "platform-engineering" with your IdP's group name at import time.
compute_group := "platform-engineering"

# Lowercased tool name. The gateway prefixes tool names with the configured MCP
# server name (e.g. `databricks-terminate_cluster`), so matching below is
# case-insensitive and suffix based to stay portable across server names.
tool_name := lower(object.get(object.get(input, "resource", {}), "name", ""))

# --- Identity (fail closed) ---
# Missing subject, missing claims, a missing groups claim, or a groups claim
# that is not an array all yield "not in the platform group" — gated compute
# calls then deny.
caller_groups := object.get(object.get(object.get(input, "subject", {}), "claims", {}), "groups", [])

# The `is_array` guard is load-bearing: `some group in caller_groups` iterates
# the *values* of an object, so a groups claim shaped as
# `{"role": "platform-engineering"}` would otherwise match and fail OPEN.
# Requiring an array keeps every non-array shape (object, string, number)
# fail-closed, as the Identity section promises.
caller_is_platform_eng if {
    is_array(caller_groups)
    some group in caller_groups
    group == compute_group
}

# --- Compute / job-control tools (the deny surface) ---
# Verified `JustTryAI/databricks-mcp-server` write/destructive tool names,
# matched by suffix so any gateway server-name prefix still matches:
#   terminate_cluster -> kills shared compute (availability impact)
#   run_job           -> fires pipelines with external side effects
#   create_cluster    -> spends money
#   start_cluster     -> brings compute online
#   export_notebook   -> exfiltrates source that often embeds credentials
compute_op_suffixes := [
    "create_cluster",
    "start_cluster",
    "terminate_cluster",
    "run_job",
    "export_notebook",
]

is_compute_op if {
    some suffix in compute_op_suffixes
    endswith(tool_name, suffix)
}

# --- Decision ---

# Reads and anything that is not one of the five gated compute/job verbs pass
# for everyone.
allow if {
    not is_compute_op
}

# Gated compute/job tools pass only for members of the platform group.
allow if {
    is_compute_op
    caller_is_platform_eng
}

reasons contains msg if {
    is_compute_op
    not caller_is_platform_eng
    msg := sprintf("Databricks compute and job-control tools (create/start/terminate cluster, run job, export notebook) are restricted to members of the '%s' group — this account has read-only Databricks access through the gateway. Inventory tools (list_clusters, get_cluster, list_jobs, list_notebooks, list_files) still work. Ask your identity admin to add you to '%s', or hand this step to a platform engineer. If this tool is actually read-only, contact your InfoSec team to update the policy.", [compute_group, compute_group])
}

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