Skip to main content
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, 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 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 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 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 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 for details.

10. Bearer authentication (HTTP transport)

When using the 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 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 sourceWhat 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 for details. For production use, create a dedicated read-only database role:
-- 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
  • 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