Skip to content
Centriu
Centriu Guardian

Device-Secret RPC Anonymous Execute Automation: A Revoke That Looked Complete and Was Not

Centriu Guardian's fleet migrations wrote a revoke statement removing execute privilege from the PUBLIC pseudo-role on every database function they created — a standard, seemingly complete way to lock a function down. It was not complete. This project's own default access-control configuration separately grants execute privilege to the anonymous and authenticated roles by name on every newly created function, and a grant made by name to a specific role is not removed by revoking from PUBLIC — the two are different privilege mechanisms entirely. Measured directly after the fleet migrations were applied: all 17 Guardian database functions still answered yes to "can the anonymous role execute this," including the specific function that mints a brand-new device secret, reachable by anyone through the database's own REST layer using nothing but the public, non-secret anonymous key. The fix revokes execute privilege explicitly from PUBLIC, anonymous, AND authenticated on every function, then grants it back deliberately in three separate bands matched to how each function is actually meant to be called — device functions to the roles the unauthenticated agent uses, console functions to logged-in users only, and purely internal functions to neither.
Revoke from PUBLIC ≠ closed
17 functions, three bands
Device enrollment and secret screen
A revoke that looked complete and was not.

A revoke that reads as complete can still leave a separate grant standing

Writing "revoke all ... from public" against a newly created function looks, and in many database configurations is, a complete way to close public access to it. The pattern breaks down specifically when a project's own configuration separately hands out access by a different mechanism — a named grant to a specific role, made automatically at creation time, that exists independently of whatever the PUBLIC pseudo-role is or is not allowed to do. Revoking from PUBLIC in that situation removes exactly what it says it removes, correctly — and leaves the other mechanism's grant completely untouched, with nothing in the revoke statement itself signaling that a second door exists.

How the underlying problem shows up before you fix it

A migration author writes a revoke statement targeting the PUBLIC pseudo-role specifically, following a pattern that closes access in many database configurations, without confirming what this particular project's own default access-control list separately grants to named roles.

The project's own configuration grants execute privilege to specific, named public-facing roles automatically on every new function — a mechanism distinct from, and unaffected by, whatever privilege the PUBLIC pseudo-role itself carries.

The gap is invisible to a reading of the migration SQL, because the SQL contains a revoke statement that looks complete and correct on its own terms — the mechanism that keeps the door open is a project-level default the migration text never mentions.

The affected functions are reachable directly by name through the database's own REST-style API layer, meaning a standing execute grant on a sensitive function is not a theoretical privilege — it is an immediately callable public endpoint.

The gap is found by measuring actual privilege on a real, already-migrated database (a direct privilege query naming the role and the function) rather than by re-reading the SQL that created the function — the SQL was written correctly for the mechanism its author had in mind; the database has a second mechanism the author did not account for.

How a standard-looking revoke was measured, found incomplete, and replaced

Centriu Guardian's fleet feature is backed by database functions that the frontend and the device agent both call directly. Every one of these functions, when first created, was accompanied by a revoke statement removing execute privilege from the PUBLIC pseudo-role — the conventional way to prevent a function from being callable by just anyone.

That revoke did not achieve what it looked like it achieved. This particular database project has its own default access-control configuration that separately grants execute privilege, by name, to the anonymous and authenticated roles on every newly created function — a distinct mechanism from PUBLIC's own privilege, and one that a PUBLIC-only revoke does not touch at all. The two privilege paths coexist independently; closing one says nothing about the other.

The gap was found the same way its table-level counterpart was found (covered on a companion page): by applying the fleet migrations to a real database and directly measuring the resulting privilege, rather than by re-reading the migration SQL. The measurement asked, for each function, whether the anonymous role specifically could execute it — and the answer was yes for all 17 Guardian database functions, including the one that mints a brand-new device secret. That function is reachable directly through the database's own REST-style API layer, meaning the standing grant was not an abstract privilege sitting unused — it was a live, callable endpoint requiring nothing but the public, deliberately non-secret anonymous key to invoke.

