---
title: Warehouse credentials
description: Where the credential you enter is stored, what can read it, and exactly how much of read-only Dashies enforces on each warehouse engine.
updated: 2026-08-05
---

Connecting a warehouse hands Dashies a credential that reaches your own data. It
is the most sensitive thing you give us, and it is handled differently from
everything else.

## The path a credential takes

You type it into the connect form **in the Dashies web app**. That is the only
way in.

From there it goes straight to storage:

- It is posted over HTTPS from the form to a Dashies server route.
- It is written into **Supabase Vault**, encrypted at rest.
- The connection's own record stores a **reference** to that vault entry, never
  the credential itself. Everything visible on the record is non-secret metadata:
  a label, the engine, a host, a status.

Nothing later reads it back out to a person or a client. There is no screen that
displays a stored credential, no API that returns one, and no support flow that
recovers one. Changing it means replacing it.

## Your AI tool never receives it

This is worth stating carefully because it is the question people actually have.

**No publishing tool takes a credential as a parameter.** Not a password, not a
key, not a token. The tool that lists your data sources returns ten fields, and
they are all descriptive: `id`, `kind`, `label`, `engine`, `status`,
`last_verified_at`, `last_error`, `health_state`, `health_error`, and
`health_source`. No password, no secret, no host, and no internal database
detail.

**And the publishing service could not read one if it tried.** That is the part
worth stating carefully, because "we do not do that" is a weaker promise than
"the system is not built so that we could".

Two independent things stand in the way, and both would have to change together:

- **The service your AI tool connects to holds no administrative database key.**
  It is not that the key is unused there; the configuration that would carry it
  does not exist on that service. It authenticates as **you**, with your own
  permissions.
- **Every routine that decrypts a stored credential is executable only by a
  server-side administrative role**, and is explicitly revoked from the ordinary
  signed-in role. So even a service holding your session could not call one.

The first of those is enforced by the build rather than by review: code in the
publishing service that tries to reach for an administrative key fails to
compile, and that check runs on every change before it can ship. We verified
this by making the change deliberately and confirming the build rejects it.

The credential path and the authoring path are therefore separate systems that
meet only at a connection **id**. Your AI passes the id; the server does the
rest.

## What reads it, and what is recorded

Only server-side routines, and only in three situations: testing a connection
when you click **Test**, introspecting or validating while you author, and
running a scheduled refresh.

Where the credential has to be **pulled out of storage into a server process** to
call the vendor's API, that read writes a row to the connection's own
**append-only** event log. Append-only here means enforced by the database rather
than by convention: updates, deletes, and truncation of that log are rejected at
the storage layer.

**PostgreSQL and SQL Server work differently, and better.** On those two the
credential is used **inside the database**, by the confined routine that runs
your query, and is never materialized in a server process at all. So there is no
extraction event to log on that path, because nothing was extracted. Do not read
an empty log there as the absence of a check; read it as the absence of a
handover. The Postgres extract path, which does pull a connection string out,
does write its own entry.

## Errors never carry your credential or your host

When a warehouse connection fails, the underlying driver's error typically
contains the host it dialled, the resolved address, the port, and the user it
tried. **None of that is passed on.**

The server reads the driver's detail only far enough to classify the failure into
a fixed vocabulary, then discards it and raises only the class. That is why a
failure in the app reads as something like "the credential was rejected" rather
than quoting your database's message, and why the run history stores a category
rather than a sentence. The one exception is deliberate: an error in **your own
cube SQL** is returned verbatim, because that text is yours and you need it.

## How much of read-only is actually enforced

This is the part where the honest answer differs per engine, and where a general
reassurance would be misleading.

**Grant a read-only credential on every engine.** How much Dashies adds on top of
it varies, and on two engines it adds very little, so the credential is the one
control that is doing work no matter which engine you picked.

### The cube must be a read-only query, but the mechanism is not the same everywhere

One property holds on **all six** engines: your SQL is rejected unless it
**begins with `select` or `with`**. That is the whole of the shared floor, and
the two checks people assume sit beside it each have an exception.

