We uncovered a critical remote code execution vulnerability in Snowflake-managed PostgreSQL, highlighting hidden risks in cloud services.
Varonis Threat Labs uncovered a remote code execution (RCE) vulnerability in Snowflake-managed PostgreSQL that turns an ordinary database privilege into control of the underlying server by chaining together legitimate features.
The security risk extends beyond a single database bug. Our research shows how trusted, managed cloud services can still contain hidden privilege chains that bypass customer-visible logging, undermine data integrity, and create attack paths beneath the layer security teams typically monitor.
The chain starts from a non-superuser role chained together with legitimate PostgreSQL behaviors to gain full OS-level command execution on the database host, surpassing Snowflake’s published mitigations without disabling them. A successful attack could allow an attacker to tamper with or destroy database files, sabotage recovery, and hide activity outside normal SQL audit trails.
We disclosed the chain to Snowflake and they quickly remediated the issue server-side. Snowflake customers do not need to take further action. Varonis Threat Labs debuted our discovery exclusively at BSides NYC in the Cloud Village in October 2026.
Varonis Threat Labs has discovered several PostgreSQL vulnerabilities, including remote code execution in AWS RDS via PL/Perl and PL/Rust and RCE in Azure Cosmos for PostgreSQL. Together, they demonstrate that privilege-escalation chains in managed PostgreSQL are a systemic pattern. Continue reading to see how we chained together Postgres features in Snowflake and a step-by-step breakdown of the chain.
Snowflake Postgres lets customers create, manage, and use PostgreSQL instances directly from the platform. According to Snowflake, each instance “runs a Postgres database server on a dedicated virtual machine managed by Snowflake”, and you connect to it directly using any standard Postgres client.
Like any managed database, Snowflake Postgres operates under a shared responsibility model. Snowflake manages the security of the database server, the virtual machine, and the surrounding infrastructure, while the customer is responsible for what Snowflake calls the Customer Configurable Security Controls.
A PostgreSQL instance running inside Snowflake — the starting point of our research into the managed database attack surface.
At a high level, the chain flowed as follows:
The starting point was snowflake_admin, a non-superuser role whose only notable attribute was CREATEROLE.
pg_write_all_data allows tampering with the pg_cron job table, whose jobs run over the local socket as any user with no password. We used it to execute SQL as postgres and escalate to superuser.
Snowflake blocks the usual SQL-to-OS primitives; we used PostgreSQL’s internal language and large objects to rebuild file read/write
We then combined that file write with PostgreSQL’s archive_command — a config-defined shell command that fires on every Write-Ahead Logging (WAL) archive — to reach arbitrary command execution (RCE) as postgres.
End result: full OS-level RCE on a managed database — defeating every published mitigation without disabling any of them.
Five links take a constrained, non-superuser role all the way to an OS command execution. Each one is an individually documented, intended PostgreSQL behavior. When chained together, they are catastrophic and enable code execution within the instance.

How the chain works link by link
CREATEROLE is widely treated as minor, but in practice it is one of PostgreSQL’s most underestimated privileges.
PostgreSQL 16 tightened CREATEROLE, but one edge survived. It can still self-grant predefined pg_* system roles, which have no “creator” and never requires a superuser to grant. snowflake_admin has rolcreaterole = true.
The CREATEROLE privilege is all we start with, and all we need.
In our research, we granted broad data access across the entire instance successfully.
With pg_read_all_data we could read every table and catalog, including pg_authid — which exposes each role’s credential verifier. Two superuser roles (postgres and snowflake_superuser) had rolsuper=true, but we needed a way to execute SQL as one of them.
That’s where pg_cron came in. The extension loads via shared_preload_libraries and, for each scheduled job with pg_write_all_data. We could write directly to the cron.job table, breaking the assumption that only trusted components modify it. We rewrote a job’s username to “postgres” and set its command to create a SECURITY DEFINER function that would run any SQL as superuser and grant itself back to us.

When the scheduler fired, it ran as postgres, created our function, and handed us the keys. We promoted our own role to snowflake_superuser and reached superuser without ever calling ALTER ROLE a SUPERUSER.
In this way, we reached full PostgreSQL superuser access without ever calling ALTER ROLE ... SUPERUSER which Snowflake blocks.
On self-hosted PostgreSQL, a superuser is effectively code execution. A managed provider’s job is to sever that equivalence. Reaching superuser only meant we’d arrived at the boundary Snowflake defends.
Superuser gave us the foundation to rebuild file read/write primitives — not command execution itself. Reading files enables reconnaissance; writing files stages payloads; the next stage turns into a running process.
We reached for the textbook SQL-to-OS primitives. Snowflake had blocked each one:
Every well-known path was a dead end — exactly where most security assessments stop.
Instead of attacking the functions, we asked how they’re built. Re-aliasing an internal C symbol via CREATE FUNCTION ... LANGUAGE internal is documented behavior. What this research shows is that a provider’s catalog-level blocklist can be fully reconstructed — because the compiled symbols remain in the binary — and chained to OS-level RCE.
The PostgreSQL documentation is remarkably candid:
“Granting users the EXECUTE privilege on pg_read_file(), or related functions, allows them the ability to read any file on the server that the database server process can read; these functions bypass all in-database privilege checks. … a user with such access is able to read the contents of the pg_authid table … as well as read any table data in the database.”
We started by confirming the internal language even existed on the instance. \dL lists procedural languages:

