connect a database

Connect PostgreSQL

Provisioning read-only credentials for PostgreSQL — the same instructions Evalyst shows you when a credential check fails.

source of truth: app repo docs/readonly/postgres.md

Run as a superuser (or the database owner) in the database you want to attach.

-- 1. the role. Evalyst only ever logs in; it never needs CREATEDB/CREATEROLE/SUPERUSER.
CREATE ROLE evalyst_reader LOGIN PASSWORD '<STRONG_PASSWORD>'
    NOSUPERUSER NOCREATEDB NOCREATEROLE NOINHERIT NOBYPASSRLS;

-- 2. let it reach the database and the schemas that hold your data
GRANT CONNECT ON DATABASE "<your_db>" TO evalyst_reader;
GRANT USAGE   ON SCHEMA public TO evalyst_reader;          -- repeat per schema

-- 3. SELECT on what exists today...
GRANT SELECT ON ALL TABLES    IN SCHEMA public TO evalyst_reader;
GRANT SELECT ON ALL SEQUENCES IN SCHEMA public TO evalyst_reader;

-- 4. ...and on what gets created later. Run this AS THE ROLE THAT OWNS THE TABLES —
--    default privileges are per-granting-role, which is the step people miss and then
--    wonder why last month's new table is invisible.
ALTER DEFAULT PRIVILEGES FOR ROLE <table_owner> IN SCHEMA public
    GRANT SELECT ON TABLES TO evalyst_reader;

-- 5. belt and braces: make read-only the default posture of every session it opens.
--    (The driver also sets it per connection; this survives a driver bug.)
ALTER ROLE evalyst_reader SET default_transaction_read_only = on;
ALTER ROLE evalyst_reader SET statement_timeout = '30s';

Check it yourself

SET ROLE evalyst_reader;
SELECT count(*) FROM <some_table>;                  -- works
CREATE TABLE probe(i int);                          -- must fail
UPDATE <some_table> SET <col> = <col> WHERE false;  -- must fail: permission denied
RESET ROLE;

Notes

  • Point Evalyst at a replica if you have one. pg_is_in_recovery() is reported in the verify evidence, and a physical replica is read-only at the storage layer — the strongest guarantee available.
  • Row-level security. If your tables use RLS, evalyst_reader is subject to it (the role is created NOBYPASSRLS above, deliberately). Grant it whatever policy exemption your analysts have, or the agent will quietly see a subset and report it as the whole.
  • CREATE on public. Before Postgres 15, every role can create objects in public. Evalyst reports this as a warning and still activates: creating a new table cannot change what an existing query returns. Revoke it if you want a clean report: REVOKE CREATE ON SCHEMA public FROM PUBLIC;
  • Connecting over SSH. If the database is not exposed publicly, give Evalyst a bastion instead of opening the port: set config.ssh = {"host": …, "user": …, "key_secret_ref": "SSH_PRIVATE_KEY"} and keep config.host as the address the bastion uses (often 127.0.0.1). Host-key trust is per-source, not from ~/.ssh/known_hosts — see the bastion notes.
  • TLS. Direct (non-SSH) connections default to sslmode=require. Set config.sslmode to verify-full if you have the CA distributed, or to prefer for an SSH-tunnelled connection where the transport is already encrypted.