Skip to content
Row & column security for VGI

Row- and column-level security, enforced adversarially.

Rowfence is a policy-driven proxy that sits in front of any VGI worker and decides, on every single query, which rows, columns, and functions a caller is allowed to touch — without asking the worker to police itself. Part of the VGI suite; a Query Farm product.

What is Rowfence?

A dedicated hop between DuckDB and the worker

A VGI worker doesn’t have to implement access control itself to get it. Point ATTACH at Rowfence instead of the worker directly, and every query — every row, every column, every function call — passes through Cedar policy evaluation before the worker ever sees it. There’s no passthrough mode: enforcement is always on.

It’s sold as part of the VGI suite, not bundled into it — a standalone Query Farm product, hosted for you or run entirely on your own infrastructure. It isn’t open source, and there’s no repository to self-host from.

New to VGI? Start there first.

Rowfence enforces policy on top of VGI — it isn’t useful on its own. If you haven’t met the protocol yet, start with VGI to see what a worker is and how ATTACH reaches one.

HOSTED BY QUERY FARM queries scoped scan DuckDB Rowfence Worker

policies.cedar

permit(
  principal in Vgi::Group::"analysts",
  action == Vgi::Action::"Read",
  resource in Vgi::Table::"main.customers"
);

forbid(
  principal,
  action == Vgi::Action::"Read",
  resource
)
when { resource.pii == true }
unless { principal in Vgi::Group::"compliance" };
What it enforces

Not just tables — rows, columns, and functions

Everything below is the same Cedar model, applied at a different grain. Full detail on each of these — including how row rules can pull a live boundary from another VGI worker and cache it — is in the technical docs.

Cedar policies

Access control in Cedar — the same open policy language AWS built for Verified Permissions — against an entity model that mirrors your catalog: servers, schemas, tables, columns, functions.

Row-level filtering

Ordered branches inject a WHERE clause built from the caller’s own identity. Entitlements can resolve from a static list or a live query against another VGI worker — a subscriber list, a geographic boundary, anything.

Column-level enforcement

A forbidden column is removed from the schema DuckDB is told about, not just filtered from results — there’s no column to select. Column-level statistics are denied outright under a column policy.

Function-level access

VGI workers expose functions as well as tables — an LLM call, a scoring model, a metered API. The same permit/forbid model gates Execute on a function exactly like it gates Read on a table.

Doesn’t trust the worker

The proxy discovers the real schema itself and enforces against that discovery, not whatever the worker advertises. Every error path denies by default — none of them fall through to “allow.”

Audit logging

JSONL, one line per event — every row filter injected, every column denied, every entitlement resolved. An optional decision journal makes past decisions replayable offline.

Why you need this

The moment more than one person can query it, someone has to decide who sees what

“The worker will just be careful” isn’t a policy. These are the situations that come up almost immediately once real customers or colleagues start attaching to something you built — and the actual policy that handles each one.

Selling the same table to different customers

A subscriber in California doesn’t need Alaska’s rows, and shouldn’t be able to query outside what they bought — same schema, same SQL, different rows per caller.

# row-filters.yaml
branches:
  - scope: "earthquakes"
    when: 'context.claim.region == "california"'
    expr: "region = ${claim.region}"

  - scope: "*"
    grant: none   # fail closed — no match, zero rows

Keeping PII out of reach by default

Give analysts the table, not the personally identifiable columns — unless they’re on the compliance team.

forbid(
  principal,
  action == Vgi::Action::"Read",
  resource
)
when { resource.pii == true }
unless { principal in Vgi::Group::"compliance" };

Splitting finance and sales by role, not by table

One unified schema — what a person can see still depends on their department, not which table they happened to query.

forbid(principal, action, resource)
when { context.schema == "finance" }
unless { principal in Vgi::Group::"finance-managers" };

Gating an expensive function call

A geocoder or LLM-backed function that costs real money per call shouldn’t be open to every caller — meter it the same way you’d meter a table.

forbid(
  principal,
  action == Vgi::Action::"Execute",
  resource in Vgi::Function::"geocode"
)
unless { principal in Vgi::Group::"premium-tier" };

All four are real syntax, shown in full (with the row-level mechanism explained in depth) in the technical docs →

Running it

Hosted or on-prem

Query Farm sells Rowfence two ways — running it for you, or running entirely inside your own network so the proxy never leaves your infrastructure.

HOSTED BY QUERY FARM queries scoped scan DuckDB Rowfence Worker

Hosted

Query Farm runs Rowfence for you, in front of a worker you already run yourself or one hosted on Orchard.

YOUR INFRASTRUCTURE queries scoped scan DuckDB Rowfence Worker

On-prem

Run it inside your own network, in front of a worker that never has to leave your infrastructure either.

Both shapes support a single worker behind stateless, load-balanced replicas, and a multi-tenant gateway mode for routing many different backends behind one proxy — the shape for a vendor brokering access to more than one downstream source. A compliance bundle — canned access-review reports and decision-replay summaries built on the audit log — is on the roadmap, not shipped yet.

In context

How this compares

Row- and column-level security itself isn’t the differentiator — Immuta, Satori, and others in that category already do it well. What’s different is what it’s enforced in front of, and how it’s deployed.

Warehouse-native governance platforms

Immuta, Satori, and similar tools connect to a fixed list of warehouses and databases — Snowflake, Databricks, BigQuery, and the like — through native integrations, and are primarily sold as SaaS.

Sits in front of any VGI worker — a live API, a custom service, a warehouse, anything the protocol reaches — because enforcement happens at the protocol layer, not against a connector list. Hosted or fully on-prem, your choice.

A proprietary policy builder

Most platforms in this category express rules through their own UI-driven builder or a vendor-specific rule format, portable only within that product.

The same open policy language AWS built for Verified Permissions — not invented for this page. permit/forbid over principals, actions, and resources, not a bespoke syntax.

Get Rowfence in front of a real worker

Hosted or on-prem — tell us about your worker and your policy needs, and we’ll get you set up.