Skip to content

getbetweenrows/betweenrows

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

98 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

BetweenRows

CI Version Status: Beta License: ELv2 Docker Built with Rust

A fully customizable data access governance layer.

BetweenRows is a SQL-aware proxy that enforces fine-grained access policies β€” masking, filtering, and blocking β€” in real-time. Works with PostgreSQL today; warehouses and lakehouses on the roadmap. Free to self-host. Source-available.

πŸ“– Full documentation: docs.betweenrows.dev

✨ Why BetweenRows

Enforcement & audit

  • SQL-aware β€” parses every query, then masks columns, filters rows, or blocks operations at the level where data actually moves.
  • Every query and every policy decision is logged end-to-end β€” who ran what, when, and what BetweenRows did about it.

Fully customizable

  • Composable roles, custom attributes, and JavaScript decision functions β€” all first-class variables in policy expressions that evaluate at query time.
  • RBAC, ABAC, and programmable logic in one unified engine.

Free and source-available

  • Self-host with no usage limits and no seat fees.
  • Inspect the code. Run it on your infrastructure. No black-box security.

Built in Rust on DataFusion β€” low overhead, memory-safe, production-grade query rewriting.

πŸ“Έ Screenshots

Admin dashboard The admin dashboard β€” data sources, users, roles, and policies at a glance.
Row filter policy editor Row filter policy editor β€” write a WHERE expression with template variables like {user.tenant}.
Query audit detail Query audit β€” see the original SQL, the rewritten SQL, and which policies fired.

πŸ—οΈ Components

BetweenRows ships as a single binary with two planes:

Data plane (port 5434) β€” PostgreSQL wire protocol proxy. Connect with any PostgreSQL client (psql, TablePlus, DBeaver, your app). Policies are enforced transparently on every query.

Management plane (port 5435) β€” Admin UI and REST API for managing users, data sources, roles, policies, and audit logs. Only admin users have access.

The two planes are independent β€” being an admin does not grant data access. All data access must be explicitly granted via data source assignments and policies.

psql / app
    ↓  PostgreSQL wire protocol (port 5434)
BetweenRows
    β”œβ”€ Authenticates user
    β”œβ”€ Checks data source access
    β”œβ”€ Applies policies:
    β”‚      row_filter   β€” inject WHERE clauses
    β”‚      column_mask  β€” replace column values
    β”‚      column_deny  β€” hide columns
    β”‚      table_deny   β€” hide tables
    β”‚      column_allow β€” allowlist columns
    └─ Executes via DataFusion
    ↓
Upstream PostgreSQL

πŸš€ Quick Start (Docker)

docker run -d \
  -e BR_ADMIN_USER=admin \
  -e BR_ADMIN_PASSWORD=changeme \
  -p 5434:5434 -p 5435:5435 \
  -v betweenrows_data:/data \
  ghcr.io/getbetweenrows/betweenrows:latest  # demo only β€” pin a specific tag for anything real
Variable Required Default Description
BR_ADMIN_USER No admin Username for the initial admin account. Change it now if you prefer a different name β€” the username cannot be changed after creation. You can always create additional admin users through the UI later.
BR_ADMIN_PASSWORD Yes β€” Password for the initial admin account. Only used on first boot. You can change the password later through the UI.
-p 5434:5434 Yes β€” SQL proxy port. Connect your SQL clients here.
-p 5435:5435 Yes β€” Admin UI and REST API port.
-v betweenrows_data:/data Yes β€” Persistent volume. Stores the SQLite database (users, data sources, policies, audit logs) and auto-generated encryption/JWT keys when BR_ENCRYPTION_KEY and BR_ADMIN_JWT_SECRET are not set. Do not omit β€” without it, all data and keys are lost when the container restarts.

Change these values to your preference before the first run. See Configuration for all available options.

Open http://localhost:5435 and log in with your admin credentials.

5-Minute Walkthrough

  1. πŸ”— Add a data source β€” Go to Data Sources β†’ Create. Enter your data source connection details and test the connection.
  2. πŸ” Discover the schema β€” Click "Discover Catalog" on your new data source. Select which schemas, tables, and columns to expose through the proxy.
  3. πŸ‘€ Create a user β€” Go to Users β†’ Create. Set a username and password.
  4. πŸ”‘ Grant access β€” On the data source page, assign the user (or a role) access to the data source.
  5. πŸ“œ Create a policy β€” Go to Policies β†’ Create. For example, a row_filter policy with expression tenant = {user.tenant} to isolate rows by tenant. (The tenant attribute must be defined as a custom attribute definition first.)
  6. 🎯 Assign the policy β€” On the data source page, assign the policy to a user, role, or all users.
  7. πŸš€ Connect through the proxy β€” The user can now query through BetweenRows:
    psql "postgresql://alice:secret@localhost:5434/my-datasource"
    Policies are applied automatically. Check the Query Audit page to see what happened.

βš™οΈ Configuration

