Skip to content
Centriu
Centriu Axis

Plaintext-Token Allowlist and Org-Scope Join Discipline Automation: Two Quiet Rules Behind Eight AI-Agent Tools

Building eight new AI-agent tools onto Maestro's paid-media data meant confronting two specific, documented facts about the underlying tables head-on rather than writing generic queries against them. First: the table storing a connected advertising account's credentials keeps its access and refresh tokens as plain, unencrypted text — so every one of these tools that touches that table reads it through a fixed, explicitly named list of columns (platform, account name, connection status, token expiry, last sync time), never a broad, wildcard selection that could accidentally pull a live credential into a response an agent might read, log, or repeat back. Second: the campaigns table itself is scoped by an individual user's identifier, not by organization — meaning the database's own row-level security does not, on its own, prevent one organization's campaign data from being readable through a query that doesn't explicitly account for that gap. Every one of the eight tools closes it the same way: an explicit join through the client record's own organization column, in the query itself, rather than trusting a security layer that does not cover this specific table.
Named columns, never a wildcard
Explicit org join, not just RLS
Screen showing scheduled queue routing configuration
Named columns in, never a wildcard.

A general-purpose safeguard has edges, and a specific table can fall outside them

A platform-wide security mechanism, like row-level security scoping every table by organization, is genuinely valuable specifically because it removes the need to think about that boundary in most individual queries. The risk that mechanism creates is a false sense of universal coverage: the moment ONE table in the schema is scoped by something other than organization — a user's own identifier, say, for reasons that made sense when that table was designed — every query against it silently falls outside the safety net everyone has learned to rely on elsewhere, unless someone explicitly knows that and compensates for it in the query itself.

How the underlying problem shows up before you fix it

A table storing a third-party credential — an OAuth access or refresh token — has no encryption applied to those specific columns, meaning anyone who can read the row at all can read the live credential in it.

A query against a table containing sensitive columns uses a wildcard selection (fetching every column) rather than an explicit, named list — meaning a sensitive column added to that table later is automatically included in every existing query without anyone deciding it should be.

A table's row-level security policy scopes it by a dimension (an individual user) that does not match the dimension every OTHER table in the system is scoped by (an organization) — creating exactly one place where the platform's otherwise-consistent security assumption quietly does not hold.

New code is written against a table without first checking what that table's OWN row-level security policy actually scopes by, on the assumption it must match the rest of the schema because everything else does.

An AI agent's tool surface is granted broad read access to a data domain without a specific, documented review of which of the underlying tables carry unencrypted secrets or a nonstandard access-control scope.

Why a single inconsistent table is harder to notice than a systemic gap

A security gap that affects every table in a schema tends to get caught early, precisely because it's so broad that almost any serious review trips over it. A gap confined to exactly one table, designed at a different time for reasons that made sense then, survives specifically BECAUSE most of the system genuinely does behave the way people expect — the one place it doesn't is easy to miss unless someone deliberately audits each table's actual security scope rather than assuming consistency across the schema.

How Centriu built eight new tools around two documented, table-specific facts rather than generic queries

Before writing any of the eight new agent tools onto Maestro's data, the migration that built them records two specific facts about the underlying schema directly in its own header — not buried in a separate document, but stated as the premise every one of the new functions has to respect.

The first: the table holding a connected advertising account's OAuth credentials stores its access and refresh tokens as plain, unencrypted text. That fact by itself doesn't change what the database contains, but it changes what a QUERY against that table is allowed to look like. Every one of the new tools that reads from this table does so through a fixed, explicitly named column list — platform, account name, connection status, when the token expires, when the account last synced — and nothing resembling a broad, wildcard selection appears anywhere in this code. The practical effect: even if the live token value sits one column over in the same row, no path built by this migration can accidentally include it in a response, because the response is built from an explicit list that simply does not name that column, rather than from "everything in the row minus whatever someone remembered to strip out."

The second: the campaigns table itself is scoped, in its own row-level security policy, by an individual user's identifier — not by organization, the dimension nearly every other table in the platform is scoped by. Stated plainly in this migration's own header: that means row-level security, on its own, does not enforce an organization boundary for this specific table. Every one of the eight new functions compensates for that explicitly, joining from campaigns through the owning client record to that client's own organization column, in the query itself, before any row is returned — rather than trusting a platform-wide security assumption that, for this one table, does not actually hold. A shared context-resolution function establishes which organization the requesting agent belongs to before any of the eight tools runs, and every one of them carries that same explicit join, not just the ones that seemed most obviously sensitive.

Both choices come from the same discipline: treating what a specific table's own security posture actually is as a fact to look up and design around, not an assumption to inherit from how most of the rest of the schema happens to behave.

What is actually built today

A fixed, explicitly named column list for every read of the connected-ad-account credentials table — platform, account name, status, token expiry, last sync — with no wildcard selection anywhere in this code path.

An explicit join from campaigns through its owning client record to that client's own organization column, present in every one of the eight new tools, compensating for row-level security not covering this table by user rather than by organization.

