Skip to content

Build1 publisher3 min readPublished

A missing sequence GRANT broke production behind 21 working RLS policies

A solo-built WhatsApp CRM enforces its tenant boundary with row-level security on 21 tables, and the privilege that actually stopped a production insert sat on a table's auto-increment sequence, granted to one database role and not the other.

The Engineer · Build desk

Illustration accompanying A missing sequence GRANT broke production behind 21 working RLS policies

What happened

  • A production n8n workflow on a shared-schema Postgres CRM failed to create a handoff notification, and the Postgres error named a sequence rather than the table or any row-level security policy.
  • The migration that created the notifications table granted INSERT on the table to both database roles but granted the sequence's USAGE and SELECT only to the API's role.
  • The fix was a follow-up migration adding the sequence grant, plus a standing rule that new tables carry their sequence grant in the same migration file as the table grant.

Compiled by The EngineerSomething wrong?How this is made

Why it matters

  • constraint RLS answers which rows a role may see and says nothing about whether that role can obtain an ID, so a tenant boundary that passes review can still block writes.
  • cost With no staging environment, the audit for this class of defect is a person reading each migration, and the BYPASSRLS exception buys a fresh hand review every time a workflow touches the database.
  • exposure One database role can read every tenant's rows in a schema holding tax and immigration documents, and only workflow review keeps it inside one tenant.
  • decision Choosing shared-schema-plus-RLS to onboard tenants in minutes moves the per-object grant checklist into every migration a developer writes.

An INSERT that needs an auto-generated ID has to clear privilege checks on two objects: the table, and the sequence the ID comes from [19]. Postgres keeps those two grants separate, and the automation role's default privileges had never been set up to inherit sequence access the way table access had [8]. So the role held INSERT on the notifications table and still could not insert a row [7][9].

That is an awkward failure to read from outside the database. The error named a sequence, and the row-level insert looked like something that should have been allowed, so the obvious suspect, the RLS policy, was innocent [6]. The developer's account also describes the insert as failing silently at the sequence while everything else on the table worked fine [9]. The symptom a user would notice was a handoff notification that never got created [6].

Now count objects. The tenant boundary is 21 RLS policies, one per tenant-scoped table, keyed off a session variable the FastAPI backend sets on every request [4]. If each of those tables carries an auto-increment sequence and both database roles write to it, the same schema holds 42 grant-bearing objects and 84 role-object grants to keep straight [18]. A policy answers which rows a role may see [5]. A grant answers whether the role may touch the object at all [8].

The other gap in the model is deliberate. n8n holds the AI agent and the handoff logic, and it cannot easily set the per-request session variable the API sets, so its database role runs with BYPASSRLS [10]. The developer wrote that this is "a real, permanent blind spot in an otherwise fail-closed model", and that it has to be reviewed by hand every time a new workflow touches the database [11][12]. The alternative described in the post, rewriting the tenant boundary check into every n8n node by hand, was rejected as "more surface area for exactly the kind of mistake RLS exists to prevent" [13].

This transfers if you have a shared schema, more than one database role writing to it, and IDs generated from sequences. The context around it has no ops team and no staging environment: one VPS, and every migration runs straight against production [2], which is one way to guarantee your integration coverage is real. End customers submit SSN, ITIN and EIN-equivalent documents, and the post says every control had to be built into the product itself because there was no security budget [3].

The patch was one migration adding GRANT USAGE, SELECT ON SEQUENCE to the automation role [14]. The durable part is a writing rule: every migration that creates a table the automation workflows write to now includes the sequence grant in the same file as the table grant, checked while writing the migration [15]. The handoff control is built the same way, as a check immediately before the write. A per-conversation boolean flips when a human agent hits "Assume", the AI workflow reads that flag before every reply and stays silent when it is set, and the flag releases automatically 30 minutes after the last human reply [16]. The system has been in continuous production since April 2026, on release 2.2 [17].

What to watch

  • Whether the automation role ever gets a way to set the per-request session variable, which would retire the BYPASSRLS exception and its hand review.
  • Whether a second paying tenant onboards onto the same shared schema, adding roles to the same per-object grant checklist.
  • Whether a later release reports another privilege class, such as functions or other sequences, failing the same way the notifications sequence did.
Loading claim ledger
Loading source directory links
Loading share composer
Loading topic controls
Loading related stories