The fix rewrites the access grant for all 17 functions from the ground up: revoke execute explicitly from PUBLIC, the anonymous role, AND the authenticated role on every one of them — closing both privilege paths at once rather than assuming one revoke handles both — and then grant execute back deliberately, function by function, in three bands matched to how each is actually meant to be used. Six device-facing functions (device enrollment, heartbeat, alerts, incidents, benchmarks, and passport updates) are granted to both the anonymous and authenticated roles, because Guardian's own device agent has no user session at all — it authenticates using the anonymous key purely as transport, presenting a device-specific token that each function verifies internally before doing anything privileged. Six console-facing functions are granted to the authenticated role only, since whoever operates the fleet console is logged in, and each of those functions independently checks organization membership regardless. Four remaining functions are granted to neither role, because they exist only to be called from inside other functions that already run with elevated privilege, and have no legitimate reason to be reachable as a public database endpoint at all.

One further, documented complication surfaced while writing this exact fix: a fifth function, used to check organization membership, looked like it belonged in the "internal only" group, since nothing calls it directly from outside the database. It does not belong there — that function is referenced inside the row-level security policy expression on all 13 of Guardian's row-secured tables, and a policy expression is evaluated using the privilege of whoever is running the query that triggered it, not the privilege of whoever owns the table. Denying that function execute privilege to the authenticated role broke every logged-in user's ability to read anything from any of those 13 tables — caught immediately by the database itself refusing every query, not by a code reviewer reading the migration. The function was granted execute privilege after all, safely: it takes no identity as an argument, runs with elevated privilege internally, and answers only whether the CURRENT session's own authenticated user belongs to a given organization — nothing a user could not already learn by querying tables they already have legitimate access to. "Nothing calls this function from outside the database" and "nothing needs this function's execute grant" turned out to be two different claims, and only the first one was true.

What is actually built today

All 17 Guardian database functions have execute privilege explicitly revoked from PUBLIC, the anonymous role, AND the authenticated role — closing both the pseudo-role privilege path and the named-role default-grant path at once, rather than relying on a revoke that only addresses one of them.

Execute privilege is re-granted in three deliberate, documented bands: device-facing functions to the roles the unauthenticated agent actually uses (verified internally by a device token), console-facing functions to authenticated users only, and purely internal functions to neither role.

The device-secret-minting function and every other sensitive device function is no longer reachable as a public database endpoint by anyone holding only the anonymous key — each now requires the device-specific token it independently checks internally.

A row-level-security helper function that is referenced inside every one of Guardian's 13 protected tables' own policy expressions retains its execute grant for authenticated users specifically because policy expressions run with the querying user's own privilege, not the table owner's — a distinction the fix's own near-miss made concrete rather than theoretical.

The fix required no change to any function's own logic — it is entirely a correction to which roles may invoke each function, layered on top of functions that were already behaviorally correct.

A dedicated automated test reads the versioned migration SQL itself for the shape of this exact gap — a revoke naming only PUBLIC, or a grant with no corresponding revoke on the same function signature — so a future function added to the fleet cannot silently reopen the same path.

One public key, one freshly minted device secret (illustrative framing of the actual measured finding)

Before the fix, a caller holding nothing but Centriu Guardian's public anonymous key could call the device-secret-minting function directly through the database's own REST-style API — the standard "revoke all ... from public" written in the migration that created the function had no effect on this caller, because the anonymous role's execute privilege came from a separate, named default grant the revoke never addressed. After the fix, the identical call is rejected outright: the function's execute privilege was revoked from the anonymous role explicitly, and access is limited to the specific, deliberately scoped bands of functions the anonymous role is actually meant to reach.

What changes operationally

Every one of Centriu Guardian's 17 fleet database functions now has execute privilege scoped deliberately across three bands matched to real calling patterns, with both the PUBLIC pseudo-role privilege path and the named-role default-grant path closed explicitly — rather than relying on a single revoke statement that, in this project's own configuration, only ever closed one of the two.

