Why this page exists

Earlier versions of this site and the project README claimed 100% Apache Iceberg REST spec compliance and marked eleven features "Production-Ready". Neither claim was supported by the code. Both are gone. What follows is the same maturity assessment that now ships in the repository README — the good and the bad, in the same place.

Maturity by Area

AreaMaturityNotes
Iceberg REST — namespaces, tables, commits Solid Commit requirements including assert-ref-snapshot-id are enforced; unsupported operations return an error rather than a false 200 OK.
Iceberg REST — full spec coverage Partial Several endpoints are missing. See the coverage list below.
Multi-tenancy and isolation Solid Tenant scope is a required parameter throughout; isolation tests pass against the production middleware.
Git-style branching, tags, merge Good Merge direction and branch-asset tracking were fixed in 0.6.0.
RBAC, service users, API keys Good API keys carry a key ID, so authentication is one bcrypt verification rather than a scan of every service user.
Audit logging Good 40+ actions, 19 resource types, plus authentication events from 0.6.0. Writes are best-effort and records are not tamper-evident.
Observability New in 0.6.0 Prometheus metrics, request correlation IDs, a working RUST_LOG, JSON logs, and real health endpoints.
PostgreSQL backend Good The recommended backend. Provisioning from a fresh database was broken before 0.6.0.
SQLite backend Good Single-writer; suitable for one node or an evaluation. Versioned migrations added in 0.6.0.
MongoDB backend Beta No index management, transactions only where the deployment supports sessions, four known-failing tests.
Kubernetes deployment Good The chart shipped referencing three templates that did not exist; all present and CI-linted from 0.6.0.
Transactions for admin operations Partial Landed late in 0.6.0. PostgreSQL now wraps delete_catalog, delete_branch and merge_branch in transactions; MongoDB covers delete_catalog only, and only on a deployment that supports sessions. Branch creation by copying assets is still not atomic.
Rate limiting Missing Global concurrency and body limits and a request timeout exist; there is no per-IP or per-account throttle.
OIDC (PKCE, JWKS, discovery) Missing OAuth works and is now safe, but it is not full OIDC.
Encryption at rest for warehouse credentials Missing Cloud credentials are stored unencrypted in the catalog database.
HA at more than one replica Partial It works, but it is unproven — see the limitations below.
Backup / restore / DR Undocumented and untested No published RPO or RTO.
Performance Unmeasured No load-test harness, no published figures, no capacity model.

AUDIT_EXECUTION_PLAN.md in the repository root is a candid, itemised assessment of the codebase with a phased plan. It is the best place to understand what is weak and what is being worked on.

Known Limitations

Stated plainly rather than buried.

Administrative operations are only partly transactional

As of 0.6.0, PostgreSQL wraps a cascading catalog delete, a branch delete and a branch merge in a database transaction, and MongoDB wraps a cascading catalog delete where the deployment supports a session — a standalone mongod cannot. Creating a branch by copying assets is still issued as independent statements, so a failure partway through leaves the catalog partially applied with no rollback and no repair tool. Take a backup before large administrative operations.

The Iceberg table-commit path is safe: it uses compare-and-swap with full requirement enforcement.

No rate limiting

There are global concurrency and body-size limits and a per-request timeout, but no per-IP or per-account throttle. The login endpoint is brute-forceable. Put Pangolin behind a gateway that can rate-limit if it is exposed to anything untrusted.

OAuth is not full OIDC

The 0.6.0 flow is safe — signed state, server-side nonces, allowlisted redirects, code exchange instead of a token in a URL — but it is not a complete OIDC implementation. There is no PKCE, no id_token signature validation, no JWKS, and no discovery document. Users are matched on the email a provider returns, with no email_verified check. JWTs are symmetric HS256 with no key rotation, so rotating the secret ends every session at once.

Warehouse credentials are stored unencrypted

Cloud storage credentials live in the clear in the catalog database. Envelope encryption at rest is not implemented. Encrypt your database backups. The in-process warehouse cache is also node-local, so a rotated credential can be served by a peer for up to the cache TTL — 5 seconds by default.

Running more than one replica works but is unproven

The background token-cleanup job runs in every replica with no coordination, and the OAuth nonce store is in-process — so OAuth needs session affinity across replicas.

No tested backup, restore or disaster recovery

