Couchbase access governance — every SQL++ statement reviewed before it runs
Last updated
AccessFlow governs Couchbase through the same submit, analyse, approve, execute pipeline as a SQL database. Couchbase speaks SQL++ (N1QL), which is SQL-shaped, so the engine classifies statements with a purpose-built tokenizer and splices row-level security into the WHERE clause — with the shapes it cannot prove safe failing closed rather than running unfiltered.
- Family
- Document
- Query language
- SQL++ (N1QL)
- Connects via
- AccessFlow engine
- Default port
11210- Default SSL mode
DISABLE- Install
- One click — checksum-verified download
How AccessFlow connects
Couchbase is served by an AccessFlow engine on its own release line, pinned in the catalog to one file and one SHA-256 — so the Couchbase support you install today is byte-for-byte the copy everyone else installs, and a mismatch stops the load rather than warning about it. How an install works →
One native cluster client is cached per datasource. Connection strings are built from host and port, with the scheme chosen by SSL mode: plaintext when disabled, or TLS with trust-any-certificate, CA validation, or full SDK defaults as the mode tightens. A full URL can be supplied verbatim instead.
Scan consistency defaults to request-plus, meaning a read observes mutations submitted before it — the predictable choice for a governance proxy, where a reviewer approving a statement should not then see a stale result. The faster unbounded default is available if you would rather have it.
What AccessFlow understands
Exactly one statement per submission, with a trailing semicolon tolerated. The tokenizer strips comments and is aware of string and backtick literals and nesting depth. SELECT is a read; INSERT and UPSERT are inserts; UPDATE and MERGE are updates; DELETE is a delete; creating or dropping an index, scope or collection is DDL.
Statements execute in the bucket's default-scope query context, so a bare FROM users resolves inside the datasource bucket and is matched against a collection-level grant, while a fully qualified bucket, scope and collection path is carried through verbatim for the allow-list to check.
What it refuses
Three things fail closed with HTTP 422, and they are the SQL++ counterparts of the server-side-code ban every other engine has: the CURL() function, which would let a statement exfiltrate query results over the network from inside the database; JavaScript user-defined functions, in every form — creating, executing or dropping them; and the system: keyspaces.
Anything the classifier does not recognise is refused rather than passed through.
Row-level security and masking
Policies are ANDed into the WHERE clause of a simple single-keyspace read, update or delete, bound as named parameters and never concatenated; an existing WHERE expression is parenthesised first so operator precedence cannot widen it. An empty value list becomes a literal false — a deny-all, not a no-op.
The shapes the splice cannot provably filter fail closed: CTEs, subqueries, JOIN, NEST and UNNEST, USE KEYS, set operations, multi-keyspace statements, and MERGE — which is both a join-shaped DML and a carrier for an insert branch. An INSERT or UPSERT into a policied keyspace is rejected outright.
Results are materialised into the same tabular shape the SQL engines produce, with a SELECT * page unwrapped from its keyspace-alias wrapper so that masking rules written as collection.field still match, and masks applied per value by the shared masker.
What the rewrite actually does
A policy restricting the orders collection to the caller's region is ANDed into the WHERE clause, with the existing predicate parenthesised first so precedence cannot widen it:
-- submitted SELECT id, email FROM orders WHERE status = 'OPEN'; -- executed (row-security policy: orders.region = the caller's region) SELECT id, email FROM orders WHERE (status = 'OPEN') AND region = $af_rls_1; -- ^ named parameter
Had the statement been a join, an UNNEST, a USE KEYS lookup or a MERGE, it would have been refused with HTTP 422 — those are the shapes the splice cannot prove it has narrowed.
Introspection and dry-run
The connection test waits for the bucket and then runs a trivial query through the query service — proving SQL++ can actually execute, not merely that the key-value port answered. Introspection maps scopes to schemas and collections to tables, samples fields with a bounded read per collection, and reports the document key as the primary key, degrading to the key alone for a collection with no index.
A dry-run runs the governed statement through Couchbase's own EXPLAIN. On submission, an UPDATE or DELETE additionally gets an exact affected-document count from a SELECT COUNT(*) splice carrying the same row-security predicate.