Env var Required Default Description
BR_ADMIN_PASSWORD Yes (first boot) β€” Password for the initial admin account. Must be set when no users exist in DB.
BR_ADMIN_USER No admin Username for the initial admin account. Only used on first boot.
BR_ENCRYPTION_KEY No (auto-persisted) 64-char hex β€” AES-256-GCM key for secrets at rest. If unset, auto-generated and saved to /data/.betweenrows/encryption_key. Set explicitly in prod. If switching from auto-generated to explicit, copy the value from /data/.betweenrows/encryption_key β€” using a different key makes existing secrets unreadable.
BR_ADMIN_JWT_SECRET No (auto-persisted) Any non-empty string β€” HMAC-SHA256 signing key for admin JWTs. If unset, auto-generated and saved to /data/.betweenrows/jwt_secret. Set explicitly in prod.
BR_ADMIN_JWT_EXPIRY_HOURS No 24 JWT lifetime in hours.
BR_ADMIN_DATABASE_URL No sqlite://proxy_admin.db?mode=rwc SeaORM connection URL (use postgres://… for shared backend).
BR_PROXY_BIND_ADDR No 127.0.0.1:5434 Proxy listen address. Docker image defaults to 0.0.0.0:5434.
BR_ADMIN_BIND_ADDR No 127.0.0.1:5435 Admin REST API listen address. Docker image defaults to 0.0.0.0:5435.
BR_IDLE_TIMEOUT_SECS No 900 (15 min) Close idle proxy connections after this many seconds. Set to 0 to disable.
BR_CORS_ALLOWED_ORIGINS No (empty, same-origin only) Comma-separated list of allowed CORS origins for the Admin API.
RUST_LOG No info Log filter (standard Rust/tracing convention).

πŸ”Œ Connecting to the Proxy

BetweenRows speaks the PostgreSQL wire protocol β€” connect with any PostgreSQL client using the datasource name as the database:

psql "postgresql://<user>:<password>@127.0.0.1:5434/<datasource-name>"

Tested with psql and TablePlus. Any tool that supports PostgreSQL or ODBC with a PostgreSQL driver should work β€” including DBeaver, DataGrip, BI tools, and application ORMs.

Note: Some SQL clients send additional metadata queries (e.g., for autocompletion or schema browsing) that BetweenRows may not support yet. If your client fails to connect, please open an issue.

πŸ›‘οΈ Policy System

BetweenRows supports five policy types:

Type What it does
row_filter Injects a WHERE clause to filter rows (e.g., tenant = {user.tenant})
column_mask Replaces column values with an expression (e.g., '***@' || split_part(email, '@', 2))
column_allow Permits access to specific columns (required in policy_required mode)
column_deny Hides columns from the user's schema entirely
table_deny Hides entire tables from the user's schema

Key concepts:

  • RBAC β€” assign policies to roles. Users inherit policies through role membership, including via role hierarchies
  • ABAC β€” define custom user attributes (e.g., department, region, clearance level) and use them in policy expressions via {user.<key>} template variables
  • Decision functions β€” optional JavaScript functions compiled to WASM that gate policy evaluation based on arbitrary logic (time windows, multi-attribute conditions, external state)
  • Assignment scopes β€” policies can be assigned to individual users, roles, or all users on a data source
  • Template variables β€” {user.username}, {user.id} (built-in), and custom attributes like {user.tenant}, {user.region} are substituted at query time, making policies dynamic per user
  • Access modes β€” data sources can be set to open (all tables accessible by default) or policy_required (tables are hidden unless a column_allow policy grants access)
  • Deny wins β€” deny policies are evaluated before permit policies and cannot be overridden
  • Visibility follows access β€” denied columns and tables are removed from the user's schema at connection time, so they don't appear in client tools like TablePlus or DBeaver

See docs-site/docs/concepts/policy-model.md for the full guide.

πŸ“‹ Catalog Workflow

Before a data source is queryable through the proxy, its catalog must be saved. The UI wizard guides through four steps:

  1. Discover schemas β€” select which schemas to include
  2. Discover tables β€” select tables within those schemas
  3. Discover columns β€” choose which columns to expose
  4. Save β€” persists selections and makes them available for queries

The catalog is an allowlist β€” the proxy can never expose tables or columns not explicitly saved. To detect schema drift after upstream changes, use "Sync Catalog" from the data source page.

πŸ”— Admin REST API

BetweenRows includes a full REST API at http://localhost:5435/api/v1 for managing users, data sources, roles, policies, catalog discovery, and audit logs. All endpoints require JWT authentication (POST /auth/login to obtain a token).

Everything you can do in the admin UI can also be done via the API β€” useful for scripting, CI/CD integration, and automation.

πŸ’» CLI

Create users without the UI β€” useful for scripting and automation. If you're locked out of the admin UI, use --admin to create a new admin user to regain access. You can then change passwords through the UI. A forgot/reset password feature is on the roadmap:

# Docker
docker exec -it <container> proxy user create --username alice --password secret
docker exec -it <container> proxy user create --username rescue --password secret --admin

# From source
cargo run -p proxy -- user create --username alice --password secret

πŸ—ΊοΈ Roadmap

See docs-site/docs/about/roadmap.md for planned features including shadow mode, governance workflows, and more.

🀝 Contributing

See CONTRIBUTING.md for architecture details, build instructions, and development setup.

About

Fine-grained access control for Postgres (row filters, column masking, blocking) via a SQL-aware proxy. Free and source-available. Warehouses on the roadmap.

Topics

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Packages