All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
- [Both] Drop
client_ipfrom audit log — the recorded value was the Fly edge proxy address, not the real client, so it was misleading. The field is removed from the admin API response (AuditLogResponse) and the React audit page; the proxy stops writing it (alwaysNone) on both the deny path and the query-completed path.- DB column kept for backward compatibility but no longer populated
- Public docs (
guides/audit-debugging.md) updated
- [Infrastructure] Bump Fly.io VM to
shared-cpu-2x/ 512 MB — doubles memory headroom and burst CPU credits. The previousshared-cpu-1x/ 256 MB sized warm-idle around 150 MB, leaving ~50 MB of headroom that a single active session could exhaust under non-pushdown DataFusion execution (cross-datasource joins,json_length/json_keysUDFs). The spike-then-throttle pattern under heavy queries is reduced (longer burst window before kernel CFS throttling kicks in) but not eliminated — moving toperformance-1xwould be required to remove burst-credit throttling outright.
- [Docs] README points at unversioned screenshot filenames — image links no longer go stale across releases.
- [Admin UI] Anchor-coverage warning is harder to miss and easier to act on —
PolicyEditPagenow hoists theuseQuery(['policy-anchor-coverage'])to the page level so the side-nav and the panel share one cache entry. TheAnchor coveragesection in the secondary nav now carries a red count pill (SectionDef.indicator) when the policy will silently deny on any table; a top-of-page red banner ("This row filter will silently deny on N tables") with aReviewbutton appears on every section except the coverage section itself, and inherits the active section's max-width so it lines up with the form/content below. The coverage panel itself was redesigned: each broken table is its own card with bold header (data source +schema.table), per-column rows with a clearer reason line, and a tertiaryAdd anchor →button instead of a plain underline. The outer wrapper switched from red wash to a neutral white card so the per-table red still reads as the alarm without desensitizing. - [Admin UI] Consistent schema-alias labeling across relationship/anchor surfaces — new
effectiveSchemaName(schema_name, schema_alias)helper insrc/utils/schemaLabel.tsis now the single rule used byRelationshipsPanel,PolicyAnchorCoveragePanel, and the?focus=deep-link matcher: display the alias if set, raw upstream name otherwise. Dropdowns and selection contexts also render the upstream name in muted parens (pg.orders (postgres.orders)) so admins can verify the mapping without bouncing to the discovery wizard. The anchor table's relationship cell now shows the fullchild_schema.child_table.child_col → parent_schema.parent_table.parent_coljoin path (the child side was previously missing), and the column header was renamed fromVia relationshiptoResolves viato honestly cover both the FK-walk and same-table-alias modes. - [Admin UI] Anchor creation flow on deep-link from the policy warning — when arriving via the
Add anchor →link, the form now auto-selects the relationship if exactly one candidate's parent table contains the resolved column. Multiple viable candidates are sorted first in the dropdown and prefixed with✓. When the prefilled child table has zero relationships, the form replaces the previous one-line amber sentence with an empty-state guidance block: a "Switch to Same-table alias" button + an inlineFkSuggestionsListfiltered to the child table + a compactInlineManualRelationshipForm(parent table + columns) that auto-injects the new relationship into the anchor on success. The user no longer has to leave the anchor flow to fix the missing prerequisite.
- [Proxy]
schema_upstreamfield on the anchor-coverage response —AnchorCoverageTableEntrynow carries bothschema(effective name, alias if set) andschema_upstream(raw upstream name). The admin UI uses this to renderpg.payments (postgres.payments)in the warning panel when an alias is in play, while continuing to deep-link by the effective name (which is what the proxy keys columns by at query time). - [Admin UI]
SectionDef.indicatorslot onSecondaryNav— sections can now carry an optional{ tone: 'red' | 'yellow'; label: string; ariaLabel?: string }indicator that renders as a small count pill on the right of the nav button. Currently consumed byPolicyEditPagefor anchor coverage; the slot is generic so future sections can adopt it without further changes to the component.
- [Admin UI] Design standard and shared layout primitives — new
admin-ui/DESIGN.mddefines four page templates (T1 sidebar detail, T2 single form, T3 list, T4 audit) plus the header / save / destructive / modal rules they share. Eight new primitives land undersrc/components/andsrc/components/layout/:PageHeader,SecondaryNav+useSectionParam,SectionPane,DangerZone+DangerRow,ModalShell(with focus trap, Esc, click-outside),StatusDot+StatusChip,ConfirmDeleteModal, andConfirmDialog. Each ships with unit tests.admin-ui/CLAUDE.mdgained a "Design standard" pointer so future sessions pick up the rules before building a new page. - [Admin UI] Every edit page now uses the sidebar template — Policy, Role, User, and Attribute Definition edit pages all adopted the shared T1 shape pioneered by DataSource, so the admin UI shows a single consistent visual language across resources. Section layouts vary by content: Policy has Details / Assignments / Anchor coverage (row_filter only) / View as code / Activity; Role consolidated its former six tabs into Details / Membership (direct members + parent/child inheritance) / Access grants (datasource access + policy assignments) / Activity; User has Profile / Password / Activity; Attribute Definition has Details / Activity. Save shows a toast and stays on the page (previously Policy, Role, and User each had different auto-navigate quirks). All edit pages carry a danger zone with typed-name delete through the shared
ConfirmDeleteModal; soft-delete escapes are "Disable instead" for policies (is_enabled), "Deactivate instead" for roles and users (is_active), and a two-step force-delete for attribute definitions that orphan user values. - [Proxy] Transitive column resolution for row filters — row-filter policies can now reference columns that aren't on the matched table, resolved via admin-curated
table_relationship+column_anchorcatalog. Anchors come in two XOR-exclusive shapes: (a) FK walk (relationship_id), where the rewriter injects anInnerJointo a parent table that carries the column, up to a hardcoded depth of 3; (b) Same-table alias (actual_column_name), where the rewriter substitutes the column name inside the filter expression with no join — covers the case where one broad policy targets tables whose tenant-isolation column is spelled differently (tenant_idon one table,org_idon another). Resolution failures (missing anchor, depth > 3, cycle, qualified parent ref, or column missing from scan schema — including alias anchors pointing at a non-existent column) substituteFilter(lit(false))with acolumn_resolution_unresolvedwarn log. Seedocs/security-vectors.mdvector 73 for the threat model anddocs/permission-system.mdfor the operator-facing description. - [Proxy] Admin REST endpoints for relationships and column anchors —
GET/POST /datasources/{id}/relationships,DELETE /datasources/{id}/relationships/{rel_id},GET/POST /datasources/{id}/column-anchors,DELETE /datasources/{id}/column-anchors/{anchor_id}, andGET /datasources/{id}/fk-suggestions(livepg_constraintintrospection filtered to single-column FKs whose parent column is a PK or single-column unique). All mutations go throughAuditedTxnand are tagged on theadmin_audit_log. The XOR betweenrelationship_idandactual_column_nameis enforced server-side with a clear 422. - [Admin UI] "Relationships & column anchors" panel on the datasource edit page — new
RelationshipsPanel.tsxlets admins promote live FK suggestions or add relationships manually, and designate column anchors with a "Resolve via" radio for either the FK-walk shape (relationship dropdown scoped to the selected child table) or the same-table alias shape (column name with a datalist of the table's own columns). Longchild → parentlabels stack onto two lines so narrow-container layouts don't clip the action column. - [Proxy] Anchor-coverage preview endpoint —
GET /policies/{id}/anchor-coveragedry-runs the same column-resolution logic the runtime uses. For every(assigned table × column referenced in the row-filter expression)it returns a verdict:on_table,anchor_walk(with FK edge metadata),anchor_alias,missing_anchor(the silent-deny case), ormissing_column_on_alias_target. ReusesRelationshipSnapshotandexpr_column_names; no caching needed (sub-millisecond per table after the one-time parse). Returns empty coverage for non-row-filter policy types. - [Admin UI] Anchor-coverage warning on the policy edit page — new
PolicyAnchorCoveragePanel.tsxrenders onPolicyEditPageforrow_filterpolicies. Green banner when every assigned-table × referenced-column pair resolves; red panel listing each broken pair with a deep link to the datasource page where the missing anchor can be added. Catches the silent-deny failure mode (filter references a column that isn't on the target table and has no anchor → runtime substitutesFilter(false)) at edit time instead of when users notice empty result sets. Refetches automatically after save via the existingpolicy.versioninvalidation; hidden for non-row-filter policy types.
- [Admin UI] Destructive row actions removed from list pages; attribute-definition delete ported to the edit page —
UsersListPage,RolesListPage,PoliciesListPage, andAttributeDefinitionsPageno longer show a per-row Delete button. Deletion now happens on each resource's edit page through the danger-zone flow (typed-name confirmation + soft-delete escape). The attribute-definition page's custom "force delete on FK-constraint error" flow was ported to the edit page's danger-zone modal: a first Delete attempt may fail with a force-required error, and the button then re-labels itself "Force delete" for a second click. Inline relationship + column-anchor deletes inRelationshipsPaneluse the newConfirmDialoginstead of nativeconfirm(). - [Admin UI] Create pages use breadcrumb headers and navigate to the new resource's edit page —
DataSourceCreatePage,RoleCreatePage,PolicyCreatePage, andAttributeDefinitionCreatePagereplaced the← Back to Xbutton with aPageHeaderbreadcrumb matching the detail-page pattern. DataSource and Policy creation now land on/{resource}/{newId}/editon success instead of the list, so users can immediately continue configuring the thing they just made. - [Admin UI] Policy "View as code" is always expanded in its section — the legacy click-to-expand toggle made sense when the code view was one of several stacked cards on a scroll page, but in the new sidebar layout the user has already clicked "View as code" in the secondary nav to reach the section. The second click is pure friction. The YAML/JSON switcher and Copy button now sit in the section header alongside an always-rendered code block.
- [Admin UI] Sidebar logo and favicon — added a
logo.svgnext to the "BetweenRows" brand text in the admin sidebar header, and afavicon.svgfor the browser tab. - [Docs] Public documentation site sidebar restructure — reorganized the VitePress sidebar into five top-level sections (Start, Concepts, Features, Guides, About), with Policy Types, Template Expressions, and Decision Functions nested under Policies. Deleted standalone reference pages (
reference/policy-types,reference/audit-log-fields,reference/cli,reference/admin-rest-api,about/changelog,about/license) — content either folded into the guide it documents or replaced with an external link to the canonical source on GitHub (LICENSE,CHANGELOG.md).
- [Docs] Repositioned from "alpha" to "beta" — single canonical page (
docs-site/docs/about/license.md, now "License & Beta Status") describes pre-1.0 stability, what's stable vs. less stable, and the recommended posture for early adopters. Scattered "alpha" caveats across README, SECURITY.md, the VitePress sidebar/footer, and a dozen docs pages have been deleted or reframed — substance kept (pin tags, read changelog, file issues), positioning dropped from places that were just repeating the canonical disclaimer. Skipping the alpha→beta transition removes a fuzzy intermediate gate; the next stage is 1.0 with API stability commitments.
- [Docs] Switch public documentation site domain to
docs.betweenrows.dev— centralize URLs + OG image indocs-site/docs/.vitepress/constants.ts, add a path-gated Cloudflare Pages deploy workflow (.github/workflows/docs-site-deploy.yml), and enable Cloudflare Web Analytics via automatic edge injection. Remove obsoletedocs-site/internal/notes and the commented-out docs-site CI job. - [Docs] Tokenize release version across docs pages — markdown files use a
{{VERSION}}token substituted at build time fromconstants.tsvia a Vite pre-transform. Covers rendered HTML, the copy-as-markdown.mdoutput, andllms-full.txt. Future releases bump one file instead of ~13. - [Docs] README polish and marketing alignment — headline + pillars rewritten to match the
wwwlanding page (A fully customizable data access governance layer.), addeddocs.betweenrows.devlink and a screenshots table linking intodocs-site/docs/public/screenshots/, replaced the stale0.11.0Docker tag example with a pointer to the Tags page, and routed the permission-system and roadmap cross-references into the public docs. Relaxed the documentation-architecture rule in.claude/CLAUDE.mdsoREADME.md,SECURITY.md, andCONTRIBUTING.mdmay link intodocs-site/for self-contained GitHub rendering (code trees still may not). - [Admin UI] "Report an issue" footer link — bumped to the new
docs.betweenrows.devdomain. - [Repo]
.github/ISSUE_TEMPLATE/config.yml— directs security reports to GitHub Security Advisories and questions to Discussions. - [Repo]
SECURITY.mdlinks — rewritten to reference in-repo files instead of external docs URLs, so the GitHub Security tab is self-contained.
- [Docs] Public documentation site — full VitePress site at
docs-site/published todocs.getbetweenrows.com.- Installation (Docker, Fly, from source), Start (introduction, quickstart), Concepts (architecture, policy model, security overview, threat model), Guides (policies, users/roles, attributes, data sources, decision functions, audit debugging, recipes), Reference (REST API, config, CLI, policy types, template expressions, audit fields, glossary, demo schema), Operations (backups, upgrading, troubleshooting, known limitations, rename safety), About (roadmap, changelog, license, report an issue).
concepts/threat-model.mdauto-transcludesdocs/security-vectors.mdso the public threat model stays in lockstep with the design source.
- [Docs]
SECURITY.md— root-level vulnerability disclosure policy.
- [Admin UI] List-page truncation polish — Attributes, Policies, and Roles list tables now truncate long names and descriptions with tooltip titles instead of letting them push the table layout.
- [Admin UI] "Report an issue" footer link — now points at
docs.getbetweenrows.com/about/report-an-issueinstead of the GitHub issues page. - [Docs] Threat model H1 —
docs/security-vectors.mdretitled from "Security Vectors" to "Threat Model" so the transcluded public page has the right heading.
- [Both]
/docs-synccommand — new Claude Code workflow that detects drift betweendocs/+ source anddocs-site/, presents findings for review, and applies approved edits.- Diff mode (
/docs-sync <range>), full-codebase audit (--full), and single-page audit (--page <path>). - Runs automatically as step 2 of
/release.
- Diff mode (
- [Docs]
docs-site/.gitignore—**/.vitepress/{dist,cache}/now matches at any depth to guard against stray builds from the wrong cwd.
- [Proxy] Extensive policy enforcement tests for aggregates, HAVING, window functions, CTEs, and subqueries — +1240 lines in
policy_enforcement.rscovering how column masks, column denies, and column allows interact withCOUNT(DISTINCT),GROUP BY/HAVING,ROW_NUMBER() OVER (ORDER BY ...), CTEs, and subqueries. Ensures masked values cannot leak through aggregates or window ordering. - [Docs] Security vectors documentation overhaul — major expansion of
docs/security-vectors.mdwith new attack vectors, defenses, and test back-references;docs/permission-system.mdupdated in lockstep. - [Demo] Ecommerce demo refresh — new
compose.demo.yaml, newsetup.shautomation script, updatedschema.sqlandseed.py, refreshedpolicies.yamlandrequirements.txt, and a rewritten README.
-
[Proxy] BREAKING:
ctx.query.tablesis now an array of objects, not strings — decision functions withevaluate_context = "query"previously receivedctx.query.tablesasstring[](e.g.["public.orders"]). It is nowArray<{datasource, schema, table}>, so decision function JS must access the fields explicitly. Bare references likeSELECT * FROM ordersnow also resolve to the session's default schema (e.g.public) rather than an empty schema segment, so qualified and unqualified references produce identical entries.Migration for any decision function that inspected
ctx.query.tables:// Before: ctx.query.tables.includes("public.orders") ctx.query.tables.some(t => t.startsWith("public.")) // After: ctx.query.tables.some(t => t.schema === "public" && t.table === "orders") ctx.query.tables.some(t => t.schema === "public")
-
[Admin UI] Form polish — small tweaks to
CatalogDiscoveryWizard,DecisionFunctionModal,PolicyForm, andDataSourceEditPage.
- [Proxy] Security: bare table references could bypass schema-scoped policies — unqualified references like
FROM orderspreviously used an empty schema segment as the policy lookup key, so a policy targetingschemas: ["public"]would not match and could be bypassed by omitting the prefix. Bare references now fall back to the session's default schema, which DataFusion is already configured with at connect time (SET search_pathis blocked upstream byReadOnlyHook). Tracked as vector #71 indocs/security-vectors.md.
- [CI] Pre-commit hook runs
docs-siteVitePress build when docs-site changes are staged — guarded by adocs-site/node_modulescheck so fresh clones without docs deps installed are not blocked. - [CI]
docs-siteGitHub Actions job added then disabled — the job is commented out untildocs-site/lands in the repo.
- [Both] Remove git commit hash from version display — simplifies the build and
/healthendpoint- drops
GIT_COMMIT_SHORTenv var and git-basedbuild.rslogic from the proxy - sidebar now shows
vX.Y.Zinstead ofvX.Y.Z (abc1234)
- drops
- [Admin UI] Polish admin UI tables and forms
- attribute definitions table: combine display name + description into a single column, show
entity.keyas a monospaced code chip, and render value type as a type signature (e.g.list<string> ∈ {us, eu}) - roles list: fold description under role name, drop the standalone description column
- user form: add a permissions description explaining what admin access grants
- attribute definitions table: combine display name + description into a single column, show
- [Admin UI] Rename audit nav and standardize route paths
- sidebar section renamed from "Activity" to "Audit"
- nav labels changed to "Query Logs" / "Admin Logs"
/auditroute renamed to/query-auditfor consistency with/admin-audit
- [Both] Entity search, copyable IDs, and audit improvements — server-side search for attribute definitions, entity search dropdowns on audit pages, copyable UUID components across list pages, debounce hook, and new admin/query audit page tests
- Proxy: search filter on
GET /attribute-definitions, copyable IDs in audit responses, policy enforcement test coverage for missing attribute defaults - Admin UI:
CopyableIdcomponent,EntitySelectcomponent,useDebouncehook, admin audit & query audit page tests
- Proxy: search filter on
- [Both] Version display — app version and git commit hash shown in sidebar footer
- Proxy:
/api/versionendpoint serving version fromCargo.toml+ build-time git commit - Admin UI:
useVersionhook, version display in Layout
- Proxy:
- [Both] Debounced search and
keepPreviousData— replaced form-submit search with real-time debounced search across all list pages (Users, Roles, Policies, Data Sources, Attributes); addedkeepPreviousDatato prevent layout flash during transitions - [Admin UI] Sidebar navigation redesign — grouped nav into Access Control / Data / Activity sections with Heroicons; added "Report an issue" link in footer; username prefixed with
@ - [Admin UI] Default value UX improvements — type-specific placeholders, inline NULL badge when empty, icon-based clear button in attribute definition form
- [Admin UI] Attribute definitions table — added entity type column, reordered entity type filter before search input
- [Admin UI] Audit timeline ��� reduced page size from 20 to 5 for inline timelines; left-aligned pagination
- [Admin UI] Table header styling — consistent
text-xssizing across all list page headers - [Both] NULL terminology standardized — replaced inconsistent "no default (null)" phrasing with explicit "NULL" across UI, docs, code comments, and security vectors
- [Proxy] Zero-column scan — fixed
EmptyProjectionFixRulehandling when all columns are denied
- Tenant as custom attribute — the built-in
tenantcolumn onproxy_userhas been removed; tenant is now managed entirely through the ABAC attribute definition system- Migration 055 drops the
tenantcolumn fromproxy_user {user.tenant}template variable still works — resolves from the user's custom attributesBR_ADMIN_TENANTenv var removed (was already deprecated)tenantis no longer a reserved attribute key — can be created/deleted like any other custom attribute- Admin UI tenant field removed from user forms and list pages
- Migration 055 drops the
- BetweenRows rebrand — renamed from QueryProxy to BetweenRows across CLI, admin UI, Dockerfile, and configuration files
- Auto-persisted secrets — encryption key and JWT secret now follow a three-tier resolution: env var → persisted file → auto-generate and save
- Keys are persisted to
.betweenrows/state directory alongside the database, surviving container restarts without explicit env vars - Persistence warning on startup if the state directory is missing alongside existing data (likely unmounted volume)
- Data directory inferred from
BR_ADMIN_DATABASE_URLfor consistent state file placement
- Keys are persisted to
- Startup banner — displays version and tagline on boot
- Linux aarch64 support for Javy —
build.rsnow downloads the correct Javy binary forlinux/aarch64(ARM servers, Graviton, etc.) - Docker quickstart compose —
compose.quickstart.yamlfor one-command local setup - Governance workflows roadmap — detailed design for three-tier governance (none → draft → code) with sandboxes, YAML-as-code, and CI/CD deployment
- README rewritten as user-facing quickstart — streamlined for new users with Docker quick start, 5-minute walkthrough, configuration reference, and policy overview
- Developer docs moved to CONTRIBUTING.md — architecture details, data model, API reference, and performance notes relocated from README
- Fly.io deployment docs — extracted to
docs/deploy-fly.md - SQLx logging suppressed below DEBUG —
sqlx_loggingnow only enabled whenRUST_LOGincludes DEBUG or lower, reducing noise in defaultinfomode - Dockerfile sets
BR_ADMIN_DATABASE_URL— explicitly sets the SQLite path to/data/proxy_admin.dbfor consistent data directory detection
.betweenrows/added to.gitignore— auto-persisted state directory excluded from version control
- Decision function test context — mock context in the expression editor nested user attributes under an
attributeskey instead of flattening them as top-level fields onctx.session.user, causing runtime errors when testing functions that access custom attributes (e.g.ctx.session.user.departments). Added cross-reference comments betweencontext.rsandDecisionFunctionModal.tsxto prevent future drift.
- Flatten user attributes in decision function context — Custom attributes are now first-class fields on
ctx.session.user(e.g.,ctx.session.user.region) instead of nested underctx.session.user.attributes. Built-in fields (id,username,tenant,roles) always take priority on collision.- BREAKING: Existing decision functions referencing
ctx.session.user.attributes.*must be updated toctx.session.user.*
- BREAKING: Existing decision functions referencing
- Expression editor with autocomplete — Filter and mask expression fields in PolicyForm now use a CodeMirror editor with
{user.*}template variable autocomplete (built-in + custom attribute definitions). - Server-side expression validation — New
POST /policies/validate-expressionendpoint and "Check" button in the expression editor to validate filter/mask syntax before saving. - ORM-derived reserved attribute keys — Reserved user attribute keys are now computed from the
proxy_userORM columns (+ virtual fields likeroles), preventing accidental collisions with DB-level field names. - Conditional policy documentation — Comprehensive ABAC expression patterns and conditional policy examples for all five policy types added to
docs/permission-system.md. Conditional Policies marked as resolved in roadmap (covered byCASE WHENexpressions + decision functions).
- List attribute type for ABAC user attributes — new
"list"value type for attribute definitions, storing arrays of strings (max 100 elements)- Use with
IN ({user.KEY})in filter expressions; list expands into comma-separated placeholders - Empty lists expand to
NULL(effectively returning no rows) - API validates list values as JSON arrays of strings;
allowed_valuesconstrains individual elements - Decision function context includes list attributes as JSON arrays
- Admin UI: tag/chip input for free-form lists, multi-select checkboxes for lists with allowed values
- Extracted
AttributeDefinitionFormcomponent (matchesRoleForm/DataSourceFormpattern) - Added PolicyForm validation: blocks submit when decision function toggle is on but no function is attached or reference is stale
- DecisionFunctionModal: autocomplete hints for per-attribute
ctx.session.user.attributes.*, test context pre-populated from current user's real attributes - Config JSON validation: blocks save on invalid JSON instead of silently defaulting to
{}
- Use with
- User Attributes (ABAC) — schema-first attribute system for attribute-based access control
attribute_definitiontable defines allowed keys with types (string/integer/boolean), entity type scoping, optional enum constraints, and reserved key protection- User attribute values stored as JSON column on
proxy_userwith full-replace semantics and write-time validation - Typed
{user.KEY}template variables in filter/mask expressions (Utf8/Int64/Boolean literals) - User attributes available in decision function context as
ctx.session.user.attributeswith typed JSON values time.now(RFC 3339 evaluation timestamp) added to decision function context for time-windowed access- Admin UI: attribute definition list/create/edit pages, user attribute editor with type-aware inputs
- CRUD API with
?force=truecascade delete (SQLitejson_remove()/ PostgreSQLjsonb -) - 3 new migrations (052–054)
- Save-time expression validation — filter and mask expressions are validated at policy create/update time; unsupported SQL syntax returns 422 immediately instead of failing silently at query time
- CASE WHEN expression support added to the expression parser
- Shared WASM runtime — consolidated
WasmDecisionRuntimeinto a singleArcsingleton created at startup, shared byPolicyHook,EngineCache, andAdminState(replaces per-use instantiation) - Security vectors doc renamed —
docs/permission-security-tests.md→docs/security-vectors.md; added vectors 59–68 covering predicate probing, aggregate inference, EXPLAIN leakage, HAVING bypass, CASE expression bypass, window function ordering, timing side channels, and ABAC-specific vectors
- Decision functions — custom JavaScript functions that control when policies fire, evaluated in a sandboxed WASM runtime
- Two evaluation contexts:
session(evaluated once at connect) andquery(evaluated per query) - Configurable error handling:
skip(policy doesn't fire) ordeny(query blocked) - Console log capture with configurable log levels
- CRUD API with test endpoint for dry-running functions against mock contexts
- Integrated into visibility-level evaluation:
column_denyandtable_denypolicies respect decision function results at connect time
- Two evaluation contexts:
- Decision function admin UI — modal for creating/editing decision functions with CodeMirror editors
- JavaScript and JSON editors with
ctx.*/config.*autocomplete - Templates for common patterns in create mode
- Test panel with client-side pre-check and server-side WASM execution
- Fire/skip/error result badges, shared function warning, optimistic concurrency
- PolicyForm integration: toggle-based attachment (create new / select existing / edit / detach)
- JavaScript and JSON editors with
- Stale decision function reference dead-end — detaching a deleted function now correctly reveals create/select buttons instead of leaving the user stuck
- Testcontainers leak — label containers and clean up orphans to prevent Docker resource exhaustion during test runs
- RBAC with transactional audit enforcement — role-based access control with
AuditedTxnwrapper- Roles with hierarchical membership (BFS traversal, cycle detection, depth cap)
- Policy assignments scoped to user, role, or all
AuditedTxnensures every admin mutation is atomically committed with its audit log entries- Role deactivation/reactivation cascades to policy visibility
- Datasource access gating by role membership
- Remove LICENSE from git history — dropped LICENSE commit from history, added
LICENSE*/LICENCE*to.dockerignore
- Scan-level column masking — Column masks now apply at the
TableScanlevel viatransform_upinstead of only at the top-level Projection, preventing CTE and subquery nodes from bypassing masks by changing the DFSchema qualifier.- Masks run before row filters so filters evaluate against raw (unmasked) data
- Integration tests for multi-table JOINs with scoped column deny, CTE mask bypass prevention, subquery mask enforcement, and combined mask+deny+filter scenarios
- Dependency upgrades — Rust 1.94, DataFusion 52.3, Vite 7, TypeScript 5.9
- Read-only hook test assertions — use
as_db_error()instead ofto_string()for reliable error matching - Row filter projection expansion — fix CI test failures related to projection expansion and
table_denyaudit status - Unused variable warning — fix compiler warning in
catalog_handlers
- CI actions upgraded to v5 —
actions/checkoutandactions/setup-nodeupdated from v4 to v5 for Node.js 24 support
- Flat policy type model — replaced the obligation model with a flat
policy_typefield- 5 types:
row_filter,column_mask,column_allow,column_deny,table_deny - Removed
column_accessaction field; type alone determines behavior - 5 new migrations (019–023) to migrate existing schema
- 5 types:
- Zero-trust column model — qualified projection for JOINs; per-user column visibility enforcement
- Cast support — SQL type cast expressions now handled in query processing
- Catalog hints — contextual hints surfaced in the catalog discovery UI
- Policy-centric assignment panel — rule assignment UI redesigned around policies rather than datasources
- Datasource assignment on create — assign a datasource when creating a new rule
- Audit status tracking — queries now record a status field in the audit log
- Audit write rejections — rejected write queries are now captured in the audit log
- Container-based integration tests — replaced manual test scripts with a Docker-based test suite
- Column mask and row filter bugs — fixed incorrect mask application and cross-policy row filter interactions
- Audit duration and rewritten query — fixed these fields not being recorded correctly
- SPA routing — production build now serves
index.htmlfor client-side routes
- Policy system — configurable row filtering, column masking, and column access control via named policies assigned to datasources and users
policy,policy_version,policy_obligation,policy_assignment,query_audit_logdatabase tables (migration 007)PolicyHookreplacesRLSHook; supportsrow_filter,column_mask, andcolumn_accessobligation types- Template variables (
{user.tenant},{user.username},{user.id}) with parse-then-substitute injection safety - Wildcard matching (
schema: "*",table: "*") in obligation definitions access_modeon datasources:"policy_required"(default) or"open"- Optimistic concurrency via
versionfield (409 Conflict on mismatch) - Immutable
policy_versionsnapshots on every mutation for audit traceability - Deny policies short-circuit with error before plan execution
- 60-second per-session cache with
invalidate_datasource/invalidate_userhooks
- Policy API — admin-only CRUD and assignment endpoints
GET/POST /policies,GET/PUT/DELETE /policies/{id}GET/POST /datasources/{id}/policies,DELETE /datasources/{id}/policies/{assignment_id}
- Query audit log — async logging of every proxied query
GET /audit/querieswith pagination and filtering by user, datasource, date range
- Visibility-follows-access — per-connection, per-user filtered
SessionContext- Users only see tables and columns their policies permit
- Policy changes take effect immediately without reconnect
- JSON/JSONB support —
jsonandjsonbcolumns via DataFusion v52 anddatafusion-functions-json->/->>operators and JSON UDFs available in queries- Filter pushdown to upstream PostgreSQL for supported operators
- EXPLAIN support —
EXPLAIN <query>returns a PostgreSQL-compatible single-columnQUERY PLANresponse - Admin UI — Policies — list, create, and edit policies with an obligation builder; inline enable/disable toggle
- Admin UI — Policy Assignments — assign/remove policies per datasource with optional user scope and priority
- Admin UI — Query Audit — paginated audit log with original query, rewritten query, and applied policy snapshots
- Demo e-commerce schema —
scripts/demo_ecommerce/with schema, seed script, and example policies - Docs —
docs/permission-system.md(user guide) anddocs/security-vectors.md(security test plan)
- Arrow encoding — migrated to
arrow-pg; handler simplified; removedarrow_conversionandsql_rewritemodules
- CI/CD — split into CI (tests on every push to
main) and CD (publish + deploy onv*tag only)- Docker images tagged
X.Y.ZandX.Y; deploy uses explicit version tag for prod traceability workflow_dispatchadded for manual redeployment of an existing version
- Docker images tagged
- Password toggle visibility on login/password fields
- Password complexity validation
- Catalog viewer page for browsing the discovered catalog
- Button on the data source list view to open the catalog viewer
tsc -btypecheck failure inclient.test.ts; alignedtypecheckscript accordingly
- TypeScript errors in test files (
as unknown ascasts, unused imports) that were silently ignored by Vitest/esbuild but caught bytscduring Docker build
- Add
typecheckscript (tsc --noEmit) toadmin-uiand run it in the pre-commit hook before tests, so type errors are caught locally before CI
/commitslash command for Claude Code/releaseskill with semver Docker image tagging
- Admin-ui test suite with Vitest, integrated into CI
- Multi Data Source Management: The proxy now supports connecting to multiple, dynamically configured upstream data sources.
- Data Source Admin API & UI: New endpoints and UI pages for creating, editing, and testing data source configurations.
- User-to-Data Source Access Control: Implemented a many-to-many permission model to assign users to specific data sources.
- Encryption at Rest: Sensitive data source configuration fields (e.g., passwords) are now encrypted with AES-256-GCM in the database.
- Engine Cache: Implemented a cache for DataFusion
SessionContexts, one for each active data source, to improve performance and resource management. - Structured Logging: Replaced
println!withtracingfor structured, level-based logging.
- Authentication Flow: The PostgreSQL
databaseparameter in the connection string is now used to select the target data source. - Project Version: Incremented crate versions to
0.2.0to reflect new feature set. - Schema Alias Support: Catalog discovery now supports schema aliases for more flexible data source mapping.
- Per-Column Selection: Catalog discovery wizard allows selecting individual columns per table.
- Idle Connection Timeout: pgwire proxy now closes idle connections after a configurable timeout.
- Fly.io Auto Stop/Start: Deployment is configured to automatically stop and start machines based on traffic.
- Initial implementation of the PostgreSQL wire protocol proxy.
- Authentication for proxy users via Argon2id password hashing.
- Basic query processing using the Apache DataFusion engine.
- Rudimentary admin REST API for user management.
- Initial Admin UI for listing and creating users.