Skip to content
Centriu
Centriu Maestro

OAuth Token Encryption-at-Rest Automation: A Column Privilege That Was Silently Ignored

When Centriu Maestro connects to Meta, Google, LinkedIn, or Google Business Profile on a client's behalf, the OAuth access and refresh tokens it receives get stored so the connection keeps working without asking the person to reconnect every time. A security check on the production database, run with a real user's own session, found those tokens stored in plain text and readable directly: a single `SELECT` returned four live, 350-character access tokens across the organization's real ad-platform connections. The application's own screen had always been careful to request only the non-sensitive columns, but that was a convention the interface chose to follow, not a control the database enforced — anyone with a valid session, a browser console, or a successful cross-site-scripting attempt could have called the database directly and read the credentials that spend real advertising money. The fix has two independent layers: the database privilege itself was corrected so no session can read the token columns at all (with a specific, measured finding along the way — revoking access to just the token column did nothing, because a broader table-level grant silently took precedence), and every stored token is now encrypted with AES-256-GCM using a key kept outside the database entirely, so even someone with a raw database backup or replica cannot read a token without also having the separate encryption key.
Table-then-column, then encrypted
Fails loud, never falls back
Command center screen with real metrics
A privilege that looked narrow, until it was measured.

A screen that only asks for the columns it needs is not the same as a database that only allows them

A well-written application screen requests exactly the data fields it displays and nothing more — it's good practice, and it genuinely limits what a normal user of that screen ever sees. What it doesn't do is limit what the database itself will hand over to anyone with a valid connection who asks for something different. A database privilege and an application's own restraint are two entirely separate mechanisms, and only one of them holds up against a request that doesn't come from the application's own well-behaved screen — a browser's developer console, a compromised script, or anyone who simply calls the same API the screen uses, but asks for a different column.

How the underlying problem shows up before you fix it

A sensitive credential (an OAuth access or refresh token, an API key) is stored in a plain, unencrypted column, readable in full by anyone with a database connection that has table-level read access.

An application's own screen carefully requests only non-sensitive columns, creating the impression of a control that is actually only a convention — nothing prevents a different caller from requesting the sensitive column directly.

A column-level privilege restriction (REVOKE on a specific column) is applied, but a broader, table-level grant for the same role remains in place — and the column-level restriction has no effect at all, silently, because database privilege precedence favors the broader grant.

A credential-write operation (disconnecting an integration, rotating a secret) still runs under the requesting user's own session privileges rather than a narrowly scoped, backend-only operation — meaning the session that can trigger the write still needs some privilege over the sensitive column.

Encryption is treated as equivalent to an access-control fix, when the two protect against different threats: access control stops a live session from reading a column it shouldn't; encryption at rest protects the same data from anyone who obtains a raw copy of the database itself (a backup, a replica, a database-management API) where access-control rules never apply.

Why a column-level privilege can be silently ineffective

Restricting who can read a specific column looks, on its face, like exactly the right level of precision — narrower than restricting the whole table, and specific to the one piece of data that actually matters. The complication is that most database systems check column-level privileges only as a fallback: if a role already holds a privilege on the table as a whole, that broader grant is what actually governs access, and the column-level restriction sitting alongside it is never consulted at all. A team that revokes access to a single sensitive column, confirms the change was applied, and moves on can reasonably believe the fix is complete — when in fact nothing changed, because the table-level grant that was never touched is quietly still the rule in force.

How Centriu Maestro closed both the read path and the raw-data path

The gap was found by directly probing the production database with a real, ordinary user's own session — not a hypothetical scenario. The result was unambiguous: a plain `SELECT` against the table storing platform connections returned four live, 350-character OAuth access tokens across the organization's real ad-platform integrations (Meta, Google, LinkedIn, Google Business Profile). The application's own service layer had always requested only the safe, non-sensitive columns — but that discipline lived entirely in application code, which any other caller with the same session privileges could simply ignore.

The first fix corrected the actual database privilege, and its own account of the process includes a specific, measured detail worth taking seriously: the team's first attempt was to revoke read access to just the token columns, leaving the broader table-level read grant in place — and measured directly, that column-level revoke had zero effect. The role still read all four tokens, because a table-level grant is checked first, and a column-level restriction is only consulted when no such broader grant exists. The working fix reverses the order: the table-level grant is revoked entirely, and read access is re-granted column by column, explicitly excluding the two token columns. Write access follows the same principle, narrowed to only the specific columns the application's screen genuinely needs to update — inserting and deleting a connection record now happens exclusively through a backend service role, never through a user's own session privileges, and disconnecting an integration (which needs to clear the token columns) was moved into a narrowly scoped database function specifically so a user's own session never needs write privilege over a secret column at all.

The second fix addresses a different threat the first one cannot: anyone who obtains a raw copy of the underlying data — a database backup, a replica, access through the database platform's own management interface — bypasses ordinary access-control privileges entirely, because those privileges only govern connections through the normal query interface. To close that gap, every stored token is now encrypted with AES-256-GCM before it's written, using a 32-byte key kept specifically outside the database itself, accessible only through a tightly scoped internal function restricted to the backend's own service identity — confirmed directly that neither an ordinary authenticated session nor an anonymous one can reach the function that resolves the encryption key. The stored format carries an explicit version marker specifically to support a gradual, reversible migration: a token stored under the old, unmarked format is recognized as legacy plain text and returned as-is, while every newly written or re-encrypted token carries the marker and the tooling needed to decrypt it. And critically, if the encryption key is ever unavailable for any reason, the encryption function fails loudly rather than silently falling back to writing the token in plain text — an absence of configuration is treated as a hard failure, specifically so the protection can never quietly turn itself off.

