CC Safety Net
코딩 에이전트 CLI 안전층으로 실행 전 파괴적 git·파일시스템 명령과 비밀 파일 접근을 차단
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Give your devs — and your AI agents — access to prod.
A transparent database proxy that gives developers and AI agents controlled access to production databases. Speaks PostgreSQL, Oracle, MySQL/MariaDB, MongoDB, and Microsoft SQL Server wire protocols, so unmodified clients, drivers, and agent tools connect as usual. Every query logged. Every connection tracked.
Full documentation is available at dbbat.com:
Deploying with Kubernetes: the Helm chart, and how the public demo is deployed and upgraded.
The Problem:
The Solution:
DBBat acts as a monitoring proxy that allows controlled access to production databases — for humans and AI agents alike — with:
| Engine | Protocol | Default proxy port | Notes |
|---|---|---|---|
| PostgreSQL | PostgreSQL wire (pgx/v5) | :5433 (DBB_LISTEN_PG) | First-class support; auth terminated at proxy |
| Oracle | TNS / TTC | :1522 (DBB_LISTEN_ORA) | O5LOGON proxy auth; go-ora end-to-end (other clients reach AUTH only — see docs/oracle.md) |
| MySQL | MySQL wire (go-mysql-org/go-mysql) | :3307 (DBB_LISTEN_MYSQL) | caching_sha2_password (default), mysql_clear_password; TLS terminated at proxy |
| MariaDB | MySQL wire (same listener) | :3307 (DBB_LISTEN_MYSQL) | Same as MySQL — mysql_native_password not supported, STMT_BULK_EXECUTE refused |
| MongoDB | MongoDB wire (OP_MSG, hand-rolled) | :27018 (DBB_LISTEN_MONGO) | SCRAM-SHA-256 or PLAIN-over-TLS at the proxy; upstream via SCRAM-SHA-256 — see docs/mongodb.md |
| SQL Server | TDS (hand-rolled) | :1434 (DBB_LISTEN_MSSQL) | PRELOGIN + encapsulated TLS + LOGIN7, SQL auth only (no NTLM/Kerberos/Entra) — see docs/mssql.md. 1434/tcp is free: the SQL Server Browser that owns 1434 is UDP-only |
Each engine has its own listener; enable only the ones you need by setting the matching DBB_LISTEN_* environment variable. PostgreSQL is enabled by default; Oracle/MySQL/MongoDB/SQL Server listen on their default ports unless explicitly disabled in config.
Any of these upstreams can be reached directly or through an SSH bastion — see SSH tunnels.
admin/viewer/connector rolesdbb_…) for programmatic access; cannot create or revoke other keys (security restriction)protocol field per server (postgresql, oracle, mysql, mariadb, mongodb, mssql, ssh)via_uid), with host-key TOFU pinning and a shared pooled dialerquery_rows table) up to configurable size limitsstarts_at / expires_at), independent controls (read_only, block_copy, block_ddl), and optional quotas (max_query_counts, max_bytes_transferred)auto_approve skip admin review and materialize the grant instantly, with a required justification and a dedicated audit trailapplication_name / program_name / AUTH_PROGRAM_NM), so target-side monitoring attributes queries to the real persondocker run -d \
-e DBB_DSN="postgres://user:pass@host:5432/dbbat?sslmode=require" \
-p 5433:5433 \
-p 1522:1522 \
-p 3307:3307 \
-p 27018:27018 \
-p 1434:1434 \
-p 4200:4200 \
ghcr.io/fclairamb/dbbat
Ports: 5433 PostgreSQL proxy, 1522 Oracle proxy, 3307 MySQL/MariaDB proxy, 27018 MongoDB proxy, 1434 SQL Server proxy, 4200 REST API + web UI.
See docker-compose installation for a complete example.
All API endpoints are under /api/v1/. See the API Reference for complete documentation. Scripts and CI jobs should standardize on DBBAT_API_KEY, DBBAT_USER, and DBBAT_URL — see Environment Variables for Clients.
Breaking change in v0.17.0:
/api/v1/databaseswas renamed to/api/v1/servers, and/api/v1/ssh-serverswas added for bastion management. No/databasesalias is kept — update any scripts that used the old path.
TOKEN=$(curl -s -X POST http://localhost:4200/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "admin"}' | jq -r '.token')
curl -X POST http://localhost:4200/api/v1/users \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "developer",
"password": "temppass123",
"roles": ["connector"]
}'
curl -X POST http://localhost:4200/api/v1/servers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "production",
"description": "Production database",
"protocol": "postgresql",
"host": "db.example.com",
"port": 5432,
"database_name": "myapp",
"username": "readonly_user",
"password": "dbpass",
"ssl_mode": "require"
}'
For Oracle, set "protocol": "oracle" and add "oracle_service_name": "ORCL". For MySQL/MariaDB, set "protocol": "mysql" (or "mariadb") and use port 3306. For MongoDB, set "protocol": "mongodb", use port 27017, and optionally add "mongo_auth_source": "admin". For SQL Server, set "protocol": "mssql" and use port 1433.
Creating a server whose name is already taken returns 409 DUPLICATE_NAME (same for grant definitions and users).
Servers that aren't directly reachable can be dialled through an SSH tunnel. Register the bastion via /api/v1/ssh-servers, then point the database server at it with via_uid:
BASTION=$(curl -s -X POST http://localhost:4200/api/v1/ssh-servers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "prod-bastion",
"host": "bastion.example.com",
"port": 22,
"username": "ec2-user",
"ssh_private_key": "-----BEGIN OPENSSH PRIVATE KEY-----\n..."
}' | jq -r '.uid')
curl -X POST http://localhost:4200/api/v1/servers \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d "{\"name\": \"prod-behind-bastion\", \"protocol\": \"postgresql\", \"host\": \"10.0.1.20\", \"port\": 5432, \"database_name\": \"myapp\", \"username\": \"readonly_user\", \"password\": \"dbpass\", \"via_uid\": \"$BASTION\"}"
Tunnelling works for all five protocols. The bastion host key is pinned on first use (TOFU) and connections are pooled and shared across sessions. ssh_private_key and ssh_passphrase are write-only and never returned. Set "clear_via_uid": true on update to go back to a direct dial.
curl -X POST http://localhost:4200/api/v1/grants \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"user_id": "<user-uid>",
"database_id": "<database-uid>",
"controls": ["read_only"],
"starts_at": "2024-01-01T00:00:00Z",
"expires_at": "2024-12-31T23:59:59Z",
"max_query_counts": 1000,
"max_bytes_transferred": 10485760
}'
controls accepts any combination of read_only, block_copy, block_ddl. An empty array means full write access.
# PostgreSQL
psql -h localhost -p 5433 -U developer -d production
# Oracle (go-ora-style easy connect)
# Use the database name (or its oracle_service_name) as SERVICE_NAME
# Example with sqlplus: developer/temppass123@//localhost:1522/production
# MySQL / MariaDB
mysql -h 127.0.0.1 -P 3307 -u developer -p production
# MongoDB (authSource carries the DBBat database name)
mongosh "mongodb://developer:temppass123@localhost:27018/?authSource=production&authMechanism=SCRAM-SHA-256"
# SQL Server (the -d database names the DBBat entry; -C trusts the proxy's self-signed cert)
sqlcmd -S localhost,1434 -U developer -P temppass123 -d production -C
| Variable | Description | Default |
|---|---|---|
DBB_DSN | PostgreSQL DSN for DBBat storage | Required |
DBB_LISTEN_PG | PostgreSQL proxy listen address | :5433 |
DBB_LISTEN_ORA | Oracle proxy listen address (empty disables) | :1522 |
DBB_LISTEN_MYSQL | MySQL/MariaDB proxy listen address (empty disables) | :3307 |
DBB_LISTEN_MONGO | MongoDB proxy listen address (empty disables) | :27018 |
DBB_LISTEN_MSSQL | SQL Server (TDS) proxy listen address (empty disables) | :1434 |
DBB_LISTEN_API | REST API listen address | :4200 |
DBB_KEY | Base64-encoded AES-256 encryption key | Auto-generated at ~/.dbbat/key |
DBB_KEYFILE | Path to file containing encryption key | - |
DBB_RUN_MODE | Run mode: empty (production), test, or demo | - |
DBB_LOG_LEVEL | debug, info, warn, error | info |
DBB_INSTANCE_ID | Identifies this process among replicas sharing the store, so crash-orphaned connections can be reclaimed | Hostname |
DBB_DUMP_DIR | Directory for session packet dumps (empty disables) | - |
DBB_DUMP_MAX_SIZE | Max dump file size per session, in bytes | 10485760 (10 MB) |
DBB_DUMP_RETENTION | Auto-delete dumps older than this (Go duration) | 24h |
DBB_QUERY_STORAGE_RETENTION | Auto-delete query history and captured result rows older than this (Go duration; 0 keeps them forever) |
Each of the two settings resolves per-provider, then instance-wide, then the
default. The overrides can equally be written as auth.providers.<name>.* in a
config file.
The two instance-wide DBB_AUTH_* settings were previously named
DBB_SLACK_AUTH_AUTO_CREATE_USERS / DBB_SLACK_AUTH_DEFAULT_ROLE; those names
still work as aliases — instance-wide, not Slack-specific — and the DBB_AUTH_*
setting wins whenever both are present, whichever source each came from
(environment or config file).
See Configuration for the full set, including rate limiting, query storage, hash presets, auth cache, Slack OAuth, demo target, and dev redirects.
Any OpenID Connect provider works — Google Workspace, Okta, Microsoft Entra,
Keycloak, Authentik. Point DBB_OIDC_ISSUER at your issuer, register
https://<your-host>/api/v1/auth/oidc/callback as the redirect URI, and DBBat
adds a button to the login page. Every sign-in is carried by an ID token DBBat
verifies against the issuer's JWKS, and the code flow uses PKCE (S256).
DBB_OIDC_ROLE_MAPPING="admin=db-admins,viewer=analysts" then lets the
directory own role assignment: the mapping is re-applied on every login, so
leaving the group demotes at the next sign-in without anyone remembering to
click. It is authoritative only for the roles it names, never empties a user's
roles below the default one, and never strips the last admin. See
Single sign-on (OIDC) for
per-provider snippets and the configuration each IdP needs to emit groups.
dbb_…) are hashed with Argon2id, exactly like passwords — only the 8-character prefix is stored in clear, for lookup, so a leaked database yields no usable key. They cannot create or revoke other keysadmin / admin) is created on first startup — change it immediatelypsql / pg client ─► DBBat (auth + grant check + log) ─┐
sqlplus / go-ora ─► DBBat (TNS service-name routing) ─┤ direct dial ┌─► PostgreSQL / Oracle
mysql / mariadb cli ─► DBBat (caching_sha2_password) ─┼───────────────► ┤
mongosh / driver ─► DBBat (SCRAM-SHA-256 / PLAIN-TLS) ─┤ or SSH tunnel └─► MySQL / MariaDB /
sqlcmd / TDS driver ─► DBBat (PRELOGIN + TLS + LOGIN7) ─┘ (via_uid) MongoDB / SQL Server
DBBat is a single Go binary backed by a PostgreSQL store (users, servers, grants, connections, queries, audit, dumps).
make dev # Start dev environment with hot reload (Air + Vite)
make test # Run Go tests (uses testcontainers)
make test-e2e # Run Playwright E2E tests
make build-app # Build frontend + backend
make lint # Run golangci-lint
See CLAUDE.md for development documentation.
AGPL-3.0
default_transaction_read_onlyLOAD DATASELECT … INTO OUTFILELOCAL INFILEaudit_log with its actor; dbbat only ever inserts, and the REST API exposes reads only (GET /api/v1/audit)DBB_KEY that is never stored in the database — so modifying, deleting or reordering a record is detectable even by someone with write access to dbbat's own PostgreSQL store. dbbat audit verify walks the chain and names the first break (see docs/audit-chain.md).pcapng files, so Wireshark dissects them out of the box; same format across all protocols (see docs/dump-format.md) with dbbat dump anonymise for sharing/api/docs), versioned under /api/v1//app — servers (/servers, listing SSH bastions alongside database servers), grants, connections and queries all have detail pages0 (recommended: 720h) |
DBB_CONNECTION_RETENTION | Auto-delete closed connections — the session ledger — once they disconnected longer ago than this. Unset inherits the query window; 0 keeps sessions forever while statements still expire. Must be ≥ the query window | Inherits DBB_QUERY_STORAGE_RETENTION |
DBB_MCP_ENABLED | Serve the MCP endpoint for AI agents at /api/v1/mcp (API-key authenticated; see docs/mcp.md) | true |
DBB_MYSQL_TLS_DISABLE | Disable MySQL TLS termination at the proxy | false |
DBB_MYSQL_TLS_CERT_FILE | PEM cert for MySQL TLS (auto self-signed if empty) | - |
DBB_MYSQL_TLS_KEY_FILE | PEM RSA key for MySQL TLS (auto-generated if empty) | - |
DBB_OIDC_ISSUER | OIDC issuer URL for single sign-on; setting it enables the provider | - |
DBB_OIDC_CLIENT_ID | OIDC client ID (required once the issuer is set) | - |
DBB_OIDC_CLIENT_SECRET | OIDC client secret (required once the issuer is set) | - |
DBB_OIDC_SCOPES | Scopes requested from the issuer (openid always added) | openid email profile |
DBB_OIDC_DISPLAY_NAME | Login-button label | SSO |
DBB_OIDC_EMAIL_DOMAINS | Comma-separated allowlist checked against the verified email claim | - (any domain) |
DBB_OIDC_GROUPS_CLAIM | ID-token claim carrying directory group membership | groups |
DBB_OIDC_ROLE_MAPPING | Binds roles to directory groups (admin=db-admins,viewer=analysts); applied on every login | - (roles stay manual) |
DBB_AUTH_AUTO_CREATE_USERS | Let a verified SSO identity with no local account provision one on first login. Applies to every login provider | true |
DBB_AUTH_DEFAULT_ROLE | Role such an account starts with, and the floor a role mapping never digs below. Must be spelled exactly like a real role (admin, viewer, connector) or startup fails | connector |
DBB_AUTH_AUTO_CREATE_USERS_<PROVIDER> | Same, for one provider only (..._SLACK, ..._OIDC) — for a deployment that trusts its corporate issuer to mint accounts but not its Slack workspace | - (the instance-wide value) |
DBB_AUTH_DEFAULT_ROLE_<PROVIDER> | Same, for one provider only. Validated exactly like the instance-wide role; an unknown provider name is a startup failure | - (the instance-wide value) |