Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,31 @@ jobs:
- run: sudo apt-get update && sudo apt-get install -y pkg-config libssl-dev
- run: cargo check --workspace --all-targets

# Every Dockerfile must build with the declared MSRV.
#
# A-36 raised `pangolin/Dockerfile` to match and missed
# `Dockerfile.tools`, which stayed on rust:1.88 and failed to build the
# instant the workspace MSRV moved past it - discovered while publishing
# 0.8.0, after the API image had already been pushed.
- name: Dockerfiles must pin the declared MSRV
working-directory: .
run: |
set -euo pipefail
declared=$(grep -m1 '^rust-version' pangolin/Cargo.toml | sed 's/.*"\(.*\)".*/\1/')
echo "declared MSRV: $declared"
status=0
while IFS= read -r dockerfile; do
pinned=$(grep -oE '^FROM rust:[0-9]+\.[0-9]+' "$dockerfile" | head -1 | sed 's/FROM rust://') || true
[ -z "$pinned" ] && continue
if [ "$pinned" != "$declared" ]; then
echo "::error file=$dockerfile::pins rust:$pinned but the workspace declares $declared"
status=1
else
echo " ok $dockerfile -> rust:$pinned"
fi
done < <(find . -name 'Dockerfile*' -not -path './node_modules/*' -not -path '*/node_modules/*' -not -path './pangolin/target/*')
exit $status

helm:
name: helm lint
runs-on: ubuntu-latest
Expand Down Expand Up @@ -612,6 +637,15 @@ jobs:
working-directory: pangolin
run: ./scripts/check_env_var_docs.sh

# The documentation carried 274 broken relative links: files moved between
# `docs/` subdirectories and the links pointing at them were never
# updated, plus references into a `planning/` directory that is not in
# this repository. A link that 404s is worse than no link - it sends a
# reader after something that does not exist while making the surrounding
# text look maintained.
- name: Every documentation link must resolve
run: ./scripts/check_doc_links.sh

- name: Every artifact must carry the same version
run: |
# Improvement #8. The "one version everywhere" property introduced in
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,38 @@ Production-readiness work: authentication hardening, credential encryption,
transactional correctness, MongoDB index management, the missing Iceberg
operations, operational tooling, and OpenID Connect.

### Fixed — the published container images

Four defects found by publishing 0.8.0 and then *running* what was published.
All 18 CI jobs passed over every one of them, because the `docker` job builds
images and does not exercise what it built.

- **`Dockerfile.tools` still pinned `rust:1.88`.** A-36 raised the API image to
match the workspace MSRV and missed the CLI image, which then failed to
compile the moment `rust-version` moved to 1.94 — mid-release, after the API
image had already been pushed. The `msrv` job now fails if any Dockerfile
pins a version other than the declared one.
- **The CLI runtime stage installed `libssl-dev`,** the development package,
shipping OpenSSL headers and static archives in the published artefact. Now
`libssl3`. This is the same defect A-36 corrected in the API image.
- **The UI runtime stage copied the entire `node_modules`,** publishing all 22
devDependencies — vite, playwright, vitest, svelte-check, the tailwind
toolchain — in the shipped image. Pruned to the single production dependency:
283MB to 141MB locally, and `node_modules` from the full toolchain to 2.3MB.
- **Neither CLI accepted `--version`.** `pangolin-admin --version` was a clap
parse error, so there was no way to ask a binary which build it was — on a
tool distributed mainly as a container image, where `latest` tells you
nothing.

Both images also ran as root and carried no OCI labels; both now run
unprivileged.

The release script's overwrite guard was all-or-nothing: if any of the three
tags existed it refused to start, so the partial 0.8.0 failure could only be
finished with `ALLOW_OVERWRITE=1` — which would also have re-pushed the good
API image over itself. It now skips images already published at the target
version.

### Added — operations: replicas, backup, performance