postgres=# \dLinternal is present. A direct binding to the C functions compiled into the server, including the very ones Snowflake “blocked., Creating LANGUAGE internal functions is superuser-only.

It worked. my_read_file does everything pg_read_file did with zero privilege checks. Snowflake’s block was a lock on the door handle; we bolted on a second handle. Every “blocked” primitive was one CREATE FUNCTION away.
Note: still confined to our own single-tenant VM
Before attempting code execution, this one primitive was enough to gut the host:
Reading passwd and key through our rebuilt my_read_file wrapper. The same C symbol Snowflake blocked, now exposed under a name we own.
Once you understand that “blocked” means “the SQL name was removed,” the rest falls the same way.
Listing directories. We rebound pg_ls_dir() through internal (prosrc = ‘pg_ls_dir’) alongside pg_stat_file to walk directories and locate files.
Writing files. lo_export(oid, path) writes anywhere the postgres user can with no sandboxing.
With arbitrary file write in hand, we targeted postgresql.auto.conf — the configuration file PostgreSQL reads on reload. We staged a payload as a large object and exported it directly over the config file using our rebuilt lo_export wrapper.
The payload overwrote archive_command — a GUC whose value PostgreSQL runs through /bin/sh -c on every completed WAL segment, as the postgres OS user. It is the cleanest server-side command sink in PostgreSQL, normally gated behind ALTER SYSTEM, which we now control through a different path.
From a single non-superuser role, we had full OS-level RCE — uid=26, the postgres user, on the instance managed by Snowflake.
As an alternative path, we also wrote and compiled a custom C extension directly on the host, loaded it via LANGUAGE c, and registered a SQL-callable shell function. This provided a durable, repeatable command-execution primitive but the archive_command method was sufficient to prove the chain.
Contents of postgresql.auto.conf lo_export overwrite.The archive_command now points to our payload.
|
Blocked Operation |
Bypass Used |
|---|---|
|
ALTER SYSTEM |
Direct file write to postgresql.auto.conf via lo_export |
|
COPY TO PROGRAM |
archive_command injection and custom .so extension |
|
pg_read_file() |
LANGUAGE internal wrapper bound to the same symbol |
|
ALTER ROLE ... SUPERUSER |
GRANT snowflake_superuser via SECURITY DEFINER backdoor |
|
COPY FROM/TO FILE |
lo_import / lo_export large-object interface |
Snowflake's hardening in action — every direct attempt to read files, execute programs, or grant dangerous roles is blocked.
A leaked admin credential is normally a rotate-and-move-on problem. This chain turns it into a permanent foothold for threats.
The attacker creates a stored function that executes OS commands. Because it's a database object, it persists independently of the credential that created it. Credential rotation doesn't touch it. Because the function runs as the postgres OS account — owner of the entire data directory — an attacker can bypass the database entirely:
Get more insights on the threat landscape from Varonis Threat Labs.
Blog_OpenSSH-RegreSSHion-Vulnerability
Snowflake remediated this chain server-side; managed customers require no action. The value below is for anyone who operates PostgreSQL, because every link in the chain is stock PostgreSQL working as designed.
The root cause is an over-granted CREATEROLE attribute combined with features that trust caller-supplied identity.
Use Snowflake token-based authentication. Replace static PostgreSQL passwords with short-lived Snowflake-issued tokens. This binds authentication to Snowflake's identity layer (MFA, SSO, centralized audit) and limits the window an attacker has if a credential is compromised.
Enable network policies. Restrict which IP addresses and ranges can connect to your Snowflake Postgres instance. Use AWS PrivateLink or Azure Private Link instead of exposing the instance over the public internet.
Apply least-privilege roles. Do not use snowflake_admin for application workloads. Create dedicated roles scoped to specific schemas and tables. Never grant CREATEROLE to application or service-account roles — it was the entry point for the entire chain.
Implement granular role-based access control (RBAC). Restrict database user privileges tightly. Define granular user and role-based access controls based strictly on operational needs, keeping the scope and duration of privileged access to a minimum.
Monitor for privilege escalation. Alert on GRANT statements that assign CREATEROLE, pg_read_all_data, pg_write_all_data, or membership in system roles. Periodically review pg_cron jobs and SECURITY DEFINER functions for anything unexpected.
We followed coordinated disclosure with Snowflake:
A single non-superuser role with CREATEROLE became full OS-level RCE on a managed PostgreSQL instance, defeating every published mitigation. None of the building blocks are novel alone, the escalation used PostgreSQL exactly as designed.
Because this attack lives beneath the database, most of it plays out where a managed customer simply can't see — so your leverage is narrower than it feels. Scope the roles you create with least privilege and lean on a platform whose security team takes this kind of hardening seriously as Snowflake's did here.
Snowflake had already hardened against the obvious SQL-to-OS paths before we ever showed up — real defense in depth, not box-checking. We reported the full chain in May 2026, and they moved quickly and remediated it server-side.
A managed database shifts operational burden to the provider but does not erase the customer’s role. Customers still own role provisioning and privilege scoping. If you run PostgreSQL yourself, treat least-privilege role design as a first-class control.
This is not just a database bug, it’s a cloud trust-boundary issue. Managed databases reduce operational burden, but they don’t eliminate risk from over-privileged roles, hidden platform-layer activity, or feature interactions that bypass normal audit trails. While Snowflake has remediated the chain, the broader lesson is that “managed” does not mean “invisible risk is gone.”
Stay up to date on the threat landscape with Varonis Threat Labs.