A shared, single context-resolution function establishing the requesting agent's organization once, used consistently as the basis for that same explicit join across all eight tools rather than re-derived differently in each one.

Execute permission on every one of the eight functions explicitly revoked from public, anonymous, and ordinary authenticated access, granted only to the internal role that runs them.

A documented, stated rationale — recorded directly in the migration that built this — for why each of these two disciplines exists, rather than a silent convention someone has to reverse-engineer later.

Confirmed today via direct inspection: all eight functions still perform the explicit organization join, and the credentials query still names its columns rather than selecting a wildcard.

A column that never gets a chance to leak (illustrative scenario, not a real client)

An agent asks to see the health of a client's connected ad accounts. The underlying table's row for that account genuinely contains a live access token in plain text, one column away from the fields the agent needs. Because the query names exactly which columns to return — platform, account name, status, expiry, last sync — the token column is never part of the result in the first place, regardless of what the agent asks for or how the request is phrased.

What changes operationally

Eight new AI-agent tools onto Maestro's paid-media data operate with two specific, documented risks in the underlying schema deliberately designed around rather than inherited by accident: a plaintext-credential table that can only ever be read through a safe, named column list, and a non-organization-scoped table that gets its organization boundary enforced explicitly, in every query, rather than left to a security layer that does not cover it.

When this is not the right fit

This page covers how these eight specific tools read from two particular tables with documented, nonstandard security characteristics — it is not a claim that every table these tools touch has the same characteristics, or a general audit of Maestro's entire schema; the migration's own header names these two facts specifically because they required deliberate handling, not because they are the only two tables in the system.

Trusting schema-wide consistency vs. checking each table's own facts

Writing a new query against the assumption that "row-level security already handles organization scope, like it does everywhere else" is a reasonable-sounding shortcut that fails silently, exactly once, on the one table where that assumption happens to be wrong. Centriu's approach treats each table's actual, specific security posture — what it's scoped by, whether it holds anything unencrypted — as a fact to check and design eight functions around individually, rather than a property to assume from how the rest of the schema generally behaves.

Related systems

Main system: Centriu Axis. Complementary when relevant: Centriu Maestro.

What it does NOT do

  • Does not read the connected-ad-account credentials table with a wildcard column selection anywhere in these eight tools — every read names its exact columns explicitly.
  • Does not rely on row-level security alone to enforce an organization boundary on the campaigns table — every one of the eight functions performs an explicit join through the client's own organization column.
  • Does not re-derive the requesting agent's organization differently in each of the eight functions — a single, shared context-resolution function establishes it once, consistently.
  • Does not grant execute permission on any of these eight functions to public, anonymous, or ordinary authenticated roles — access is restricted to the one internal role that runs them.
  • Does not claim this review covers every table in Maestro's schema — it documents two specific, nonstandard facts about two specific tables that required deliberate handling.
  • Does not encrypt the underlying credential columns as part of this migration — the fix scopes to how these eight new tools READ that table safely, not to changing how the credential itself is stored.

Security and governance

Every one of the eight new agent tools reads connected-account credentials through a named-column allowlist rather than a wildcard selection, and enforces organization scope on the campaigns table through an explicit join rather than relying on row-level security alone. Any personal or business data involved remains subject to Brazil's LGPD (Law No. 13,709/2018). Full detail on access control lives at /governanca and /iso.

Pricing and contracting

Included at no extra cost with any Centriu contract. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).

Frequently asked questions

Why does it matter that ad-account credentials are stored as plain text?

It means anyone able to read that table row at all can read the live token in it — so every query against it has to be deliberately built to never include that column, rather than trusting encryption to make a broad selection safe.

Why can't row-level security alone protect the campaigns table by organization?

Because that table's own security policy scopes it by an individual user's identifier, not by organization — the dimension nearly every other table uses — so a query that doesn't explicitly account for that gap falls outside the platform's usual organization boundary.

How do the eight new tools compensate for that gap?

Every one of them performs an explicit join from campaigns through the owning client record to that client's own organization column, enforced in the query itself rather than assumed from row-level security.

Could a live access token ever appear in one of these tools' responses?

No — every read of the credentials table names its exact columns explicitly (platform, account name, status, expiry, last sync), and the token columns are simply never part of that named list.

Does this fix encrypt the stored credentials themselves?

No — this specific work is about how these eight new tools read that table safely today; it does not change how the underlying credential is stored.

What does Centriu Axis cost?

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

See how Centriu keeps AI-agent tools safe around sensitive tables

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

Sources

  1. Centriu Axis — public product page — Centriu, 2026-07-20 · link(primária)
  2. Centriu Axis — public factsheet (API, JSON) — Centriu, 2026-07-21 · link
  3. Centriu Maestro — public product page — Centriu, 2026-07-20 · link(primária)
  4. Centriu Maestro — public factsheet (API, JSON) — Centriu, 2026-07-21 · link
  5. 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