@@ -540,13 +540,19 @@ pub struct Security {
540540 #[ doc = "RLS Enabled No Policy: Detects cases where row level security (RLS) has been enabled on a table but no RLS policies have been created." ]
541541 #[ serde( skip_serializing_if = "Option::is_none" ) ]
542542 pub rls_enabled_no_policy : Option < RuleConfiguration < crate :: splinter:: SplinterRuleOptions > > ,
543+ #[ doc = "RLS Policy Always True: Detects RLS policies that use overly permissive expressions like USING (true) or WITH CHECK (true) for UPDATE, DELETE, or INSERT operations. SELECT policies with USING (true) are intentionally excluded as this pattern is often used deliberately for public read access." ]
544+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
545+ pub rls_policy_always_true : Option < RuleConfiguration < crate :: splinter:: SplinterRuleOptions > > ,
543546 #[ doc = "RLS references user metadata: Detects when Supabase Auth user_metadata is referenced insecurely in a row level security (RLS) policy." ]
544547 #[ serde( skip_serializing_if = "Option::is_none" ) ]
545548 pub rls_references_user_metadata :
546549 Option < RuleConfiguration < crate :: splinter:: SplinterRuleOptions > > ,
547550 #[ doc = "Security Definer View: Detects views defined with the SECURITY DEFINER property. These views enforce Postgres permissions and row level security policies (RLS) of the view creator, rather than that of the querying user" ]
548551 #[ serde( skip_serializing_if = "Option::is_none" ) ]
549552 pub security_definer_view : Option < RuleConfiguration < crate :: splinter:: SplinterRuleOptions > > ,
553+ #[ doc = "Sensitive Columns Exposed: Detects tables exposed via API that contain columns with potentially sensitive data (PII, credentials, financial info) without RLS protection." ]
554+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
555+ pub sensitive_columns_exposed : Option < RuleConfiguration < crate :: splinter:: SplinterRuleOptions > > ,
550556 #[ doc = "Unsupported reg types: Identifies columns using unsupported reg* types outside pg_catalog schema, which prevents database upgrades using pg_upgrade." ]
551557 #[ serde( skip_serializing_if = "Option::is_none" ) ]
552558 pub unsupported_reg_types : Option < RuleConfiguration < crate :: splinter:: SplinterRuleOptions > > ,
@@ -565,8 +571,10 @@ impl Security {
565571 "policyExistsRlsDisabled" ,
566572 "rlsDisabledInPublic" ,
567573 "rlsEnabledNoPolicy" ,
574+ "rlsPolicyAlwaysTrue" ,
568575 "rlsReferencesUserMetadata" ,
569576 "securityDefinerView" ,
577+ "sensitiveColumnsExposed" ,
570578 "unsupportedRegTypes" ,
571579 ] ;
572580 const RECOMMENDED_RULES_AS_FILTERS : & ' static [ RuleFilter < ' static > ] = & [
@@ -584,6 +592,8 @@ impl Security {
584592 RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 11 ] ) ,
585593 RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 12 ] ) ,
586594 RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 13 ] ) ,
595+ RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 14 ] ) ,
596+ RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 15 ] ) ,
587597 ] ;
588598 const ALL_RULES_AS_FILTERS : & ' static [ RuleFilter < ' static > ] = & [
589599 RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 0 ] ) ,
@@ -600,6 +610,8 @@ impl Security {
600610 RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 11 ] ) ,
601611 RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 12 ] ) ,
602612 RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 13 ] ) ,
613+ RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 14 ] ) ,
614+ RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 15 ] ) ,
603615 ] ;
604616 #[ doc = r" Retrieves the recommended rules" ]
605617 pub ( crate ) fn is_recommended_true ( & self ) -> bool {
@@ -671,21 +683,31 @@ impl Security {
671683 index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 10 ] ) ) ;
672684 }
673685 }
674- if let Some ( rule) = self . rls_references_user_metadata . as_ref ( ) {
686+ if let Some ( rule) = self . rls_policy_always_true . as_ref ( ) {
675687 if rule. is_enabled ( ) {
676688 index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 11 ] ) ) ;
677689 }
678690 }
679- if let Some ( rule) = self . security_definer_view . as_ref ( ) {
691+ if let Some ( rule) = self . rls_references_user_metadata . as_ref ( ) {
680692 if rule. is_enabled ( ) {
681693 index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 12 ] ) ) ;
682694 }
683695 }
684- if let Some ( rule) = self . unsupported_reg_types . as_ref ( ) {
696+ if let Some ( rule) = self . security_definer_view . as_ref ( ) {
685697 if rule. is_enabled ( ) {
686698 index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 13 ] ) ) ;
687699 }
688700 }
701+ if let Some ( rule) = self . sensitive_columns_exposed . as_ref ( ) {
702+ if rule. is_enabled ( ) {
703+ index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 14 ] ) ) ;
704+ }
705+ }
706+ if let Some ( rule) = self . unsupported_reg_types . as_ref ( ) {
707+ if rule. is_enabled ( ) {
708+ index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 15 ] ) ) ;
709+ }
710+ }
689711 index_set
690712 }
691713 pub ( crate ) fn get_disabled_rules ( & self ) -> FxHashSet < RuleFilter < ' static > > {
@@ -745,21 +767,31 @@ impl Security {
745767 index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 10 ] ) ) ;
746768 }
747769 }
748- if let Some ( rule) = self . rls_references_user_metadata . as_ref ( ) {
770+ if let Some ( rule) = self . rls_policy_always_true . as_ref ( ) {
749771 if rule. is_disabled ( ) {
750772 index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 11 ] ) ) ;
751773 }
752774 }
753- if let Some ( rule) = self . security_definer_view . as_ref ( ) {
775+ if let Some ( rule) = self . rls_references_user_metadata . as_ref ( ) {
754776 if rule. is_disabled ( ) {
755777 index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 12 ] ) ) ;
756778 }
757779 }
758- if let Some ( rule) = self . unsupported_reg_types . as_ref ( ) {
780+ if let Some ( rule) = self . security_definer_view . as_ref ( ) {
759781 if rule. is_disabled ( ) {
760782 index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 13 ] ) ) ;
761783 }
762784 }
785+ if let Some ( rule) = self . sensitive_columns_exposed . as_ref ( ) {
786+ if rule. is_disabled ( ) {
787+ index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 14 ] ) ) ;
788+ }
789+ }
790+ if let Some ( rule) = self . unsupported_reg_types . as_ref ( ) {
791+ if rule. is_disabled ( ) {
792+ index_set. insert ( RuleFilter :: Rule ( Self :: GROUP_NAME , Self :: GROUP_RULES [ 15 ] ) ) ;
793+ }
794+ }
763795 index_set
764796 }
765797 #[ doc = r" Checks if, given a rule name, matches one of the rules contained in this category" ]
@@ -800,8 +832,10 @@ impl Security {
800832 "policyExistsRlsDisabled" => Severity :: Error ,
801833 "rlsDisabledInPublic" => Severity :: Error ,
802834 "rlsEnabledNoPolicy" => Severity :: Information ,
835+ "rlsPolicyAlwaysTrue" => Severity :: Warning ,
803836 "rlsReferencesUserMetadata" => Severity :: Error ,
804837 "securityDefinerView" => Severity :: Error ,
838+ "sensitiveColumnsExposed" => Severity :: Error ,
805839 "unsupportedRegTypes" => Severity :: Warning ,
806840 _ => unreachable ! ( ) ,
807841 }
@@ -855,6 +889,10 @@ impl Security {
855889 . rls_enabled_no_policy
856890 . as_ref ( )
857891 . map ( |conf| ( conf. level ( ) , conf. get_options ( ) ) ) ,
892+ "rlsPolicyAlwaysTrue" => self
893+ . rls_policy_always_true
894+ . as_ref ( )
895+ . map ( |conf| ( conf. level ( ) , conf. get_options ( ) ) ) ,
858896 "rlsReferencesUserMetadata" => self
859897 . rls_references_user_metadata
860898 . as_ref ( )
@@ -863,6 +901,10 @@ impl Security {
863901 . security_definer_view
864902 . as_ref ( )
865903 . map ( |conf| ( conf. level ( ) , conf. get_options ( ) ) ) ,
904+ "sensitiveColumnsExposed" => self
905+ . sensitive_columns_exposed
906+ . as_ref ( )
907+ . map ( |conf| ( conf. level ( ) , conf. get_options ( ) ) ) ,
866908 "unsupportedRegTypes" => self
867909 . unsupported_reg_types
868910 . as_ref ( )
@@ -996,6 +1038,17 @@ impl Security {
9961038 }
9971039 }
9981040 }
1041+ if let Some ( conf) = & self . rls_policy_always_true {
1042+ if let Some ( options) = conf. get_options_ref ( ) {
1043+ if !options. ignore . is_empty ( ) {
1044+ let mut m = pgls_matcher:: Matcher :: new ( pgls_matcher:: MatchOptions :: default ( ) ) ;
1045+ for p in & options. ignore {
1046+ let _ = m. add_pattern ( p) ;
1047+ }
1048+ matchers. insert ( "rlsPolicyAlwaysTrue" , m) ;
1049+ }
1050+ }
1051+ }
9991052 if let Some ( conf) = & self . rls_references_user_metadata {
10001053 if let Some ( options) = conf. get_options_ref ( ) {
10011054 if !options. ignore . is_empty ( ) {
@@ -1018,6 +1071,17 @@ impl Security {
10181071 }
10191072 }
10201073 }
1074+ if let Some ( conf) = & self . sensitive_columns_exposed {
1075+ if let Some ( options) = conf. get_options_ref ( ) {
1076+ if !options. ignore . is_empty ( ) {
1077+ let mut m = pgls_matcher:: Matcher :: new ( pgls_matcher:: MatchOptions :: default ( ) ) ;
1078+ for p in & options. ignore {
1079+ let _ = m. add_pattern ( p) ;
1080+ }
1081+ matchers. insert ( "sensitiveColumnsExposed" , m) ;
1082+ }
1083+ }
1084+ }
10211085 if let Some ( conf) = & self . unsupported_reg_types {
10221086 if let Some ( options) = conf. get_options_ref ( ) {
10231087 if !options. ignore . is_empty ( ) {
0 commit comments