-
Notifications
You must be signed in to change notification settings - Fork 2
OAuth and Security
postgres-mcp provides enterprise-grade security through simple bearer token authentication, OAuth 2.1 authentication, scope-based access control, and supply chain security.
Lightweight authentication for development or single-tenant deployments. No external identity provider needed.
node dist/cli.js --transport http --port 3000 --auth-token my-secret --postgres "postgres://..."
# Or via environment variable
export MCP_AUTH_TOKEN=my-secret
node dist/cli.js --transport http --port 3000 --postgres "postgres://..."Docker:
docker run --rm -p 3000:3000 \
-e POSTGRES_URL=postgres://user:pass@host:5432/db \
-e MCP_AUTH_TOKEN=my-secret-token \
writenotenow/postgres-mcp:latest \
--transport http --port 3000Clients must include Authorization: Bearer <token> on all requests. /health and / are exempt. Unauthenticated requests receive 401 with WWW-Authenticate: Bearer headers per RFC 6750.
Priority: When both
--auth-tokenand--oauth-enabledare set, OAuth 2.1 takes precedence. The bearer token middleware is skipped when OAuth is active.
When using HTTP transport, OAuth 2.1 protects your MCP endpoints.
CLI Options:
node dist/cli.js \
--transport http \
--port 3000 \
--postgres "postgres://user:pass@localhost:5432/db" \
--oauth-enabled \
--oauth-issuer http://localhost:8080/realms/postgres-mcp \
--oauth-audience postgres-mcp-clientEnvironment Variables (Required):
| Variable | Description |
|---|---|
OAUTH_ENABLED |
Enable OAuth (true) |
OAUTH_ISSUER |
OAuth provider URL |
OAUTH_AUDIENCE |
Expected audience claim |
Environment Variables (Optional - auto-discovered):
| Variable | Description |
|---|---|
OAUTH_JWKS_URI |
JSON Web Key Set endpoint |
OAUTH_CLOCK_TOLERANCE |
Token clock skew tolerance (seconds) |
Access control is managed through OAuth scopes in the token:
| Scope | Access Level |
|---|---|
read |
Read-only queries (SELECT, EXPLAIN) |
write |
Read + write operations (INSERT, UPDATE, DELETE) |
admin |
Full administrative access (includes Code Mode) |
full |
Grants all access |
| Scope Pattern | Description |
|---|---|
db:{name} |
Access to specific database |
schema:{name} |
Access to specific schema |
table:{schema}:{table} |
Access to specific table |
{
"scope": "read write db:production schema:public"
}Code Mode (pg_execute_code) requires the admin scope and enforces:
- Rate limiting: 60 executions/minute
-
Blocked operations:
require(),process,eval(), filesystem, network - Isolation: Sandboxed V8 context
See Code-Mode for full documentation.
Write and admin tool invocations are logged to a structured JSONL audit trail with OAuth identity, timing, and outcome. Enable with --audit-log <path> or AUDIT_LOG_PATH (use stderr for containers). Use --audit-redact to omit tool arguments.
See Audit Trail for full documentation including JSONL format, scope mapping, container mode, and log shipping integrations.
This implementation follows current OAuth standards:
| RFC | Description |
|---|---|
| RFC 9728 | OAuth 2.1 Protected Resource Metadata |
| RFC 8414 | OAuth 2.1 Authorization Server Metadata |
| RFC 7591 | OAuth 2.1 Dynamic Client Registration |
The server exposes metadata at /.well-known/oauth-protected-resource.
When using Keycloak as your OAuth provider:
- Create a realm for postgres-mcp
- Create a client with the appropriate scopes
-
Important: Add an Audience mapper to include the correct
audclaim
Adding Audience Mapper:
- Go to Client → Client Scopes → Dedicated Scope
- Add Mapper → Audience
- Set the audience value to match
OAUTH_AUDIENCE
When using HTTP transport, the server applies multiple layers of protection:
| Feature | Description |
|---|---|
| 7 Security Headers | X-Content-Type-Options, X-Frame-Options, CSP, Cache-Control, Referrer-Policy (no-referrer), Permissions-Policy, opt-in HSTS |
| Server Timeouts | Configurable timeouts prevent Slowloris DoS (via MCP_REQUEST_TIMEOUT and MCP_HEADERS_TIMEOUT) |
| DNS Rebinding | Enforces validateHostHeader to strictly match permissible client origins |
| Rate Limiting | 100 requests/minute per IP with Retry-After header on 429 responses |
| Health Check Bypass |
/health always serves regardless of rate-limit state (monitoring-safe) |
| Trust Proxy | Opt-in trustProxy for correct X-Forwarded-For IP extraction behind reverse proxies |
| Wildcard Subdomain CORS | Pattern-based origin matching (e.g., *.example.com) |
| Body Size Limit | Configurable max request body size (default: 1 MB) |
| Cross-Protocol Guard | SSE session IDs rejected on /mcp and vice versa |
Docker images include security features:
| Feature | Description |
|---|---|
| Build Provenance | Cryptographic proof of build process |
| SBOM | Software Bill of Materials |
| Attestations | Verifiable build integrity |
| Non-root | Runs as non-privileged user |
For maximum security, use SHA-pinned images:
# Find SHA tags at Docker Hub
docker pull writenotenow/postgres-mcp:sha256-<manifest-digest>
# Or use direct digest
docker pull writenotenow/postgres-mcp@sha256:<manifest-digest>- Never commit credentials to version control
- Use environment variables for secrets
- Consider secret managers (Vault, AWS Secrets Manager)
- Use TLS/SSL for database connections
- Enable OAuth or
--auth-tokenfor HTTP transport - Restrict network access with firewalls
- Use minimal scopes (principle of least privilege)
- Enable resource-level scopes for multi-tenant environments
- Audit scope assignments regularly
- HTTP-Transport - HTTP endpoint details
- Code-Mode - Admin scope requirements
- Quick-Start - Basic configuration
- Troubleshooting - OAuth issues