Skip to content
Centriu
Centriu Gauge

AI Chat Conversation Ownership Verification Automation: The Gap Between Two Correct Layers

Before a Gauge module reaches even a controlled pilot, it goes through an internal certification audit specifically designed to try to prove the module isn't ready. One audit found a critical defect in an AI chat feature: when continuing an existing conversation, the conversation's identifier arrived in the request and was used directly to save the new question and answer — and that save happened through the backend's own elevated database access, which bypasses the database's row-level security entirely. Simply sending someone else's conversation identifier — a colleague's, or a different organization's entirely — would have appended a private question and its answer to that person's own chat history. The unusual part is that every individual safeguard around this feature was independently correct: the access check that ran earlier in the request genuinely verified the requester's own organization, and the row-level security policy on the conversation table was itself written correctly. The gap existed structurally between those two correct layers — the identifier crossed from "what the client is asking for" into "what the server will execute" without ever being re-verified against who was actually asking. The fix re-derives the conversation using the requesting user's own restricted database access, so the database's own row-level security decides authoritatively whether the conversation belongs to them — and refuses outright, rather than silently starting a new conversation, when it doesn't.
Re-checked against the real rule
Refuses, never redirects silently
Person working on a laptop with notifications on screen
23 attempts. 23 refused.

Two individually correct security layers can still leave a gap between them

A layered security design — an access check at the API boundary, a row-level security policy at the database — is sound practice specifically because each layer catches what the other might miss. What that design doesn't automatically guarantee is that every value flowing through the system is re-verified at each layer it passes through. An identifier that a client supplies and a server later uses to perform a write can cross from "a value the requester is asking about" to "a value the server treats as already validated" without either individual layer doing anything wrong — the access check validated something real, the row-level policy is correctly written, and the specific value in between was simply never checked against either.

How the underlying problem shows up before you fix it

A client-supplied identifier for an existing record (a conversation, a document, an order) is used directly in a write operation without the server independently confirming that record actually belongs to the requester.

The write operation that uses the client-supplied identifier runs under elevated, backend-level database access — which bypasses row-level security entirely, so a policy that would have caught the problem never gets the chance to run.

An access check earlier in the same request path is genuinely correct — it validates the requester's real organization or identity — but validates a DIFFERENT value than the one later used in the write, so its correctness doesn't transfer.

A dedicated pre-launch security review or homologation audit — built specifically to look for exactly this class of defect — is the first process to systematically test cross-user and cross-organization access, rather than relying on each layer's own, narrower unit tests.

When an invalid or mismatched identifier is encountered, the system's default behavior is to proceed anyway (starting a new record, silently ignoring the mismatch) rather than treating the mismatch itself as a meaningful signal worth refusing outright.

Why the hole lived between the layers, not inside either one

A request handler that receives a client-supplied identifier and immediately writes using that identifier is, from a purely mechanical standpoint, doing exactly what it was built to do — appending a message to an existing conversation the person is already in. The defect isn't a mistake in the handler's own logic, and it isn't a mistake in the row-level security policy either; each is independently sound. The gap opens specifically because the write happens through a database connection with elevated, backend-level privileges — precisely so the operation can succeed regardless of row-level security — and nothing else in that specific code path re-establishes the ownership check the row-level policy would otherwise have provided for free. A defect shaped like this is genuinely difficult for either side's own testing to catch: a test of the access-check layer confirms it validates identity correctly, and a test of the row-level policy confirms it enforces ownership correctly, and neither test ever exercises the specific path where the elevated write bypasses the second layer entirely.

How Centriu Gauge closed the boundary between two correct layers

This finding came from a pre-launch certification audit built specifically to attempt to prove a module wasn't ready before it reached even a controlled pilot — not a routine test pass, but a dedicated review whose stated purpose was finding real defects, and it found a critical one. In the module's AI chat feature, continuing an existing conversation meant the conversation's identifier arrived directly in the request body and was used, unmodified, in the operation that saved the new question and its answer. That save runs through the backend's own elevated database access — the mechanism that lets the system perform legitimate operations regardless of row-level security — which meant the conversation table's own correctly-written ownership policy never had the opportunity to weigh in on that specific write at all.

The audit's own framing of the root cause is precise and worth stating directly: every individual defense was functioning correctly. The access check that ran earlier in the request genuinely confirmed the requester belonged to the organization they claimed. The row-level security policy on the conversation table was itself correctly scoped to ownership. Neither layer was broken. The identifier itself simply crossed the boundary between "what the client is requesting" and "what the server will actually execute" without being reclassified from an unverified claim into a checked fact at any point along the way — the gap existed structurally between two correct layers, not as a flaw within either one.

The fix deliberately avoids reimplementing the ownership rule a second time by hand — which would risk the same category of drift the original gap came from. Instead, before accepting a client-supplied conversation identifier for a continued conversation, the code re-derives that exact conversation using a database connection scoped to the requesting user's own restricted access — the same access level the row-level security policy was always designed to govern. If that lookup succeeds and the returned conversation genuinely matches the organization, context, and original owner the request claims, the identifier is accepted. If the row-level security policy itself would not return that record for this specific user — meaning, by the database's own authoritative rule, the conversation isn't theirs — the request is refused outright with an explicit, clear error, rather than silently starting a brand-new conversation in its place. The reasoning behind that specific choice is direct: supplying an identifier that isn't yours is not an honest typo deserving a quiet fallback, and treating it as ordinary user error would have hidden exactly the kind of attempt this fix exists to catch.

Verification, read directly from the audit's own closing report, was conducted against the production database using a reverted transaction — genuinely realistic conditions rather than a synthetic test environment — with three organizations and two collaborators involved, and systematic, deliberate attempts at cross-user and cross-organization access: 23 out of 23 attempts were correctly blocked.

