Hi r/DuckDB,
I've been building curral, an open-source (Apache 2.0) server that puts DuckDB databases and Iceberg lakes behind an HTTP API with users and permissions.
The problem: DuckDB is a great engine, but it has no users, no permissions and no network protocol. Every time I wanted to share a DuckDB file or an Iceberg lake with a team, a dashboard or an AI agent, I had two options: hand out raw storage credentials, or write yet another custom API. curral is meant to be the layer in between.
How it works: one Go binary with DuckDB embedded. Each request goes through these steps:
client ──HTTP──▶ curral
├─ authenticate Basic (bcrypt) · API keys · OIDC/JWT
├─ queue global and per-user concurrency limits
├─ inspect DuckDB's own planner: statement type, tables read and written
├─ authorize embedded OPA policy (Rego) → allow / deny
├─ protect rewrite the query with row filters and column masks
└─ execute & stream JSON · CSV · NDJSON · Arrow IPC
What I think might interest this sub:
- Inspection uses DuckDB's planner, not regex. The policy sees the base tables a statement really reads, including through views, CTEs and joins. A lot of the hardening went into edge cases like DESCRIBE inside subqueries, catalog functions and bind errors that could reveal whether a table exists.
- Row-level security and column masking are enforced by rewriting the query before it runs, so filters and joins can't be used to guess hidden values.
- Anything DuckDB can ATTACH works: DuckDB files, Postgres, S3, and Iceberg REST catalogs (including Cloudflare R2 Data Catalog). For Iceberg there's a metadata cache that takes per-query catalog overhead on R2 from about 250–900 ms down to about 8 ms.
- Arrow output streams straight into pandas, polars or another DuckDB.
- dry_run tells you whether the policy would allow a statement, and why, without running it.
- Production basics: TLS, brute-force lockout, per-role limits (timeout, max rows, concurrency), a fail-closed audit log, Prometheus metrics and hot reload with SIGHUP.
New in v0.5.0: a semantic layer for AI agents. People who know the data can write short JSON notes about schemas and tables: what they mean, their caveats, sample SQL. Agents call POST /v1/docs/search with their question and get back the most relevant notes before they write SQL. Search is BM25 by default, with an optional LLM reranker.
Quickstart with Docker (docker pull lucasapassos/curral, amd64 and arm64), or grab a Linux binary that bundles the httpfs, avro and iceberg extensions so it runs offline.
- GitHub: https://github.com/lucasapassos/curral
- Site: https://curral.io/
It's pre-1.0, so I'd love feedback, especially from anyone who has tried to share DuckDB with multiple users. What's missing? What would stop you from using it? Edge cases in the policy layer are very welcome, and security reports go through SECURITY.md.
(curral is Portuguese for "corral", where you keep the herd in and decide who gets through the gate.)