When this is not the right fit

This automation governs the internal execute-privilege configuration underneath Centriu Guardian's own fleet database functions — it does not change what any function actually does when called by an authorized caller, does not add a customer-facing setting, and is specific to a database configuration where named-role default grants exist independently of PUBLIC's own privilege; a project without that specific default-grant mechanism would not have needed this exact correction, though the broader lesson — confirm what a revoke statement actually removes in your own database's configuration — applies generally.

Trusting a conventional revoke pattern vs. measuring the actual resulting privilege

Writing the conventional "revoke all ... from public" is a reasonable default and, in many database configurations, is genuinely sufficient — the risk is specific to any project where a separate mechanism (here, a default access-control list granting execute to named roles automatically) coexists with PUBLIC's own privilege, because nothing about the conventional revoke statement discloses whether that second mechanism is even present. Measuring actual resulting privilege directly against a real, migrated database — asking the database itself, for each role and each function, whether execute is actually possible — closes that blind spot regardless of how many independent privilege mechanisms a given project happens to have layered on top of each other.

Related systems

Main system: Centriu Guardian.

What it does NOT do

  • Does not change what any Guardian database function actually does once called by an authorized caller — this fix governs only which roles are permitted to call each function, not any function's own internal logic.
  • Does not add a new customer-facing access-control setting — the corrected privilege bands were always the intended design for these internal fleet functions.
  • Does not affect the device agent's own ability to call the functions it legitimately needs — those functions remain reachable using the anonymous key as transport, exactly as designed, verified internally by a device-specific token.
  • Does not retroactively confirm whether the device-secret-minting function was actually called by an unauthorized party during the window this gap existed — a team with that concern would need its own database-level call-audit logging for the affected period, outside the scope of this fix.
  • Does not overlap with the fleet table TRUNCATE fix — that fix closes a different privilege gap, at the table level rather than the function level, from the same source commit.

Security and governance

All 17 of Centriu Guardian's fleet database functions have execute privilege explicitly revoked from PUBLIC, the anonymous role, and the authenticated role, then re-granted in three deliberate bands matched to real calling patterns — closing both a pseudo-role privilege path and a named-role default-grant path that existed independently of each other. Any personal data referenced in device or fleet records 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

Why did revoking execute from PUBLIC not close public access to these functions?

Because this database project's own default configuration separately grants execute privilege by name to the anonymous and authenticated roles on every new function — a distinct mechanism from PUBLIC's own privilege. Revoking from PUBLIC removes exactly what it says and nothing more; it does not touch a grant made by name to a specific role.

Was the device-secret function actually callable, or is this a theoretical gap?

Measured directly: after the fleet migrations were applied, a live privilege query confirmed the anonymous role could execute all 17 Guardian functions, including the one that mints a device secret — and that function is reachable through the database's own public REST-style API layer, using only the non-secret anonymous key.

How are the 17 functions secured differently now?

In three explicit bands: six device-facing functions to the anonymous and authenticated roles (the agent has no user session and authenticates via a device token checked inside each function), six console-facing functions to authenticated users only, and four purely internal functions to neither role.

What was the near-miss with the organization-membership helper function?

It looked purely internal since nothing calls it from outside the database, but it is referenced inside the row-level security policy expression on all 13 protected tables — and a policy expression runs with the querying user's own privilege. Denying it execute broke every user's SELECT, caught by the database itself; it was granted execute for authenticated users after confirming it reveals nothing a user could not already learn from tables they already access.

Is this the same fix as the fleet table TRUNCATE page?

No — that page covers a table-level privilege gap (a default grant including TRUNCATE, which bypasses row-level security). This page covers a function-level privilege gap (a stale nominal EXECUTE grant that a PUBLIC-only revoke did not remove). Both come from the same source commit but are distinct PostgreSQL privilege mechanisms.

What does Centriu Guardian cost?

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

See how Centriu Guardian scopes device RPC execute privilege

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

Sources

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