> ## Documentation Index
> Fetch the complete documentation index at: https://isthmus.dev/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Security

> Isthmus's safety model: how it protects your database from unintended AI operations.

Isthmus is designed to be safe to point at production databases. Multiple independent layers ensure the AI can only read data, and only the data you allow.

## Local-first architecture

Isthmus runs entirely on your machine. By default, it communicates with your AI client over stdio (standard input/output) — no network ports are opened, no data leaves your machine, and no cloud service is involved.

When using the [HTTP transport](/docs/features/http-transport), Isthmus opens a network port. Bind to `127.0.0.1` to restrict access to your machine, or use a reverse proxy for TLS and authentication if remote access is needed.

## Defense in depth

### 1. SQL AST validation

Every query is parsed using PostgreSQL's actual parser (`pg_query`) and validated at the AST level. Only `SELECT` and `EXPLAIN` statements are allowed. This is not regex matching — it uses the same parser that PostgreSQL itself runs.

See [SQL Validation](/docs/features/sql-validation) for details.

### 2. Read-only transactions

All queries execute inside read-only transactions (`SET TRANSACTION READ ONLY`). Even if a write query somehow passed AST validation, PostgreSQL would reject it.

### 3. Row limits

Results are capped at `MAX_ROWS` (default: 100). This prevents accidental data dumps from `SELECT *` on large tables.

### 4. Query timeout

Queries are cancelled after `QUERY_TIMEOUT` (default: 10s). This prevents runaway queries from consuming database resources.

### 5. Schema filtering

The `SCHEMAS` environment variable restricts which schemas the AI can discover. Only listed schemas appear in `discover` and `describe_table` results.

See [Schema Filtering](/docs/features/schema-filtering) for details.

### 6. Column masking

The policy file supports per-column masking rules (`redact`, `hash`, `partial`, `null`) that protect PII in both `query` results and `describe_table` sample rows. Masking is enforced server-side — the AI cannot bypass it regardless of what SQL it generates.

See [Column Masking](/docs/features/column-masking) for details.

### 7. Policy engine

The policy file enriches responses with business context but also acts as a documentation layer — operators can describe exactly what data means, reducing the chance of AI misinterpretation.

See [Policy Engine](/docs/features/policy-engine) for details.

### 8. Explain-only mode

The `--explain-only` flag forces all `query` calls to return `EXPLAIN` plans instead of actual data. Useful for environments where you want the AI to help with query writing without accessing the data.

### 9. Audit logging

The `--audit-log` flag writes every executed query to an NDJSON file with timestamps, row counts, and execution times. Use this for compliance and monitoring.

See [Audit Logging](/docs/features/audit-logging) for details.

### 10. Bearer authentication (HTTP transport)

When using the [HTTP transport](/docs/features/http-transport), Isthmus requires a bearer token on every request to the `/mcp` endpoint. Isthmus refuses to start in HTTP mode without a token configured, preventing accidental unauthenticated exposure.

Key implementation details:

* **Constant-time comparison** — tokens are compared using `crypto/subtle.ConstantTimeCompare`, which prevents timing attacks that could leak the token byte-by-byte
* **Mandatory token** — the server refuses to start if `HTTP_BEARER_TOKEN` is empty when `TRANSPORT=http`
* **Health endpoints excluded** — `/health` and `/ready` are unauthenticated so container orchestrators can probe them without credentials

See [HTTP Transport](/docs/features/http-transport) for configuration details.

### 11. Error sanitization

Isthmus wraps all database errors before returning them to the AI model. Raw PostgreSQL error messages — which can contain table names, constraint definitions, internal IP addresses, or schema details — are never exposed directly. Instead, errors are wrapped with a generic context prefix:

| Error source             | What the AI sees                                  |
| ------------------------ | ------------------------------------------------- |
| Query validation failure | `"query failed: only SELECT queries are allowed"` |
| Query execution error    | `"query failed: ..."` (wrapped, not raw PG error) |
| Table not found          | `"failed to describe table: ..."`                 |
| Schema listing failure   | `"failed to list schemas: ..."`                   |

This prevents information disclosure through error messages — a common OWASP risk where database internals leak through stack traces or verbose error responses.

### 12. Credential redaction

Database credentials are never logged or displayed in plaintext:

* **Structured logs** — the `DATABASE_URL` is never included in `slog` log output
* **Dry-run output** — when `--dry-run` prints the resolved configuration, the password in the connection string is replaced with `***` using Go's `net/url` parser (e.g. `postgres://user:***@localhost:5432/mydb`)
* **Error messages** — connection failures do not include the full DSN

### 13. Observability

OpenTelemetry tracing and metrics (`--otel`) let you monitor query latency, error rates, and tool usage in production. Traces include the full SQL statement, row counts, and error details.

See [OpenTelemetry](/docs/features/opentelemetry) for details.

## Recommended Postgres role

For production use, create a dedicated read-only database role:

```sql theme={null}
-- Create a read-only role
CREATE ROLE isthmus_reader LOGIN PASSWORD 'strong-random-password';

-- Grant connect
GRANT CONNECT ON DATABASE mydb TO isthmus_reader;

-- Grant usage on specific schemas
GRANT USAGE ON SCHEMA public TO isthmus_reader;
GRANT USAGE ON SCHEMA analytics TO isthmus_reader;

-- Grant select on all tables in those schemas
GRANT SELECT ON ALL TABLES IN SCHEMA public TO isthmus_reader;
GRANT SELECT ON ALL TABLES IN SCHEMA analytics TO isthmus_reader;

-- Ensure future tables are also readable
ALTER DEFAULT PRIVILEGES IN SCHEMA public
  GRANT SELECT ON TABLES TO isthmus_reader;
ALTER DEFAULT PRIVILEGES IN SCHEMA analytics
  GRANT SELECT ON TABLES TO isthmus_reader;
```

Then use this role in your connection string:

```
postgres://isthmus_reader:strong-random-password@localhost:5432/mydb
```

## Security checklist

### Database

* [ ] Use a dedicated read-only Postgres role with minimal privileges
* [ ] Set `SCHEMAS` to limit schema discovery to only the schemas the AI needs
* [ ] Set `MAX_ROWS` to a reasonable limit for your use case
* [ ] Set `QUERY_TIMEOUT` appropriate for your database workload

### Data protection

* [ ] Mask all PII columns (`email`, `ssn`, `phone`, etc.) in your [policy YAML](/docs/features/column-masking)
* [ ] Consider `--explain-only` if the AI should analyze queries without accessing actual data
* [ ] Use `redact` mask for highly sensitive columns, `hash` for columns needed in JOINs/GROUP BY

### Monitoring

* [ ] Enable `--audit-log` in production for query forensics
* [ ] Review the audit log periodically for unexpected queries
* [ ] Consider `--otel` for real-time monitoring of query latency and error rates

### Network (HTTP transport only)

* [ ] Set a strong, randomly generated `HTTP_BEARER_TOKEN` (at least 32 bytes)
* [ ] Bind to `127.0.0.1` unless remote access is specifically required
* [ ] Use TLS (via reverse proxy) to protect the bearer token in transit
* [ ] Never commit bearer tokens to version control
