http lane fronts both.
Clients use the Sidecar’s own name in place of the workspace host. No DNS change. The Sidecar reads the SQL inside each request body, so a guardrail refuses a DROP before the warehouse runs it. Every statement from an AI agent on the SQL MCP server, from an application on the Statement Execution API, or from a developer with curl or the Databricks SDK then passes through your guardrails, masking rules and audit trail.
Requirements
- A Databricks workspace with a SQL warehouse. A free trial works.
- A personal access token. OAuth through the Sidecar’s name is not tested yet.
- A Sidecar token. The Sidecar runs logged in: sign in to use.hoop.dev, open Sidecars, choose Connect an existing Sidecar, and copy the token. A free use.hoop.dev organization holds one Sidecar.
- Docker on a host with outbound HTTPS to the workspace host and to
use.hoop.dev. - A certificate authority your clients trust, and
opensslto issue a certificate from it. - A client:
curl, the Databricks SDK, or an MCP client that speaks Streamable HTTP. - A Sidecar release with the
http.sqlblock. An older one refuses the config withunknown field "sql".
Setup
1
Find the workspace host and the warehouse ID
The workspace host is the host of your workspace URL, for example
dbc-a1b2c3d4-e5f6.cloud.databricks.com. In SQL Warehouses, open your warehouse and its Connection details tab. The warehouse ID is the last segment of the HTTP path, /sql/1.0/warehouses/<warehouse-id>.2
Issue a certificate for the Sidecar's name
Clients call the Sidecar by its own name, for example Python 3.13 and later refuse a certificate without
databricks.sidecar.internal, so it presents a certificate for that name, issued by a CA your clients trust. With a private CA:keyUsage on the CA or authorityKeyIdentifier on the leaf, so keep both lines.3
Write the config
Save this as
config.yaml next to the certs folder. Replace <workspace-host> with yours.config.yaml
listenandupstreamas URLs: clients dialhttps://<sidecar-host>:8443, and the Sidecar sends each request to the workspace host with that host’s name, over verified TLS.http.sqlreads the SQL out of each request body.fieldsmaps a route to the JSON path of its SQL:statementon the Statement Execution API, thequeryargument of atools/callon the SQL MCP server.toolsnames the MCP tool that runs SQL.dialect: sparkreads the SQL as Databricks SQL.refuserefuses a request that asks for external links, withrefuse_message, whichrefuserequires. Those results come from cloud storage, so the rows would skip masking and the audit. The enum number ("disposition": 2) is refused too.- The guardrail refuses the writes it names, and every statement the Sidecar cannot read (
unknown).COMMENT ON,ANALYZE TABLEandREFRESHareotherand pass; namingotherrefuses them, andUSE CATALOGwith them. maskreplaces email addresses in JSON results.auditwrites every request and statement to stdout as JSON lines, andquery_sessionsturns on the/api/sessionsendpoint.adminserves it with no authentication, so the next step publishes it on loopback only.
4
Validate and start the Sidecar
--validate prints config OK: 1 listener(s) and limits: 1 guardrail rule(s), 1 data masking rule(s); it needs no token. Versions are on the releases page.The token logs the Sidecar in to your use.hoop.dev organization, and the lane runs this config.yaml. Without a token or a license the Sidecar waits for a person to approve a login code, which it prints in docker logs hoop-databricks.5
Point your clients at the Sidecar
Use
https://<sidecar-host>:8443 where the client had the workspace URL, and trust ca.crt:The token stays the same:
Authorization: Bearer <token>.Verify
Create a table with email addresses in the SQL editor:DROP is refused before it reaches the warehouse, and the table is still there:
DROP as an execute_sql call on the SQL MCP server gets the same 403. A request for external links is refused with your message:
POST /api/2.0/sql/statements, then DROP TABLE <catalog>.default.customers with operation: drop and the table it names.
Allow only the Sidecar
Without a network rule, a client that calls the workspace host directly skips the Sidecar. Databricks has two front-end controls; both are enforced together:- Context-based ingress, in the account console. Allow the API access type only from the Sidecar’s egress IP, and keep the Workspace UI rule for your users. Enterprise tier on AWS and GCP, Premium on Azure.
- IP access lists, per workspace. Add the Sidecar’s egress IP to an allow list. This list covers the UI too, so add your users’ IPs as well. Enterprise tier on AWS, Premium on Azure and GCP. Public IPv4 addresses only.
Behavior to know
- Thrift drivers are not read. JDBC, ODBC and the Python, Go and Node.js SQL connectors speak Thrift by default. Their SQL is not read and their results are not masked. Cloud Fetch rows come from cloud storage directly.
- External links skip the Sidecar. With
EXTERNAL_LINKS, the API answers with links to cloud storage.refuseturns those requests away, so clients must ask forINLINE. - Large results are refused, not leaked. A JSON result the Sidecar cannot mask is refused with a 403: over 8 MiB as sent, or over 16 MiB once a gzip body is inflated. Databricks allows up to 25 MiB inline; use
row_limitorbyte_limitto stay under the Sidecar’s limit. - Long statements need a larger capture. The Sidecar reads at most
max_body_bytesof a request body, 64 KiB by default, and refuses a longer one. Raisehttp.max_body_bytesfor long SQL. - What
unknownrefuses besides writes. A SQL script (BEGIN ... END),EXECUTE IMMEDIATE, and any statement that holds${.VACUUM t DRY RUNandCOPY INTO t ... VALIDATEare refused as the write they preview. A read that names a column like a write verb afterTHENreads as a write. IDENTIFIER(:t)hides the table from table rules. The operation is still read.- A denial is an HTTP 403 with a text body.
curland MCP clients show the message. The Databricks SDK for Python raisesPermissionDeniedwithunable to parse response, because it expects a JSON error body. - The audit stores the bodies of the SQL routes. That is the SQL and the warehouse ID, never the token. Requests to other APIs keep no body. Keep the admin port on loopback all the same.
- The principal reads
anonymous. The lane does not resolve the token to a Databricks user yet. - Reviews are not available on this lane yet. A lane with
http.sqlcannot set an analyzer that holds for approval.
Free and Enterprise
Free tier: one Data Masking rule and one Guardrail per Sidecar are free, forever. Running more than one rule per feature, or managing rules centrally across Sidecars, requires Enterprise.
- The config fits the free tier. It uses one guardrail rule,
read-only, and one masking rule,emails. One rule can carry more than one check:read-onlynames ten operations. - The license comes from use.hoop.dev. With
HOOP_SIDECAR_TOKEN, the Sidecar takes your use.hoop.dev organization’s license on each heartbeat. A free organization holds one Sidecar. An Enterprise license with the guardrails and data-masking features lifts the one-rule caps. Your organization can hold it, or you pass it with-e HOOP_LICENSE="$(cat license.json)"instead of the token. See Licensing. - Approvals and central management: the Control Plane adds approvals, shared rule sets and one view of many Sidecars. It also needs an Enterprise license.
Next
Data Masking
Choose entity types, columns and masking strategies.
Policy rules
Guardrail rule types, and OPA for everything they cannot express.
HTTP
What the http lane captures, masks and terminates.
Config File Reference
Every section and field of
config.yaml.