What is actually built today

A client-supplied conversation identifier is re-derived through a database connection scoped to the requesting user's own restricted access before being accepted, rather than trusted and used directly.

The database's own row-level security policy — not a second, hand-written copy of the same rule — makes the authoritative decision about whether a given conversation belongs to the requesting user.

An explicit refusal, not a silent fallback to a new conversation, whenever a supplied identifier doesn't genuinely belong to the requester.

The specific write that appends a message to an existing conversation only proceeds once ownership has been independently re-confirmed for that exact request.

Verified directly against the production database using a reverted transaction, across three organizations and two collaborators, with 23 out of 23 deliberate cross-user and cross-organization access attempts correctly blocked.

Confirmed today via direct inspection of the current edge function: the ownership re-verification block remains in place, unmodified since the fix.

Someone else's identifier, refused rather than accepted (illustrative scenario, not a real client)

A person continuing their own AI chat conversation accidentally — or deliberately — sends a conversation identifier that actually belongs to a colleague at a different company sharing the same underlying database. Before the fix, that identifier would have been accepted, and the new question and its answer would have been appended to the colleague's own private conversation history. After the fix, the same request is refused outright the moment the re-derivation confirms the conversation doesn't belong to the requester — nothing is written, and nothing is silently redirected into a new conversation either.

What changes operationally

A conversation identifier arriving from a client request is no longer trusted on its own merit — it is independently re-confirmed against the requester's own restricted database access before any write occurs, using the same rule the database's row-level security was always designed to enforce. The specific failure mode this closes — an identifier crossing from an unverified request value into an executed database write without ever being reclassified — is exactly the shape of gap that individual-layer testing tends not to catch, which is precisely why a dedicated, adversarial certification audit exists.

When this is not the right fit

This fix closes one specific, critical finding from a broader pre-launch certification audit — the audit itself documented three additional high-severity findings in the same module (a dual-approval gate, a self-approval loophole, and a cost display issue) that are deliberately not the subject of this page, and the audit's own conclusion was that the module was ready only for internal testing at the time, pending further steps involving real people rather than code. This page describes the specific conversation-ownership mechanism only, not the module's overall readiness at any particular point in time.

Two correct layers vs. a boundary that connects them

A security review that separately confirms an access check is correct and a row-level policy is correct can still miss the gap between them, because neither individual test exercises the specific path where a value crosses from one layer's territory into the other's without being re-verified. Centriu Gauge's fix closes that exact boundary — not by adding a third, independent check, but by making the client-supplied value pass back through the same authoritative rule the row-level policy already enforces, so the two layers can no longer quietly disagree about who owns a conversation.

Related systems

Main system: Centriu Gauge.

What it does NOT do

  • Does not use a client-supplied conversation identifier directly in a write operation — it is re-derived through the requesting user's own restricted database access first.
  • Does not reimplement the ownership rule by hand a second time — the database's own row-level security policy makes the authoritative decision, avoiding a second copy of the rule that could drift from the first.
  • Does not silently start a new conversation when a supplied identifier doesn't belong to the requester — the request is refused outright with an explicit error instead.
  • Does not perform the conversation-continuation write through elevated, backend-level database access until ownership has been independently re-confirmed under the user's own restricted access.
  • Does not treat this single fix as a statement about the module's overall pre-launch readiness — the same audit documented three additional high-severity findings elsewhere in the module, not covered by this page.
  • Does not rely on the earlier access check's correctness to imply the conversation identifier is also valid — the two are independently verified.

Security and governance

A conversation identifier supplied in a request is independently re-verified against the requesting user's own restricted database access — governed by row-level security — before any write occurs, and a mismatch is refused outright rather than silently redirected. Any personal or business data referenced in a conversation remains subject to Brazil's LGPD (Law No. 13,709/2018). Full detail on access control lives at /governanca and /iso.

Pricing and contracting

Available by custom proposal, arranged directly with the team. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).

Frequently asked questions

How could a client-supplied conversation ID cause a cross-user data leak if the access check was correct?

The access check validated the requester's own organization — a genuinely different value from the specific conversation identifier later used in the write, which was never independently re-verified.

Why did the row-level security policy not catch this on its own?

The write that used the conversation identifier ran through the backend's own elevated database access, which bypasses row-level security entirely — the policy never had the opportunity to evaluate that specific operation.

What happens now if someone supplies a conversation ID that isn't theirs?

The request is refused outright with an explicit error — the system does not silently start a new conversation in its place.

How was the fix actually verified?

Directly against the production database using a reverted transaction, across three organizations and two collaborators, with 23 out of 23 deliberate cross-user and cross-organization access attempts correctly blocked.

Does this fix mean the whole module was certified ready for launch?

No — the same audit documented three additional high-severity findings elsewhere in the module and concluded it was ready only for internal testing at that point; this page covers only the conversation-ownership mechanism.

What does Centriu Gauge cost?

It is sold by subscription with a published starting price — exact current values are on the central pricing page.

See how Centriu Gauge verifies who actually owns an AI conversation

Reach our commercial team directly, or leave your details below — we'll follow up with guidance for your case.

Sources

  1. Centriu Gauge — public product page — Centriu, 2026-07-20 · link(primária)
  2. Centriu Gauge — public factsheet (API, JSON) — Centriu, 2026-07-21 · link
  3. Law No. 13,709/2018 — Brazil’s General Data Protection Law (LGPD) — Presidência da República (Brazil), 2018-08-14 · link

Last material update on .

By · AI-assisted production, with human review

We value your privacy

We use cookies to improve your experience, analyze site usage and support our marketing. You can accept all cookies or manage your preferences. To learn more, see our Cookie Policy.