Skip to content

Commit 047d5fb

Browse files
psteinroeclaude
andauthored
chore: upgrade splinter to latest commit (#684)
## Summary - Upgrade splinter vendor files to latest commit (b9de3a3) from supabase/splinter - Add two new security lint rules: - `sensitiveColumnsExposed`: detects tables exposed via API with potentially sensitive columns (PII, credentials, financial info) without RLS protection - `rlsPolicyAlwaysTrue`: detects overly permissive RLS policies using `USING (true)` or `WITH CHECK (true)` for UPDATE, DELETE, or INSERT operations - Regenerated all codegen artifacts (rules, registry, categories, configuration, docs) ## Test plan - [x] `cargo clippy` passes - [ ] CI passes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 6e9c949 commit 047d5fb

File tree

17 files changed

+713
-10
lines changed

17 files changed

+713
-10
lines changed

crates/pgls_configuration/src/splinter/rules.rs

Lines changed: 70 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -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() {

crates/pgls_diagnostics_categories/src/categories.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,10 @@ define_categories! {
9595
"splinter/security/policyExistsRlsDisabled": "https://supabase.com/docs/guides/database/database-linter?lint=0007_policy_exists_rls_disabled",
9696
"splinter/security/rlsDisabledInPublic": "https://supabase.com/docs/guides/database/database-linter?lint=0013_rls_disabled_in_public",
9797
"splinter/security/rlsEnabledNoPolicy": "https://supabase.com/docs/guides/database/database-linter?lint=0008_rls_enabled_no_policy",
98+
"splinter/security/rlsPolicyAlwaysTrue": "https://supabase.com/docs/guides/database/database-linter?lint=0024_permissive_rls_policy",
9899
"splinter/security/rlsReferencesUserMetadata": "https://supabase.com/docs/guides/database/database-linter?lint=0015_rls_references_user_metadata",
99100
"splinter/security/securityDefinerView": "https://supabase.com/docs/guides/database/database-linter?lint=0010_security_definer_view",
101+
"splinter/security/sensitiveColumnsExposed": "https://supabase.com/docs/guides/database/database-linter?lint=0023_sensitive_columns_exposed",
100102
"splinter/security/unsupportedRegTypes": "https://supabase.com/docs/guides/database/database-linter?lint=unsupported_reg_types",
101103
// splinter rules end
102104
;

crates/pgls_splinter/build.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use std::fs;
22
use std::io::Write;
33
use std::path::Path;
44

5-
const EXPECTED_COMMIT: &str = "27ea2ece65464213e466cd969cc61b6940d16219";
5+
const EXPECTED_COMMIT: &str = "b9de3a3001cbdf01dc1da327acae0700c07f0110";
66
const REPO: &str = "supabase/splinter";
77

88
fn main() {

0 commit comments

Comments
 (0)