# **Privacy Policy for Exmergo Dex**

*Last modified: August 16, 2026*

### **1. The Short Answer**

**Exmergo Dex runs on your machine, inside your own coding agent, using your own warehouse credentials. Exmergo receives nothing.**

There is no Dex account, no Exmergo server in the Dex path, no telemetry, no usage reporting, no licence check, and no update ping. Dex contains no code that sends anything to an Exmergo endpoint, because no such endpoint exists for it. Dex is [open source under Apache 2.0](https://github.com/exmergo/dex), so you do not have to take our word for any of this.

That is the whole claim, and the rest of this page is the detail behind it.

### **2. Where Your Data Actually Goes**

Dex is a toolkit your agent calls. Several parties can end up seeing something, and it matters which relationship each one sits in. This is the complete list.

| Destination | What reaches it | Whose relationship |
| --- | --- | --- |
| **Your data warehouse** | Read-only SQL that Dex generates or that your agent authors | Yours. Your credentials, your infrastructure, your vendor agreement. |
| **Your AI model provider**, through your agent | Schema, statistics, PII flags, and capped non-personal query results, as detailed in section 3 | Yours. Dex makes no model call of its own. |
| **dbt Cloud Semantic Layer**, only if you opt in | Metric and dimension names, group-by tokens, and `where` clauses passed through as SQL. Aggregated rows come back. | Yours. Your dbt Cloud account and token. |
| **dbt Labs**, during `dex transform build` | dbt-core's own anonymous usage statistics, sent by dbt itself | dbt Labs'. See section 6. |
| **Your Python package index**, typically PyPI | A package resolve and download for the pinned `exmergo-dex-core` release | The index operator's. |
| **Exmergo** | **Nothing.** | None exists. |

The only entry Dex introduces on its own is the last row, and it is empty.

### **3. What Crosses into Your Agent's Context**

This is the question a security review should actually ask, because whatever Dex hands your agent is then sent to whichever AI model provider that agent is configured with.

Every Dex command returns exactly one sanitized JSON envelope. Nothing else crosses that boundary.

**What can cross:**

*   **Structure.** Table and column names, data types, object types, row counts, and byte sizes.
*   **Statistics.** Null fractions, distinct counts, uniqueness and key findings, orphan fractions on inferred joins, and data-quality observations expressed as percentages.
*   **Minimum and maximum values, but only for columns that carry no personal-data flag and hold numeric, temporal, or boolean data.** A minimum of a text column is a raw value, and the minimum of a personal-data column is a sensitive one. Neither is ever computed.
*   **Value domains,** meaning up to 25 distinct values with their frequencies, and only for columns that carry no personal-data flag at any confidence, are not part of a key, and hold fewer distinct values than 10% of their rows. In practice this captures things like `status in (pending, shipped, cancelled)`. It cannot capture a high-cardinality or personal column.
*   **Query results,** only through the query firewall described in section 4, and capped at 50 rows, 256 characters per cell, and 16 KB per payload.
*   **Personal-data flags,** recorded strictly as a column name, a category, and a confidence score.

**What can never cross:**

*   **Credentials.** The envelope sanitizer scans every payload for secret-shaped keys and raises an error rather than quietly stripping them, so a leak fails the command instead of shipping silently.
*   **Personal data values.** A flag has no field for an example value, so there is nowhere to put one even by accident.
*   **Raw rows.** The sanitizer rejects any row-shaped payload outright. Query results are returned column-wise for exactly this reason.

### **4. How Personal Data Is Detected and Blocked**

Dex flags personal data **from column names and aggregate statistics, never by reading values.** Column names are matched against categories including email address, phone number, personal name, postal address, government identifier, financial identifier, credential, geographic coordinate, date of birth, and free text. Confidence is then adjusted using shape statistics computed inside SQL aggregates, so only numeric fractions ever leave your warehouse.

Evidence can raise or lower a confidence score. **It can never clear a flag.** The only way to clear one is an explicit human entry in your own `.dex/config.yml`, which is re-applied on every profiling run and leaves an audit trail.

Once a column is flagged at or above the blocking threshold, the query firewall enforces the following:

*   **Allowed over a flagged column:** measuring aggregates such as `COUNT`, `COUNT(DISTINCT ...)`, `AVG`, `SUM`, standard deviation, variance, and correlation. Their output is a statistic, not a value. `select count(distinct email) from customers` runs.
*   **Refused:** any row-level projection of a flagged column, and every value-carrying aggregate, including `MIN`, `MAX`, `ANY_VALUE`, `MODE`, percentiles, `STRING_AGG`, and `ARRAY_AGG`. `select email from customers` is refused.
*   **Unknown functions are treated as value-carrying,** so the gate fails closed rather than open.
*   **Reshaping does not launder anything.** Columns produced by `UNNEST`, `FLATTEN`, or `EXPLODE` inherit the flags of their inputs.
*   **An unprofiled table or column is refused,** because the firewall has no basis on which to allow it.
*   **Refusal messages carry no values.** They name the column and suggest unflagged alternatives, nothing more.

The blocking threshold is a fixed constant compiled into the engine. It is deliberately not configurable, so no one can widen the personal-data boundary with a one-line edit to a committed config file.

One consequence worth stating plainly: the firewall governs what a query **returns**, not what a query **filters on**. `WHERE` clauses, joins, and `GROUP BY` are unrestricted, because values flow into them rather than out of them. This matters for section 5.

### **5. What Dex Writes to Your Disk**

Dex stores its state in a `.dex/` directory in your project. **This directory is designed to be committed to your repository**, so it is worth knowing exactly what is in it.

| File | What it holds |
| --- | --- |
| `config.yml` | Connector targets, budgets, limits, and personal-data overrides. Non-secret by design. |
| `cache.json` | The exploration map: structure, statistics, personal-data flags, and the bounded values described in section 3. |
| `snapshot.json` | A frozen copy of the same, used as the baseline for drift detection. |
| `drift.json` | The last drift report: counts, identifiers, and severities. |
| `queries.jsonl` | An append-only decision ledger of every query attempt, storing **the SQL text verbatim**, along with the decision made. Never the results. |
| `spend.jsonl` | An append-only billing ledger: byte counts, durations, job identifiers, and **hashed** statements. Never SQL text, never values. |
| `plans/*.json` | Proposed dbt file changes awaiting your review. |

Two things follow from this, and we would rather state them than let you discover them:

1.  **The cache and the snapshot do contain some values derived from your warehouse**, specifically the minimums, maximums, and low-cardinality value domains described in section 3. They exclude anything flagged as personal data. It would be inaccurate to tell you that nothing from your warehouse is written to disk.
2.  **`queries.jsonl` records SQL verbatim, and `WHERE` clauses are not restricted by the firewall.** If your agent writes `select count(*) from users where email = 'someone@example.com'`, that literal value is in the ledger, and if `.dex/` is committed, it is in your version control history.

**No file in `.dex/` ever contains a credential.**

### **6. Third-Party Paths You Should Know About**

Three of the destinations in section 2 are not Exmergo's and are worth calling out on their own.

*   **Your AI model provider.** Dex never calls a model. Your agent does. Claude Code, or whichever agent you run, sends context to the provider you configured, under the agreement you have with them, governed by their privacy policy and their data-retention and training terms. Exmergo is not a party to that relationship and has no visibility into it. What Dex contributes to that context is bounded by section 3.
*   **dbt Labs.** `dex transform build` invokes dbt on your machine. dbt-core sends its own anonymous usage statistics to dbt Labs, and Dex does not disable them. That telemetry is dbt's, not ours, and it is governed by dbt Labs' privacy policy. You can turn it off in your own dbt configuration, in the usual way.
*   **Your package index.** The agent skills launch Dex through a pinned package specification, which resolves against whichever Python package index you have configured, usually PyPI. That request tells the index which version you fetched, in the same way any `pip install` does. It tells Exmergo nothing.

The **dbt Cloud Semantic Layer** backend is opt-in and off by default. When you enable it, metric names, dimension names, and filter expressions go to your own dbt Cloud host with your own token, and aggregated rows come back. Personal-data screening runs before the request is sent. This is also the one path where Dex's cost guard cannot apply, because execution happens on dbt Cloud rather than under Dex's control, and every result on that path says so.

### **7. Credentials**

Dex **discovers** credentials and never asks for them. It reads them from the chains you already use: Application Default Credentials for BigQuery, `connections.toml` or the Snowflake environment, the Databricks SDK chain, the AWS credential chain for Redshift, `pg_service.conf` or `DATABASE_URL` for Postgres, `CLICKHOUSE_URL` for ClickHouse, or an existing dbt profile.

A credential is held in memory for the duration of a connection and then discarded. It is never written to `.dex/`, never printed, never rendered into a generated dbt profile, which uses environment variable references instead, and it is stripped out of error messages before they are shown. The envelope sanitizer treats a credential crossing the output boundary as a failure rather than something to clean up.

### **8. Read-Only Against Your Data**

Dex is read-only against your warehouse, enforced independently at three levels: the connection is opened read-only where the connector supports it, a SQL guard refuses any statement that is not a single read-only `SELECT` anywhere in its parse tree, including one hidden inside a CTE, and the query firewall applies on top of both. If the component that validates SQL is unavailable, guarded commands refuse to run rather than fall back to something weaker.

Dex writes in exactly three places, none of them your data: the `.dex/` directory, dbt project files in your repository through a reviewable diff you approve, and dev-target builds, which refuse to run against a production target.

### **9. Our Role, Formally**

*   **For the data Dex reads from your warehouse, Exmergo is neither controller nor processor.** We do not determine the purposes or means of that processing, we do not carry it out on your behalf, and we could not access that data if asked. Dex is software you run, not a service we operate.
*   **For information you send us directly**, such as a support email, a GitHub issue, or a security report, Exmergo is the controller. That is covered by section 4 of the [main policy](/privacy).

**Sub-processors for Dex: none.** No data from Dex reaches Exmergo, so there is nothing to pass on.

Because we process no personal data on your behalf through Dex, a Data Processing Agreement covering Dex would have no subject matter. If your procurement process requires a written statement to that effect, write to legal@exmergo.com and we will provide one. If you also use Exmergo Viz, a DPA covering that service is available on the same address.

### **10. Verifying This Yourself**

Every claim on this page is a statement about code you can read. Dex is published under Apache 2.0 at [github.com/exmergo/dex](https://github.com/exmergo/dex), and the guarantees described here are enforced by tests in that repository rather than by convention.

If you find a way to violate any of them, we want to hear about it. Report privately through a [GitHub security advisory](https://github.com/exmergo/dex/security/advisories/new) or to security@exmergo.com. We aim to acknowledge within three business days. Please send a sanitized reproduction, never real credentials or real warehouse data.

### **11. Scope, Changes, and Contact**

This annex forms part of the [Exmergo Privacy Policy](/privacy) and is incorporated into it by reference. The main policy covers who we are, your rights, and how to contact us. Exmergo Viz is a hosted service with a different architecture and is covered by its own annex, the [Privacy Policy for Exmergo Viz](/privacy/viz).

If a future version of Dex introduces any path that sends data to Exmergo, we will update this annex before that version ships, and the change will be visible in the repository's history as well as here.

Questions go to support@exmergo.com. Contractual questions go to legal@exmergo.com. Security reports go to security@exmergo.com.