**Bounding the query to a single statement holds on five.** It does not hold on
**SQL Server**, where the statement terminator is optional, so no check on the
text can reliably tell how many statements a query contains. That is not an
oversight, and it is the specific reason SQL Server is the one engine where a
read-only login is [required and verified](#sql-server-a-read-only-login-is-required-and-verified)
rather than recommended.

**Stripping comments and string literals before checking holds on five**, so a
keyword cannot hide inside one. What hides a keyword differs per dialect:
dollar-quoting on Redshift, raw string prefixes and backtick identifiers on
Databricks, bracketed identifiers on SQL Server. **PostgreSQL does not strip; it
refuses.** Anything containing a `;` or a `/*` is rejected outright and the
leading keyword is matched against the raw text. That is more conservative than
stripping, not less: there is no stripper to outwit.

Above that floor the engines diverge further, and the difference is worth knowing
rather than smoothing over:

| Engine | What else stands between your cube and a write |
|---|---|
| **PostgreSQL** | The query runs inside a **read-only transaction**. The database refuses a write itself, whatever the text says. |
| **Redshift**, **Databricks**, **SQL Server** | Write and DDL verbs are additionally rejected as whole words, including `into`, because `select ... into` creates a table on these engines and would otherwise pass a leading-`select` check. |
| **BigQuery**, **Snowflake** | The shared floor only. There is no additional keyword reject, so the privileges on your credential are what stands between a cube and a write. |

PostgreSQL's read-only transaction is the strongest of the three approaches,
because it is the database enforcing it rather than a check on the query text.
BigQuery and Snowflake have the weakest, which is the practical reason the advice
below is not optional on those two.

### PostgreSQL: structural confinement, plus the role you create

Each connection gets its own private schema and its own **no-login, no-inherit
database role** with no memberships. The routine that runs your cube SQL is owned
by that role and runs as it, natively, with no privilege switch to undo. It
refuses to run at all if it is ever re-owned, and attempting to switch roles from
inside cube SQL is rejected by the database.

References to system catalogues are also blocked, so one tenant's query cannot
enumerate another's connection topology.

What Dashies does **not** do here: it does not check that the login you supplied
is read-only. The `grant select`-only role you create is doing real work. The
connect form gives you the exact SQL for it. See
[Connect PostgreSQL](/guides/connect-warehouse/postgres).

### SQL Server: a read-only login is required and verified

The **only** engine where Dashies refuses your credential outright. On the
credential axis this is the strongest of the six; PostgreSQL is the strongest on
the query axis, above. They are different guarantees, not a ranking.

When you test the connection, Dashies probes the login's actual privileges and
**refuses it if it holds any write or administrative privilege**, with
`readonly_login_required`. A server administrator account is rejected, not warned
about.

It is required rather than recommended because of the gap noted above: with an
optional statement terminator, no check on the query text can reliably bound how
many statements it contains. So on this engine the read-only login **is** the
boundary, and the SQL check is only defense in depth. See
[Connect SQL Server](/guides/connect-warehouse/sqlserver).

Transport is also fixed rather than configurable: encryption is always on and
server certificate validation is never disabled.

### BigQuery, Snowflake, Redshift, and Databricks: your grant is the boundary

These four are reached over the vendor's own HTTPS API rather than a database
connection, so there is no schema, role, or read-only transaction for Dashies to
confine your query inside. Each still gets its row in the table above, and
Redshift and Databricks get the extra keyword reject, but on all four
**the privileges attached to the credential are what actually limits it.**

Concretely, that means a service account, key pair, IAM identity, or service
principal with more than read access has more than read access, and Dashies will
not stop it. Each engine's guide gives the minimum grant. Follow it.

## Reaching your network

Dashies connects outward to the warehouse you name; nothing connects inward to
you.

For engines where you supply a host, the address is validated before anything is
dialled, and private and internal address ranges are refused, including
addresses written in unusual numeric forms. Which ports are permitted is fixed
per engine. **IP allow-listing is not supported**, so your warehouse has to be
reachable from the public internet.

For BigQuery, Snowflake, and Redshift you never supply a host at all: it is
derived server-side from the account or region identifier, so there is nothing
there to point somewhere else. Databricks takes a workspace host, constrained to
Databricks' own domains.

## What this does not protect you from

Stated plainly, because a security page that only lists strengths is not useful.

- **An over-privileged credential.** **SQL Server is the only engine where
  Dashies checks** whether the login you supplied is read-only. Everywhere else,
  including PostgreSQL, a credential with write access has write access, and the
  read-only SQL check is the only thing standing in front of it. That is your
  decision, not ours.
- **Data you choose to expose.** A cube that selects raw personal data ships raw
  personal data into the dashboard. See
  [dashboard isolation](/trust/dashboard-isolation) for who can then read it.
- **Your warehouse's own access controls.** Dashies reads with the identity you
  gave it. If that identity can see a table, so can a dashboard built on it.
