The Root Cause: SECURITY DEFINER Misconfiguration

In May, a routine security probe at Supabase uncovered a critical vulnerability that had silently impacted multiple systems. The issue stemmed from a misconfiguration in PostgreSQL's SECURITY DEFINER functions, specifically how they inherited execution privileges. When a function is marked as SECURITY DEFINER, it executes with the privileges of the function's owner, not the user who calls it. Supabase, in its architecture, had configured certain functions with this setting, intending them to run with elevated database privileges for administrative tasks.

The problem arose because these SECURITY DEFINER functions were also marked as executable by PUBLIC. This meant that any user, including unauthenticated ones (often referred to as anon in Supabase's context), could execute these functions. Consequently, these users could leverage the function's elevated privileges to perform actions they were not explicitly authorized for, such as accessing sensitive data or modifying database objects.

This is akin to giving a master key to a building's janitorial staff (the SECURITY DEFINER function owner) and then leaving that master key accessible to anyone who walks in off the street (PUBLIC execution). The janitors can do their job, but now anyone can potentially access any room in the building.

Diagram illustrating PostgreSQL SECURITY DEFINER and PUBLIC execution inheritance

Incident 1: PII Backup Table Exposure

The first incident, discovered on a Tuesday morning in May, involved a PII (Personally Identifiable Information) backup table. The daily drift probe, which runs an aclexplode query across all public objects filtered for the anon role, flagged an anomaly. A row in a table containing sensitive personal data in plaintext was unexpectedly present and accessible. Further investigation revealed that an unauthorized user had leveraged a misconfigured SECURITY DEFINER function to create and potentially access this backup table, which should have been strictly protected.

The function in question, intended for internal database maintenance, was incorrectly exposed to public execution. This allowed an attacker, or even an accidental query, to invoke the function with superuser-like privileges, granting them the ability to create database objects like tables and populate them with data they shouldn't have had access to. The probe caught it because it was looking for unexpected table creations or modifications in public schemas, but the underlying vulnerability meant much more could have been compromised.

Incident 2: Mutation Silently Swallowed by SDK

While not directly caused by the SECURITY DEFINER issue, the second incident, detailed in a subsequent article, highlights the broader security and reliability concerns during this period. A data mutation, a crucial operation for updating records, was silently swallowed by the SDK. This means the operation was sent, but the server or database failed to process it, and critically, the SDK did not report any error back to the client. The user's data remained unchanged, but they had no indication of the failure.

This type of silent failure is a significant problem for application integrity. Developers rely on the SDK to provide feedback on the success or failure of operations. When mutations disappear into a black hole, it leads to data inconsistencies, corrupted application states, and a complete loss of trust in the system. While not a direct privilege escalation like the first incident, it points to a system under stress or undergoing changes that introduced subtle, yet impactful, bugs.

Incident 3: RLS Recursion and JWT Hooks

The third incident involved a complex issue with Row Level Security (RLS) policies and a JWT (JSON Web Token) hook. RLS is a powerful feature in PostgreSQL that allows fine-grained access control on a per-row basis. However, RLS policies can sometimes lead to recursion issues if not carefully crafted, especially when combined with other authentication or authorization mechanisms.

In this case, a JWT hook, likely used to extract user information or claims from a token for authentication or authorization purposes, triggered a recursive loop within the RLS policy. This meant that attempts to access data would repeatedly invoke the same RLS check, leading to performance degradation, timeouts, or even outright denial of service for legitimate users. Resolving this required a deep understanding of both PostgreSQL RLS and Supabase's JWT handling, indicating a sophisticated interplay of features that, when misconfigured, could break critical access controls.

Incident 4: Query Limit Without Warning

The fourth incident, also detailed in a separate article, concerned a query that stopped returning results after exactly 1000 rows, without any indication to the user or developer. This is a common behavior for certain database operations or client-side fetching mechanisms that implement default pagination or limits to prevent overwhelming the client or network. However, the critical flaw here was the lack of explicit signaling.

When a query returns fewer rows than expected, it's usually a sign that there's no more data. But when it returns exactly 1000 rows and stops, it's ambiguous. The user doesn't know if there are more results or if the dataset is simply that size. This ambiguity can lead to incorrect assumptions about data completeness, potentially causing flawed analysis or incomplete data processing in applications. It's a usability and data integrity issue stemming from how query results are handled and communicated.

The Broader Security Implications

The common thread across these incidents, particularly the first and most critical one, is the underlying security posture of the database layer. The SECURITY DEFINER inheriting EXECUTE TO PUBLIC vulnerability is a classic example of privilege escalation. It bypasses intended access controls by allowing unprivileged users to execute code with the privileges of a trusted user or role. Such vulnerabilities are severe because they can lead to complete system compromise, data exfiltration, or data manipulation.

For developers using platforms like Supabase, this underscores the importance of understanding the underlying database security primitives. While platforms abstract away much of the complexity, critical configurations like function ownership and execution context remain paramount. A silent data leak or an unauthorized table creation can have devastating consequences, especially when dealing with sensitive user data.

The fact that multiple incidents, some seemingly unrelated, occurred within a short period also suggests potential systemic issues. This could be due to rapid development, insufficient testing of new features, or a lack of comprehensive security audits. The platform's response to these incidents, particularly the initial communication, is also a point of concern raised by the author, highlighting the need for transparency and prompt disclosure when security vulnerabilities are identified.

What remains unaddressed is the long-term impact on developer trust. When a platform experiences multiple security and reliability issues, even if resolved, it can erode confidence. Developers and businesses rely on these services for their core infrastructure, and any perceived instability or security lapse necessitates a re-evaluation of risk and potential migration strategies. The journey to building robust, secure platforms is continuous, and incidents like these serve as stark reminders of the vigilance required.

Mitigation and Moving Forward

The immediate mitigation for the SECURITY DEFINER vulnerability involves ensuring that such functions are not executable by PUBLIC. They should be restricted to specific roles or users that require their elevated privileges. Developers should review their database functions, particularly those marked with SECURITY DEFINER, and verify their access control settings. The principle of least privilege should always be applied: grant only the necessary permissions to execute functions.

For the other incidents, the solutions would involve code fixes within the SDK, RLS policy adjustments, and clearer communication of query limitations. Supabase, like any platform provider, must invest in rigorous testing, automated security scanning, and a robust incident response plan. Transparency with users about vulnerabilities and their resolution is crucial for maintaining trust.

If you are a developer relying on Supabase, it is imperative to stay informed about security advisances and to audit your own database configurations. Ensure that your RLS policies are correctly implemented and that your application handles potential data inconsistencies or query limitations gracefully. The responsibility for security is shared, and understanding these potential pitfalls is the first step in protecting your application and your users' data.