The fix's own end-to-end verification, performed directly against the production environment, is worth stating plainly: an ordinary user session attempting to select a token column now receives an explicit access-denied response; the application's own screen continues reading its real connections normally; the live backend service can still decrypt a real token and successfully call the ad platform's own API in a read-only capacity to confirm the credential still works; and every one of the organization's existing real tokens was confirmed migrated to the encrypted format, with zero left in plain text.

What is actually built today

Table-level read access to the connections table fully revoked for ordinary user sessions, replaced with an explicit, column-by-column grant that excludes both token columns entirely.

Write access narrowed to exactly the columns the application's own screen updates — creating and deleting a connection record now requires the backend's own service identity, never a user's own session.

A dedicated, narrowly scoped database function for disconnecting an integration, so clearing a token no longer requires giving a user session any write privilege over a secret column.

Every stored token encrypted at rest with AES-256-GCM, using a key resolvable only through a tightly restricted internal path — confirmed unreachable by both ordinary authenticated and anonymous sessions.

An explicit version marker on every encrypted value supporting a gradual, reversible migration — an unmarked value is recognized as legacy plain text rather than causing an error.

A hard failure, rather than a silent fallback to plain text, whenever the encryption key is genuinely unavailable — confirmed the encryption path cannot quietly disable itself.

The same request, a different answer (illustrative scenario, not a real client)

Someone with a valid, ordinary session opens their browser's developer console and issues a direct request for the token columns on their organization's own ad-platform connection — exactly the kind of request an XSS attempt or a curious extension might make. Before the fix, that request returned the real, usable access token. After the fix, the same request is refused outright by the database itself, regardless of what the requesting application intended to ask for.

What changes operationally

A credential that spends real advertising money on a client's behalf is no longer reachable through an ordinary user session under any circumstance, and even someone with a raw copy of the underlying database cannot read it without also possessing a separate, narrowly held encryption key. The protection holds regardless of which specific application code path is used to reach the database — it is enforced by the database itself, not by any particular screen's discipline.

When this is not the right fit

This fix specifically closes the read and raw-storage exposure of ad-platform OAuth tokens — it does not, by itself, rotate or invalidate credentials that may have been exposed before the fix was applied; that remains a separate, platform-side decision for whoever owns the connected ad accounts. It also does not change how the tokens are actually used to call Meta, Google, LinkedIn, or Google Business Profile's own APIs — only how they are stored and who can read them.

A privilege that looks narrow vs. one that is actually enforced

A column-level restriction applied without also removing a broader table-level grant looks, from the migration script alone, exactly like a real fix — and can pass a casual review while doing nothing at all. Centriu Maestro's fix was verified the only way that actually settles the question: by measuring the real, resulting access directly against the database, not by trusting that the privilege statement alone was sufficient.

Related systems

Main system: Centriu Maestro.

What it does NOT do

  • Does not rely on a column-level privilege revocation alone — the table-level grant is removed first, with column-level access explicitly re-granted only for non-sensitive fields, specifically because a column-only revoke was measured to have zero effect otherwise.
  • Does not allow an ordinary user session to insert or delete a platform-connection record — those operations require the backend's own service identity.
  • Does not require a user's own session to hold write privilege over a secret column in order to disconnect an integration — that operation runs through a dedicated, narrowly scoped database function instead.
  • Does not store any OAuth token in plain text going forward — every token is encrypted with AES-256-GCM before being written.
  • Does not fall back to writing a token in plain text when the encryption key is unavailable — the operation fails loudly instead.
  • Does not make the encryption key reachable by an ordinary authenticated or anonymous session — it resolves only through a tightly restricted internal path used exclusively by the backend's own service identity.

Security and governance

Ad-platform OAuth tokens are encrypted at rest with AES-256-GCM and are not readable by an ordinary user session at the database level, regardless of application code path. The encryption key is resolvable only by the backend's own service identity. Any personal or business data referenced in a stored connection 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 monthly subscription, with tiered plans. Values and terms come from the official pricing table at /precos (Centriu's central source — never restated here).

Frequently asked questions

Could a real user session actually read another person's ad-platform token before this fix?

The finding was more direct than that — an ordinary session could read the token columns on the table at all, confirmed by a real `SELECT` returning four live, usable access tokens.

Why didn't revoking access to just the token column fix the problem?

Because a broader, table-level read grant for the same role was still in place, and that broader grant is checked first — the column-level restriction had measurably zero effect until the table-level grant was also removed.

Does encryption at rest replace the need for access-control privileges, or the other way around?

Neither replaces the other — access control stops an ordinary session from reading a column through the normal query interface; encryption at rest protects the same data from anyone who obtains a raw copy of the database itself, where access-control rules don't apply.

What happens if the encryption key becomes unavailable?

The encryption operation fails loudly rather than silently writing the token in plain text — an absence of the key is treated as a hard failure, not a fallback to the insecure state.

Were any of the organization's existing tokens left unencrypted after the fix?

No — the fix's own verification confirmed every existing real token was migrated to the encrypted format, with zero left in plain text.

What does Centriu Maestro cost?

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

See how Centriu Maestro protects ad-platform credentials

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

Sources

  1. Centriu Maestro — public product page — Centriu, 2026-07-20 · link(primária)
  2. Centriu Maestro — 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