**The token-cleanup job never ran.** `start_token_cleanup_job` was defined, the
Expand Down
20 changes: 16 additions & 4 deletions ORGANIZATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,19 @@ The Pangolin repository follows a monorepo structure separating the core Rust im
pangolin-monorepo/
├── docs/ # Comprehensive Documentation
│ ├── api/ # API Reference and Swagger info
│ ├── architecture/ # Design and internals
│ ├── backend_storage/ # Per-backend storage notes
│ ├── best-practices/ # Deployment and usage guidance
│ ├── cli/ # CLI Command Reference
│ ├── features/ # Feature Guides (RBAC, Federation, etc.)
│ ├── getting-started/ # Installation and Architecture guides
│ ├── known-issues/ # Registry of quirks and temporary traps
│ └── ui/ # UI User Guide
│ ├── operations/ # Running it: parity, encryption, backup,
│ │ # performance, replicas, OIDC, runbook
│ ├── reference/ # Lookup tables and reference material
│ ├── ui/ # UI User Guide
│ ├── upgrading/ # Version-to-version upgrade notes
│ └── warehouse/ # Warehouse and credential-vending docs
├── pangolin/ # Core Rust Implementation (Workspace)
│ ├── pangolin_api/ # REST API Server (Axum)
Expand All @@ -27,13 +35,17 @@ pangolin-monorepo/
│ └── src/lib/ # Shared Components and Stores
├── pypangolin/ # Python SDK
│ ├── pypangolin/ # Source Code
│ ├── src/pypangolin/ # Source Code
│ └── docs/ # SDK-specific Documentation
├── scripts/ # Automation & Verification
│ ├── verify_pypangolin_*.py # SDK Verification Scripts
│ ├── test_release_*.py # End-to-End Release Tests
│ └── docker-build.sh # Build helpers
│ ├── release_smoke_test.py # Verifies a released image over HTTP
│ ├── backup_restore_drill.sh # Dumps, destroys, restores, verifies
│ ├── load_test.py # Load harness (client + server-side latency)
│ ├── bump_version.sh # One version across every artifact
│ ├── check_env_var_docs.sh # Env-var reference vs. the code
│ └── build_docker_sequential.sh # Builds and pushes the three images
├── tests/ # Integration Test Suites
│ └── pyiceberg/ # PyIceberg compatibility tests
Expand Down
33 changes: 20 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ See [Quick Start Guide](docs/getting-started/getting_started.md) for detailed se
*Production guides and operational wisdom.*
- **[Production Runbook](docs/operations/runbook.md)** - Health, metrics, incidents, upgrades, backup.
- **[Backend Feature Parity](docs/operations/backend-parity.md)** - Which features work on which backend.
- **[OAuth / SSO](docs/operations/oidc.md)** - Configuration, the 0.6.0 client change, and OIDC limitations.
- **[OAuth / OIDC](docs/operations/oidc.md)** - Provider setup, what is verified, and what still is not.
- **[Best Practices Index](docs/best-practices/README.md)** - Complete guide to operating Pangolin.
- **[Deployment & Security](docs/best-practices/deployment.md)** - Production checklists.
- **[Scalability](docs/best-practices/scalability.md)** - Tuning for high performance.
Expand All @@ -126,37 +126,44 @@ See [Quick Start Guide](docs/getting-started/getting_started.md) for detailed se

## 🚦 Project Status

**Current version: 0.6.0. Status: Alpha.**
**Current version: 0.8.0. Status: Beta.**

Pangolin is pre-1.0 software under active hardening. It is a capable catalog
with a broad feature set, and it is not yet something we would tell you to put
in front of a production data lake without reading the rest of this section.
Pangolin is pre-1.0 software. It is a capable catalog with a broad feature set,
and after two full audits it is substantially hardened — but see the honest
limits below and in [STATUS.md](STATUS.md) before putting it in front of a
production data lake.

0.6.0 is a **security release**. If you run anything earlier, upgrade: it fixes
**0.8.0 and 0.7.0 are security releases. If you run anything earlier, upgrade.**
Between them they fix a privilege escalation exploitable by any authenticated
principal, unauthorized cloud-credential vending, a logout that revoked nothing,
a remotely exploitable OAuth account-takeover path, a working default JWT
signing secret published in this repository, an authentication bypass, an
unauthenticated denial-of-service primitive, and an Iceberg commit path that
could silently fork snapshot lineage under concurrent writers. See
signing secret published in this repository, and an authentication bypass. See
[SECURITY.md](SECURITY.md) for the full list and the upgrade steps.

Note that **no 0.6.0 or 0.7.0 container image was ever published** — the release
pipeline could not complete. If you are running a Pangolin image older than
0.8.0, you are on 0.5.1 or earlier and predate every fix above.

### Maturity by area

