ship-safe 1.0.1 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,236 @@
1
+ # Firebase Security Checklist
2
+
3
+ **Complete this checklist before launching your Firebase-powered app.**
4
+
5
+ Based on common Firebase security vulnerabilities found in bug bounty programs and penetration tests.
6
+
7
+ ---
8
+
9
+ ## Critical: Security Rules
10
+
11
+ ### 1. [ ] NOT using test mode rules
12
+
13
+ **Check your Firestore rules don't contain:**
14
+ ```javascript
15
+ // DANGEROUS: Test mode - allows anyone to read/write
16
+ allow read, write: if true;
17
+
18
+ // DANGEROUS: Time-based test mode (often forgotten)
19
+ allow read, write: if request.time < timestamp.date(2024, 12, 31);
20
+ ```
21
+
22
+ **Fix:** Replace with proper authentication-based rules.
23
+
24
+ ### 2. [ ] Firestore rules require authentication
25
+
26
+ ```javascript
27
+ // GOOD: Requires authentication
28
+ allow read, write: if request.auth != null;
29
+
30
+ // BETTER: Requires authentication AND ownership
31
+ allow read, write: if request.auth.uid == userId;
32
+ ```
33
+
34
+ ### 3. [ ] Storage rules have file type validation
35
+
36
+ ```javascript
37
+ // GOOD: Only allow specific image types
38
+ allow write: if request.resource.contentType.matches('image/.*')
39
+ && request.resource.contentType in ['image/jpeg', 'image/png', 'image/webp'];
40
+ ```
41
+
42
+ ### 4. [ ] Storage rules have file size limits
43
+
44
+ ```javascript
45
+ // GOOD: Limit to 5MB
46
+ allow write: if request.resource.size < 5 * 1024 * 1024;
47
+ ```
48
+
49
+ ### 5. [ ] Default deny rule at the end
50
+
51
+ ```javascript
52
+ // Catch-all: deny everything not explicitly allowed
53
+ match /{document=**} {
54
+ allow read, write: if false;
55
+ }
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Critical: API Keys
61
+
62
+ ### 6. [ ] API key restrictions configured
63
+
64
+ Firebase Console > Project Settings > API Keys (in Google Cloud Console)
65
+
66
+ - [ ] Application restrictions (HTTP referrers for web, app signatures for mobile)
67
+ - [ ] API restrictions (only enable APIs you use)
68
+
69
+ ### 7. [ ] Firebase config not containing sensitive data
70
+
71
+ Your `firebaseConfig` is designed to be public, but verify:
72
+
73
+ ```javascript
74
+ // These are OK to be public:
75
+ const firebaseConfig = {
76
+ apiKey: "...", // OK - restricted by security rules
77
+ authDomain: "...", // OK - public
78
+ projectId: "...", // OK - public
79
+ storageBucket: "...", // OK - protected by storage rules
80
+ messagingSenderId: "...", // OK - public
81
+ appId: "..." // OK - public
82
+ };
83
+
84
+ // NEVER include these in client code:
85
+ // - Service account private keys
86
+ // - Admin SDK credentials
87
+ // - Database URLs with embedded secrets
88
+ ```
89
+
90
+ ### 8. [ ] Service account keys not in client code
91
+
92
+ ```bash
93
+ # Scan for service account files
94
+ npx ship-safe scan .
95
+
96
+ # Or manually search
97
+ find . -name "*.json" -exec grep -l "private_key" {} \;
98
+ grep -r "BEGIN PRIVATE KEY" .
99
+ ```
100
+
101
+ ---
102
+
103
+ ## High: Authentication
104
+
105
+ ### 9. [ ] Email enumeration protection enabled
106
+
107
+ Firebase Console > Authentication > Settings > User Actions
108
+ - [ ] Email enumeration protection = ON
109
+
110
+ This prevents attackers from discovering valid email addresses.
111
+
112
+ ### 10. [ ] Password requirements configured
113
+
114
+ Firebase Console > Authentication > Sign-in method > Email/Password
115
+ - [ ] Minimum password length (recommend 8+)
116
+ - [ ] Require uppercase/lowercase/numbers (if supported)
117
+
118
+ ### 11. [ ] OAuth providers properly configured
119
+
120
+ For each OAuth provider (Google, Facebook, etc.):
121
+ - [ ] Authorized domains list is correct
122
+ - [ ] Callback URLs are HTTPS only
123
+ - [ ] Client secrets are not exposed
124
+
125
+ ### 12. [ ] Phone auth abuse protection
126
+
127
+ If using phone authentication:
128
+ - [ ] SMS region policy configured (limit to countries you serve)
129
+ - [ ] App verification enabled
130
+ - [ ] Rate limiting understood
131
+
132
+ ---
133
+
134
+ ## High: Database Security
135
+
136
+ ### 13. [ ] No sensitive data in public collections
137
+
138
+ Review your data structure:
139
+ - [ ] User emails not in publicly readable collections
140
+ - [ ] Payment info not in Firestore (use Stripe)
141
+ - [ ] Passwords never stored (use Firebase Auth)
142
+
143
+ ### 14. [ ] Indexes don't expose data patterns
144
+
145
+ Firebase Console > Firestore > Indexes
146
+
147
+ Complex indexes can reveal data structure. Review each index.
148
+
149
+ ### 15. [ ] Backup and recovery plan exists
150
+
151
+ Firebase Console > Firestore > Backups
152
+ - [ ] Automated backups enabled
153
+ - [ ] Tested restore process
154
+
155
+ ---
156
+
157
+ ## Medium: Monitoring & Alerts
158
+
159
+ ### 16. [ ] Firebase App Check enabled
160
+
161
+ Firebase Console > App Check
162
+ - [ ] reCAPTCHA for web
163
+ - [ ] Device Check for iOS
164
+ - [ ] Play Integrity for Android
165
+
166
+ App Check helps prevent abuse from unauthorized clients.
167
+
168
+ ### 17. [ ] Budget alerts configured
169
+
170
+ Google Cloud Console > Billing > Budgets & alerts
171
+ - [ ] Budget set for expected usage
172
+ - [ ] Alerts at 50%, 90%, 100%
173
+
174
+ Prevents surprise bills from attacks or bugs.
175
+
176
+ ### 18. [ ] Security rules monitoring
177
+
178
+ Firebase Console > Firestore > Rules > Monitor
179
+ - [ ] Review denied requests regularly
180
+ - [ ] Set up alerts for unusual patterns
181
+
182
+ ---
183
+
184
+ ## Quick Security Audit Commands
185
+
186
+ ### Check for exposed Firebase configs
187
+
188
+ ```bash
189
+ # Search for Firebase config in your codebase
190
+ grep -r "firebaseConfig" --include="*.js" --include="*.ts" .
191
+ grep -r "apiKey.*firebase" --include="*.js" --include="*.ts" .
192
+ ```
193
+
194
+ ### Test your security rules
195
+
196
+ ```bash
197
+ # Install Firebase Emulator
198
+ npm install -g firebase-tools
199
+
200
+ # Run security rules tests
201
+ firebase emulators:start --only firestore
202
+ # Then run your test suite against localhost
203
+ ```
204
+
205
+ ### Scan for secrets
206
+
207
+ ```bash
208
+ npx ship-safe scan .
209
+ ```
210
+
211
+ ---
212
+
213
+ ## Testing Checklist
214
+
215
+ Before launch, test these scenarios:
216
+
217
+ 1. [ ] Unauthenticated user cannot read private data
218
+ 2. [ ] User A cannot read User B's private data
219
+ 3. [ ] User cannot write to another user's document
220
+ 4. [ ] File uploads are rejected if wrong type
221
+ 5. [ ] Large file uploads are rejected
222
+ 6. [ ] Rate limiting works (if implemented)
223
+
224
+ ---
225
+
226
+ ## Firebase Security Resources
227
+
228
+ - [Firebase Security Rules Documentation](https://firebase.google.com/docs/rules)
229
+ - [Firebase Security Checklist](https://firebase.google.com/support/guides/security-checklist)
230
+ - [Firebase App Check](https://firebase.google.com/docs/app-check)
231
+
232
+ ---
233
+
234
+ **Remember: Firebase makes it easy to build fast, but "test mode" is not a security strategy.**
235
+
236
+ Run `npx ship-safe scan .` to check for leaked keys before every deploy.
@@ -0,0 +1,206 @@
1
+ // =============================================================================
2
+ // FIREBASE STORAGE SECURITY RULES TEMPLATES
3
+ // =============================================================================
4
+ //
5
+ // Copy these rules to your Firebase Console > Storage > Rules
6
+ //
7
+ // WHY THIS MATTERS:
8
+ // - Default rules may allow anyone to upload/download files
9
+ // - Uploaded files can contain malware or illegal content
10
+ // - Storage costs can explode if not properly secured
11
+ //
12
+ // HOW TO USE:
13
+ // 1. Go to Firebase Console > Storage > Rules
14
+ // 2. Replace the default rules with these templates
15
+ // 3. Customize paths and limits for your app
16
+ // 4. Publish and test thoroughly
17
+ //
18
+ // =============================================================================
19
+
20
+ rules_version = '2';
21
+ service firebase.storage {
22
+ match /b/{bucket}/o {
23
+
24
+ // =========================================================================
25
+ // HELPER FUNCTIONS
26
+ // =========================================================================
27
+
28
+ // Check if user is authenticated
29
+ function isAuthenticated() {
30
+ return request.auth != null;
31
+ }
32
+
33
+ // Get the authenticated user's ID
34
+ function userId() {
35
+ return request.auth.uid;
36
+ }
37
+
38
+ // Check file size (in bytes)
39
+ function isUnderMaxSize(maxSizeMB) {
40
+ return request.resource.size < maxSizeMB * 1024 * 1024;
41
+ }
42
+
43
+ // Check if file is an image
44
+ function isImage() {
45
+ return request.resource.contentType.matches('image/.*');
46
+ }
47
+
48
+ // Check if file is a specific image type
49
+ function isImageType(types) {
50
+ return request.resource.contentType in types;
51
+ }
52
+
53
+ // Check if file is a document
54
+ function isDocument() {
55
+ return request.resource.contentType in [
56
+ 'application/pdf',
57
+ 'application/msword',
58
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
59
+ ];
60
+ }
61
+
62
+ // =========================================================================
63
+ // PATTERN 1: USER AVATARS (User's own files)
64
+ // =========================================================================
65
+
66
+ match /avatars/{userId}/{fileName} {
67
+ // Anyone can view avatars (they're public profile pictures)
68
+ allow read: if true;
69
+
70
+ // Users can only upload to their own folder
71
+ allow write: if isAuthenticated()
72
+ && request.auth.uid == userId
73
+ && isImage()
74
+ && isUnderMaxSize(2) // 2MB max
75
+ && isImageType(['image/jpeg', 'image/png', 'image/webp']);
76
+ }
77
+
78
+ // =========================================================================
79
+ // PATTERN 2: PRIVATE USER FILES
80
+ // =========================================================================
81
+
82
+ match /users/{userId}/private/{allPaths=**} {
83
+ // Users can only access their own private files
84
+ allow read: if isAuthenticated() && request.auth.uid == userId;
85
+
86
+ // Users can upload to their own folder with size limits
87
+ allow write: if isAuthenticated()
88
+ && request.auth.uid == userId
89
+ && isUnderMaxSize(10); // 10MB max
90
+
91
+ // Users can delete their own files
92
+ allow delete: if isAuthenticated() && request.auth.uid == userId;
93
+ }
94
+
95
+ // =========================================================================
96
+ // PATTERN 3: SHARED/PUBLIC FILES (Authenticated upload, public read)
97
+ // =========================================================================
98
+
99
+ match /public/{allPaths=**} {
100
+ // Anyone can read public files
101
+ allow read: if true;
102
+
103
+ // Only authenticated users can upload
104
+ allow write: if isAuthenticated()
105
+ && isUnderMaxSize(5)
106
+ && (isImage() || isDocument());
107
+
108
+ // Only admins can delete (implement admin check via custom claims)
109
+ allow delete: if request.auth.token.admin == true;
110
+ }
111
+
112
+ // =========================================================================
113
+ // PATTERN 4: ORGANIZATION FILES
114
+ // =========================================================================
115
+
116
+ match /organizations/{orgId}/{allPaths=**} {
117
+ // Check org membership via custom claims
118
+ function isOrgMember() {
119
+ return request.auth.token.orgId == orgId;
120
+ }
121
+
122
+ function isOrgAdmin() {
123
+ return request.auth.token.orgId == orgId
124
+ && request.auth.token.orgRole == 'admin';
125
+ }
126
+
127
+ // Org members can read
128
+ allow read: if isAuthenticated() && isOrgMember();
129
+
130
+ // Org members can upload
131
+ allow create: if isAuthenticated()
132
+ && isOrgMember()
133
+ && isUnderMaxSize(50); // 50MB for org files
134
+
135
+ // Org admins can delete
136
+ allow delete: if isAuthenticated() && isOrgAdmin();
137
+ }
138
+
139
+ // =========================================================================
140
+ // PATTERN 5: TEMPORARY UPLOADS (with expiration)
141
+ // =========================================================================
142
+
143
+ match /temp/{uploadId} {
144
+ // Anyone authenticated can upload to temp
145
+ allow create: if isAuthenticated()
146
+ && isUnderMaxSize(10)
147
+ // Set metadata for cleanup jobs
148
+ && request.resource.metadata.uploadedBy == userId()
149
+ && request.resource.metadata.expiresAt != null;
150
+
151
+ // Only the uploader can read/delete their temp files
152
+ allow read, delete: if isAuthenticated()
153
+ && resource.metadata.uploadedBy == userId();
154
+ }
155
+
156
+ // =========================================================================
157
+ // PATTERN 6: STRICT IMAGE UPLOADS (Profile pictures, etc.)
158
+ // =========================================================================
159
+
160
+ match /images/{imageId} {
161
+ // Public read
162
+ allow read: if true;
163
+
164
+ // Strict upload requirements
165
+ allow create: if isAuthenticated()
166
+ // Must be an allowed image type
167
+ && isImageType(['image/jpeg', 'image/png', 'image/gif', 'image/webp'])
168
+ // Max 5MB
169
+ && isUnderMaxSize(5)
170
+ // Must have dimensions metadata (set by client)
171
+ && request.resource.metadata.width != null
172
+ && request.resource.metadata.height != null
173
+ // Reasonable dimensions (prevent huge images)
174
+ && int(request.resource.metadata.width) <= 4096
175
+ && int(request.resource.metadata.height) <= 4096;
176
+ }
177
+
178
+ // =========================================================================
179
+ // ANTI-PATTERNS: NEVER DO THIS
180
+ // =========================================================================
181
+
182
+ // BAD: Allows anyone to read/write everything
183
+ // match /{allPaths=**} {
184
+ // allow read, write: if true;
185
+ // }
186
+
187
+ // BAD: No file type validation (allows malware uploads)
188
+ // match /uploads/{file} {
189
+ // allow write: if request.auth != null;
190
+ // }
191
+
192
+ // BAD: No size limits (allows storage cost attacks)
193
+ // match /files/{file} {
194
+ // allow write: if request.auth != null;
195
+ // }
196
+
197
+ // =========================================================================
198
+ // DEFAULT: DENY ALL (Safety net)
199
+ // =========================================================================
200
+
201
+ // Deny access to any path not explicitly defined above
202
+ match /{allPaths=**} {
203
+ allow read, write: if false;
204
+ }
205
+ }
206
+ }
@@ -0,0 +1,242 @@
1
+ -- =============================================================================
2
+ -- SUPABASE ROW LEVEL SECURITY (RLS) TEMPLATES
3
+ -- =============================================================================
4
+ --
5
+ -- Copy-paste these policies to secure your Supabase tables.
6
+ --
7
+ -- WHY RLS MATTERS:
8
+ -- Without RLS, anyone with your anon key can read/write ALL data.
9
+ -- 83% of exposed Supabase databases have RLS misconfigurations.
10
+ -- Source: https://byteiota.com/supabase-security-flaw-170-apps-exposed-by-missing-rls/
11
+ --
12
+ -- HOW TO USE:
13
+ -- 1. Enable RLS on your table: ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
14
+ -- 2. Copy the relevant policy below
15
+ -- 3. Replace 'your_table' with your actual table name
16
+ -- 4. Test with different user contexts
17
+ --
18
+ -- =============================================================================
19
+
20
+
21
+ -- =============================================================================
22
+ -- STEP 1: ALWAYS ENABLE RLS FIRST
23
+ -- =============================================================================
24
+
25
+ -- Enable RLS on a table (REQUIRED before adding policies)
26
+ ALTER TABLE your_table ENABLE ROW LEVEL SECURITY;
27
+
28
+ -- Force RLS for table owners too (recommended for security)
29
+ ALTER TABLE your_table FORCE ROW LEVEL SECURITY;
30
+
31
+
32
+ -- =============================================================================
33
+ -- PATTERN 1: USER OWNS THEIR DATA
34
+ -- =============================================================================
35
+ -- Use when: Each user should only see/edit their own records
36
+ -- Example tables: profiles, settings, user_preferences
37
+
38
+ -- Users can only SELECT their own rows
39
+ CREATE POLICY "Users can view own data"
40
+ ON your_table
41
+ FOR SELECT
42
+ USING (auth.uid() = user_id);
43
+
44
+ -- Users can only INSERT rows with their own user_id
45
+ CREATE POLICY "Users can insert own data"
46
+ ON your_table
47
+ FOR INSERT
48
+ WITH CHECK (auth.uid() = user_id);
49
+
50
+ -- Users can only UPDATE their own rows
51
+ CREATE POLICY "Users can update own data"
52
+ ON your_table
53
+ FOR UPDATE
54
+ USING (auth.uid() = user_id)
55
+ WITH CHECK (auth.uid() = user_id);
56
+
57
+ -- Users can only DELETE their own rows
58
+ CREATE POLICY "Users can delete own data"
59
+ ON your_table
60
+ FOR DELETE
61
+ USING (auth.uid() = user_id);
62
+
63
+ -- COMBINED: All operations for own data (simpler but less granular)
64
+ CREATE POLICY "Users manage own data"
65
+ ON your_table
66
+ FOR ALL
67
+ USING (auth.uid() = user_id)
68
+ WITH CHECK (auth.uid() = user_id);
69
+
70
+
71
+ -- =============================================================================
72
+ -- PATTERN 2: ORGANIZATION/TEAM BASED ACCESS
73
+ -- =============================================================================
74
+ -- Use when: Users belong to orgs and should see all org data
75
+ -- Example tables: projects, documents, team_settings
76
+
77
+ -- First, create a helper function to get user's org
78
+ CREATE OR REPLACE FUNCTION get_user_org_id()
79
+ RETURNS UUID AS $$
80
+ SELECT org_id FROM profiles WHERE id = auth.uid()
81
+ $$ LANGUAGE SQL SECURITY DEFINER;
82
+
83
+ -- Users can view all data in their organization
84
+ CREATE POLICY "Org members can view org data"
85
+ ON your_table
86
+ FOR SELECT
87
+ USING (org_id = get_user_org_id());
88
+
89
+ -- Users can insert data into their organization
90
+ CREATE POLICY "Org members can insert org data"
91
+ ON your_table
92
+ FOR INSERT
93
+ WITH CHECK (org_id = get_user_org_id());
94
+
95
+ -- Only allow updates to org data
96
+ CREATE POLICY "Org members can update org data"
97
+ ON your_table
98
+ FOR UPDATE
99
+ USING (org_id = get_user_org_id())
100
+ WITH CHECK (org_id = get_user_org_id());
101
+
102
+
103
+ -- =============================================================================
104
+ -- PATTERN 3: ROLE-BASED ACCESS CONTROL (RBAC)
105
+ -- =============================================================================
106
+ -- Use when: Different users have different permission levels
107
+ -- Example: admin, editor, viewer roles
108
+
109
+ -- Helper function to check user role
110
+ CREATE OR REPLACE FUNCTION get_user_role()
111
+ RETURNS TEXT AS $$
112
+ SELECT role FROM profiles WHERE id = auth.uid()
113
+ $$ LANGUAGE SQL SECURITY DEFINER;
114
+
115
+ -- Anyone authenticated can view (public within app)
116
+ CREATE POLICY "Authenticated users can view"
117
+ ON your_table
118
+ FOR SELECT
119
+ USING (auth.role() = 'authenticated');
120
+
121
+ -- Only admins and editors can insert
122
+ CREATE POLICY "Admins and editors can insert"
123
+ ON your_table
124
+ FOR INSERT
125
+ WITH CHECK (get_user_role() IN ('admin', 'editor'));
126
+
127
+ -- Only admins can delete
128
+ CREATE POLICY "Only admins can delete"
129
+ ON your_table
130
+ FOR DELETE
131
+ USING (get_user_role() = 'admin');
132
+
133
+
134
+ -- =============================================================================
135
+ -- PATTERN 4: PUBLIC READ, AUTHENTICATED WRITE
136
+ -- =============================================================================
137
+ -- Use when: Content is public but only logged-in users can contribute
138
+ -- Example tables: blog_posts, comments, public_profiles
139
+
140
+ -- Anyone can read (including anonymous)
141
+ CREATE POLICY "Public read access"
142
+ ON your_table
143
+ FOR SELECT
144
+ USING (true);
145
+
146
+ -- Only authenticated users can insert
147
+ CREATE POLICY "Authenticated users can insert"
148
+ ON your_table
149
+ FOR INSERT
150
+ WITH CHECK (auth.role() = 'authenticated');
151
+
152
+ -- Users can only update their own posts
153
+ CREATE POLICY "Users can update own posts"
154
+ ON your_table
155
+ FOR UPDATE
156
+ USING (auth.uid() = author_id)
157
+ WITH CHECK (auth.uid() = author_id);
158
+
159
+
160
+ -- =============================================================================
161
+ -- PATTERN 5: PRIVATE BY DEFAULT (Explicit sharing)
162
+ -- =============================================================================
163
+ -- Use when: Data is private unless explicitly shared
164
+ -- Example tables: documents, files with sharing
165
+
166
+ -- Owner can always access
167
+ CREATE POLICY "Owner full access"
168
+ ON documents
169
+ FOR ALL
170
+ USING (auth.uid() = owner_id)
171
+ WITH CHECK (auth.uid() = owner_id);
172
+
173
+ -- Shared users can view (requires a shares table)
174
+ CREATE POLICY "Shared users can view"
175
+ ON documents
176
+ FOR SELECT
177
+ USING (
178
+ auth.uid() = owner_id
179
+ OR
180
+ EXISTS (
181
+ SELECT 1 FROM document_shares
182
+ WHERE document_shares.document_id = documents.id
183
+ AND document_shares.user_id = auth.uid()
184
+ )
185
+ );
186
+
187
+
188
+ -- =============================================================================
189
+ -- PATTERN 6: TIME-BASED ACCESS
190
+ -- =============================================================================
191
+ -- Use when: Content has publish dates or expiration
192
+ -- Example tables: scheduled_posts, limited_offers
193
+
194
+ -- Only show published content
195
+ CREATE POLICY "Show only published content"
196
+ ON posts
197
+ FOR SELECT
198
+ USING (
199
+ published_at IS NOT NULL
200
+ AND published_at <= NOW()
201
+ AND (expires_at IS NULL OR expires_at > NOW())
202
+ );
203
+
204
+ -- Authors can always see their drafts
205
+ CREATE POLICY "Authors see own drafts"
206
+ ON posts
207
+ FOR SELECT
208
+ USING (auth.uid() = author_id);
209
+
210
+
211
+ -- =============================================================================
212
+ -- ANTI-PATTERNS: DON'T DO THIS
213
+ -- =============================================================================
214
+
215
+ -- BAD: Allows anyone to read everything
216
+ -- CREATE POLICY "bad_policy" ON users FOR SELECT USING (true);
217
+
218
+ -- BAD: No WITH CHECK means users could insert data for other users
219
+ -- CREATE POLICY "bad_insert" ON posts FOR INSERT WITH CHECK (true);
220
+
221
+ -- BAD: Missing USING clause on UPDATE allows updating any row
222
+ -- CREATE POLICY "bad_update" ON posts FOR UPDATE WITH CHECK (auth.uid() = user_id);
223
+
224
+
225
+ -- =============================================================================
226
+ -- TESTING YOUR POLICIES
227
+ -- =============================================================================
228
+
229
+ -- Test as a specific user (in SQL editor)
230
+ -- SET request.jwt.claim.sub = 'user-uuid-here';
231
+ -- SELECT * FROM your_table;
232
+
233
+ -- Check existing policies on a table
234
+ SELECT * FROM pg_policies WHERE tablename = 'your_table';
235
+
236
+ -- List all tables without RLS enabled (DANGER!)
237
+ SELECT schemaname, tablename
238
+ FROM pg_tables
239
+ WHERE schemaname = 'public'
240
+ AND tablename NOT IN (
241
+ SELECT tablename FROM pg_policies
242
+ );