No procedure has been exercised and there is no published RPO or RTO. The production runbook covers health, metrics, incidents and upgrades, and is explicit about where the gaps are.

No published performance figures

Rust makes for a small, fast binary, but "fast" is a claim we have not measured. There is no load-test harness and no capacity model.

Iceberg REST Coverage

This project previously claimed 100% spec compliance. That was not supported by the code, and is not claimed now.

✅ Implemented

getConfig listNamespaces createNamespace dropNamespace updateNamespaceProperties listTables createTable loadTable updateTable dropTable tableExists renameTable createView loadView

Plus credential vending and the OAuth token endpoint. getConfig returns per-warehouse configuration from 0.6.0.

❌ Not implemented

loadNamespaceMetadata namespaceExists registerTable commitTransaction

Plus most of the view API — no list, drop, replace, exists or rename. commitTransaction means there are no multi-table atomic commits.

🔒 Commit requirements — all enforced

assert-create assert-table-uuid assert-ref-snapshot-id assert-current-schema-id assert-default-spec-id assert-default-sort-order-id assert-last-assigned-field-id

An unrecognised requirement is refused rather than ignored.

🔧 Commit updates — all applied

assign-uuid upgrade-format-version add-schema set-current-schema add-snapshot set-snapshot-ref remove-snapshot-ref set-properties remove-properties set-location add-spec set-default-spec add-sort-order set-default-sort-order remove-snapshots

An unrecognised update returns 501 rather than a false 200 OK.

Backend Parity

CatalogStore is a large trait in which many methods carry a default implementation returning "Operation not supported by this store" — so a missing method compiles cleanly and surfaces only as an opaque runtime 500. This is the matrix that tells you, before you deploy, which features work where.

CapabilityMemorySQLitePostgreSQLMongoDB
Tenants, catalogs, namespaces, assets
Warehouses and credential vending
Branches, tags, commits
Branch merge
Merge operations and conflicts⚠️
Users, roles, permissions (RBAC)⚠️
Service users and API keys
Token issue / revoke / list
Audit logging, filtering and counting⚠️
Business metadata and access requests
Federated catalogs
Asset search
Versioned schema migrationsn/a
Multi-statement transactionsn/a⚠️⚠️⚠️
Survives a restart

✅ implemented and covered by tests · ⚠️ implemented with known gaps · ❌ not available

Choosing a backend

Full backend parity documentation →

Security Posture

Fixed in 0.6.0

IDIssueImpact
A-8 The OAuth callback appended the session JWT to a redirect URL taken from the unsigned state parameter, with no allowlist Account takeover. An authorize link whose state decoded to {"redirect_uri":"https://evil.example/"} delivered a valid token for the victim to the attacker's access log, with no credential theft required
A-9 The OAuth state nonce was generated but never stored or verified Login CSRF. An attacker could bind a victim's browser to the attacker's account
A-10 PANGOLIN_JWT_SECRET fell back to a value published in this repository; the Helm chart shipped working placeholder passwords and the seeded admin used password123 Anyone could forge a Root token against a deployment that missed one environment variable
A-11 The authentication whitelist matched any path ending in /config and any path containing /oauth/tokens Authentication bypass. A namespace or table named config was reachable unauthenticated, including its DELETE route
A-12 API-key authentication ran bcrypt::verify against every service user in every tenant Unauthenticated denial of service. At 100 service users a single request with a bogus key burned roughly 25 CPU-seconds, before any rate limiting
A-13 A store error during the token-revocation check was logged and ignored Revoked tokens were accepted again during any database disruption. It now returns 503
A-14 The root password was compared with == Timing oracle on the password

Known gaps that remain

Before you expose Pangolin to anything you care about

Full security policy, advisory and hardening checklist →

What Comes Next

The outstanding work, tracked in AUDIT_EXECUTION_PLAN.md.

Should you use Pangolin?

Yes, if you are evaluating Iceberg catalogs, want Git-style branching over catalog metadata, are running a single-tenant internal lakehouse behind a trusted network, or want to contribute to a Rust catalog implementation.

Not yet, if you need a catalog in front of production data with a tested recovery path, a compliance-grade audit trail, credential encryption at rest, or full Iceberg REST spec coverage. Apache Polaris and Dremio are the honest recommendations for that today.