| Area | Maturity | Notes |
|---|---|---|
| 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 below |
| Iceberg REST — full spec coverage | **Good** | `registerTable`, `listViews`, `viewExists`, `dropView` added in 0.8.0. `commitTransaction` is deliberately absent and `replaceView`/`renameView` are not implemented; see 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 |
| Authentication | **Good** | OIDC with PKCE, `id_token` validation via JWKS, and `iss`/`aud`/`exp`/`nonce` checks from 0.8.0. Rate limited per address and per account. GitHub is not an OIDC provider and cannot be validated this way |
| Audit logging | **Good** | 40+ actions, 19 resource types, plus authentication events from 0.6.0. Writes are best-effort and are not tamper-evident |
| Observability | **New in 0.6.0** | Prometheus metrics, request IDs, working `RUST_LOG`, 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 |
| MongoDB backend | **Beta** | No index management, no transactions, four known-failing tests |
| MongoDB backend | **Beta** | Index management and uniqueness constraints from 0.8.0. Still no versioned schema migrations, and multi-statement transactions only where the deployment provides a session |
| 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** | PostgreSQL wraps `delete_catalog`, `delete_branch` and `merge_branch`; MongoDB wraps `delete_catalog` where the deployment supports sessions. Branch creation by copy is still not atomic |
| Transactions for admin operations | **Good** | PostgreSQL and SQLite wrap `delete_catalog`, `delete_branch`, `merge_branch` and branch-creation-by-copy; MongoDB wraps `delete_catalog` where the deployment supports sessions, and reports the non-atomic fallback rather than hiding it |
| HA at N > 1 replicas | **Partial** | See below |
| Backup / restore / DR | **Undocumented and untested** | |
| Backup / restore / DR | **Documented and drilled** | `scripts/backup_restore_drill.sh` dumps, destroys and restores against a real database. Measured figures in [docs/operations/backup-and-recovery.md](docs/operations/backup-and-recovery.md). No point-in-time recovery |
| Warehouse credentials at rest | **Good** | AES-256-GCM when `PANGOLIN_ENCRYPTION_KEY` is set; plaintext with a startup warning when it is not |

### Known limitations

Expand Down
6 changes: 6 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@ configured", which at least failed closed.
Fixed in 0.7.0, with a CI job that builds every optional feature so it cannot
recur silently.

### Upgrading

- **To 0.8.0:** [docs/upgrading/0.7-to-0.8.md](docs/upgrading/0.7-to-0.8.md) —
MSRV 1.94, `PANGOLIN_ENCRYPTION_KEY`, MongoDB uniqueness constraints that can
fail on existing duplicates, rate limiting behind a proxy, and OIDC.

### Upgrading to 0.7.0

1. **Rotate every issued token.** B0a means any account may have minted a
Expand Down
60 changes: 46 additions & 14 deletions STATUS.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ pointing here. **Where they disagree with this file, this file is correct.**
Everything marked done below is verified by tests that run against live
PostgreSQL, MongoDB and MinIO, not by inspection:

- **63 test targets, 415 tests, zero failures**
- **19 CI jobs green**, including an authorization matrix, a four-backend parity
suite, both MongoDB topologies, an MSRV check, and a build of every optional
feature
- **65 test targets, 448 tests, zero failures**
- **18 CI jobs green on every push and pull request** — 14 job definitions, of
which `test` and `features` are matrices that expand to 2 and 4 runs. They
include an authorization matrix, a four-backend parity suite, both MongoDB
topologies, an MSRV check, and a build of every optional feature. Five further
jobs build and release the binaries, and run only on a `v*` tag.
- `cargo audit` clean; clippy at a ratcheted budget of 30 (from 314)

That standard exists because this project has repeatedly had things that
Expand Down Expand Up @@ -51,7 +53,7 @@ exited 25 seconds after startup passed all 18 CI jobs and the full suite.

| Item | Where |
|---|---|
| CI that actually runs — 19 jobs | 0.7.0 / 0.8.0 |
| CI that actually runs — 18 jobs per push, 5 more per release tag | 0.7.0 / 0.8.0 |
| A release pipeline that produces a release (it never had; `macos-13` was retired and hung every tag for 24h) | 0.7.0 |
| A release gate that verifies the published image over HTTP | 0.7.0 |
| Token-cleanup sweep that **runs** (it was dead code) and staggers across replicas | 0.8.0 |
Expand Down Expand Up @@ -119,18 +121,48 @@ blip, every revoked token is accepted again. Watch
- Eight accepted dependency advisories to re-check when dependencies move
- clippy 30 and svelte-check 150 backlogs, both ratcheted

## Not shipped
## Shipped

**0.7.0 and 0.8.0 are not published.** The work is merged to the branch and CI
is green, but the merge, tag, Docker push and PyPI upload have not been made.
The most recent published artifact is `alexmerced/pangolin-api:0.5.1` from
2025-12-30 — so **anything running Pangolin today is on 0.5.1**, which predates
every security fix listed above.
**0.8.0 is published**, on 2026-08-11:

The `SECURITY.md` advisory covers `< 0.7.0` for that reason.
| Artifact | State |
|---|---|
| GitHub release `v0.8.0` | 12 binaries across linux, macOS Intel, macOS ARM and Windows — **the first release this project has produced**; v0.4.0 through v0.6.0 had tags and no releases |
| PyPI `pypangolin` 0.8.0 | wheel and sdist |
| `alexmerced/pangolin-api` 0.8.0 + latest | linux/amd64 + linux/arm64, 58MB |
| `alexmerced/pangolin-cli` 0.8.0 + latest | linux/amd64 + linux/arm64, 39MB — the first CLI image since 0.5.0 |
| `alexmerced/pangolin-ui` 0.8.0 + latest | linux/amd64 + linux/arm64, 52MB — the first UI image since 0.5.0 |

Each was verified by pulling the published tag and running it, not by trusting
the build's exit code: the CLI reports `pangolin-admin 0.8.0` and runs as uid
10001, the UI serves `HTTP 200` as uid 1000 with a 2.3MB `node_modules`.

The PyPI token has been rotated. Still requiring a person: decide whether to
publish a GHSA once a fixed version actually exists.
Publishing them turned up four defects that every one of the 18 CI jobs had
passed over, because CI builds images and never runs what it built:

| Defect | Consequence |
|---|---|
| `Dockerfile.tools` still pinned `rust:1.88` | The CLI image could not compile once the MSRV moved to 1.94. It failed mid-release, after the API image had already pushed. |
| The CLI runtime stage installed `libssl-dev` | Headers and static archives shipped in the published artefact. A-36 fixed this in the API image and missed this one. |
| The UI runtime stage copied all of `node_modules` | All 22 devDependencies — vite, playwright, vitest, svelte-check, tailwind — published in the image. It is now 2.3MB. |
| Neither CLI accepted `--version` | No way to ask a binary which build it was, on a tool distributed mainly as an image. |

Both images also ran as root. CI now fails if any Dockerfile pins a version
other than the declared `rust-version`.

No 0.6.0 or 0.7.0 image was ever published, and no `-cli` or `-ui` image since
0.5.0, because the release pipeline could
not complete: `build-macos-intel` targeted the `macos-13` runner image, retired
in December 2025, and hung for the full 24-hour limit on every tag. Fixing that
exposed a second failure that had been unreachable behind it — the workflow
never declared `permissions: contents: write`, so creating a release was refused
with a 403. Both are fixed.

**Anyone running an image older than 0.8.0 is on 0.5.1 or earlier**, which
predates every security fix listed above.

Still requiring a person: decide whether to publish a GHSA now that a fixed
version exists.

## If you are deciding whether to run this

Expand Down
2 changes: 1 addition & 1 deletion deployment_assets/GITHUB_ACTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,6 @@ The workflow:

## Related Documentation

- [bin/README.md](./bin/README.md) - Using pre-compiled binaries
- bin/README.md - Using pre-compiled binaries
- [GitHub Actions Documentation](https://docs.github.com/en/actions)
- [Rust Cross-Compilation Guide](https://rust-lang.github.io/rustup/cross-compilation.html)
2 changes: 1 addition & 1 deletion docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This directory contains detailed technical documentation for the Pangolin archit
- **[Enums](./enums.md)**: Exhaustive list of system enumerations and their serialized values.

## 🔧 Interfaces & Logic
- **[System Traits](./traits.md)**: In-depth look at `CatalogStore` and `Signer` interfaces.
- **[System Traits](./catalog-store-trait.md)**: In-depth look at `CatalogStore` and `Signer` interfaces.
- **[Branching & Merging](./branching.md)**: Operational details of the "Git-for-Data" versioning model.
- **[Caching Strategy](./caching.md)**: multi-layered performance optimizations for metadata and cloud backends.

Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/handlers.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This document lists the handler modules responsible for the API implementation,
- `list_tables` / `create_table`: Table lifecycle.
- `load_table` / `update_table` / `delete_table`: Table operations.
- `report_metrics`: Metrics reporting.
- **Planned Refactor**: See [Iceberg Modularization Plan](../../planning/modularization_plan_iceberg.md).
- **Planned Refactor**: See Iceberg Modularization Plan.

## Tenant & Storage Management
**Files**:
Expand Down Expand Up @@ -58,4 +58,4 @@ This document lists the handler modules responsible for the API implementation,

## CLI Admin Handlers
**File**: `pangolin_cli_admin/src/handlers.rs` (Refactor to `handlers/` in progress)
- **Planned Refactor**: See [CLI Modularization Plan](../../planning/modularization_plan_cli.md).
- **Planned Refactor**: See CLI Modularization Plan.
Loading
Loading