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.
- package/README.md +175 -19
- package/ai-defense/cost-protection.md +292 -0
- package/ai-defense/llm-security-checklist.md +324 -0
- package/ai-defense/prompt-injection-patterns.js +283 -0
- package/cli/bin/ship-safe.js +8 -1
- package/cli/utils/patterns.js +345 -24
- package/configs/firebase/firestore-rules.txt +215 -0
- package/configs/firebase/security-checklist.md +236 -0
- package/configs/firebase/storage-rules.txt +206 -0
- package/configs/supabase/rls-templates.sql +242 -0
- package/configs/supabase/secure-client.ts +225 -0
- package/configs/supabase/security-checklist.md +278 -0
- package/package.json +11 -2
- package/snippets/README.md +89 -25
- package/snippets/api-security/api-security-checklist.md +412 -0
- package/snippets/api-security/cors-config.ts +322 -0
- package/snippets/api-security/input-validation.ts +430 -0
- package/snippets/auth/jwt-checklist.md +322 -0
- package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
- package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Supabase Secure Client Configuration
|
|
3
|
+
* =====================================
|
|
4
|
+
*
|
|
5
|
+
* Copy this file to your project and customize for your needs.
|
|
6
|
+
*
|
|
7
|
+
* WHY THIS MATTERS:
|
|
8
|
+
* - Separates anon (public) and service_role (admin) clients
|
|
9
|
+
* - Adds type safety for environment variables
|
|
10
|
+
* - Includes helpers for common secure patterns
|
|
11
|
+
*
|
|
12
|
+
* USAGE:
|
|
13
|
+
* // In client components (React, Vue, etc.)
|
|
14
|
+
* import { supabase } from '@/lib/supabase';
|
|
15
|
+
*
|
|
16
|
+
* // In server-side code (API routes, server components)
|
|
17
|
+
* import { supabaseAdmin } from '@/lib/supabase';
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { createClient, SupabaseClient } from '@supabase/supabase-js';
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// ENVIRONMENT VARIABLE VALIDATION
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Validates that required environment variables are set.
|
|
28
|
+
* Call this at app startup to fail fast if misconfigured.
|
|
29
|
+
*/
|
|
30
|
+
function validateEnv() {
|
|
31
|
+
const required = ['NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY'];
|
|
32
|
+
|
|
33
|
+
for (const key of required) {
|
|
34
|
+
if (!process.env[key]) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
`Missing required environment variable: ${key}\n` +
|
|
37
|
+
`Add it to your .env.local file.`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Validate on module load (comment out if causing issues in edge runtime)
|
|
44
|
+
// validateEnv();
|
|
45
|
+
|
|
46
|
+
// =============================================================================
|
|
47
|
+
// SUPABASE URL AND KEYS
|
|
48
|
+
// =============================================================================
|
|
49
|
+
|
|
50
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
|
51
|
+
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;
|
|
52
|
+
|
|
53
|
+
// Service role key - ONLY available server-side
|
|
54
|
+
// This key bypasses RLS and should NEVER be exposed to the client
|
|
55
|
+
const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
|
56
|
+
|
|
57
|
+
// =============================================================================
|
|
58
|
+
// CLIENT-SIDE SUPABASE CLIENT (uses anon key)
|
|
59
|
+
// =============================================================================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Public Supabase client for use in browser/client components.
|
|
63
|
+
*
|
|
64
|
+
* SECURITY NOTES:
|
|
65
|
+
* - Uses the anon key (safe to expose)
|
|
66
|
+
* - All operations are subject to RLS policies
|
|
67
|
+
* - User must be authenticated for protected operations
|
|
68
|
+
*/
|
|
69
|
+
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
|
|
70
|
+
auth: {
|
|
71
|
+
// Persist sessions in localStorage
|
|
72
|
+
persistSession: true,
|
|
73
|
+
// Automatically refresh tokens
|
|
74
|
+
autoRefreshToken: true,
|
|
75
|
+
// Detect session from URL (for OAuth callbacks)
|
|
76
|
+
detectSessionInUrl: true,
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// =============================================================================
|
|
81
|
+
// SERVER-SIDE SUPABASE CLIENT (uses service_role key)
|
|
82
|
+
// =============================================================================
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Admin Supabase client for use in server-side code ONLY.
|
|
86
|
+
*
|
|
87
|
+
* SECURITY NOTES:
|
|
88
|
+
* - Uses the service_role key (NEVER expose to client)
|
|
89
|
+
* - Bypasses ALL Row Level Security policies
|
|
90
|
+
* - Use only in: API routes, server actions, cron jobs
|
|
91
|
+
* - Always validate user permissions before using
|
|
92
|
+
*
|
|
93
|
+
* WHEN TO USE:
|
|
94
|
+
* - Admin operations (deleting users, bulk updates)
|
|
95
|
+
* - Background jobs that need full database access
|
|
96
|
+
* - Webhooks from external services
|
|
97
|
+
*
|
|
98
|
+
* WHEN NOT TO USE:
|
|
99
|
+
* - Any client-side code
|
|
100
|
+
* - Operations where user context matters
|
|
101
|
+
* - When RLS should apply
|
|
102
|
+
*/
|
|
103
|
+
export const supabaseAdmin: SupabaseClient | null = supabaseServiceRoleKey
|
|
104
|
+
? createClient(supabaseUrl, supabaseServiceRoleKey, {
|
|
105
|
+
auth: {
|
|
106
|
+
// Don't persist sessions for admin client
|
|
107
|
+
persistSession: false,
|
|
108
|
+
autoRefreshToken: false,
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
: null;
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Get admin client with error if not configured.
|
|
115
|
+
* Use this when service_role key is required.
|
|
116
|
+
*/
|
|
117
|
+
export function getSupabaseAdmin(): SupabaseClient {
|
|
118
|
+
if (!supabaseAdmin) {
|
|
119
|
+
throw new Error(
|
|
120
|
+
'SUPABASE_SERVICE_ROLE_KEY is not configured.\n' +
|
|
121
|
+
'This operation requires admin privileges.'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return supabaseAdmin;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// =============================================================================
|
|
128
|
+
// HELPER: SERVER-SIDE CLIENT WITH USER CONTEXT
|
|
129
|
+
// =============================================================================
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Creates a Supabase client that acts as a specific user.
|
|
133
|
+
* Useful for server-side operations that should respect RLS.
|
|
134
|
+
*
|
|
135
|
+
* @param accessToken - The user's JWT access token
|
|
136
|
+
* @returns Supabase client authenticated as the user
|
|
137
|
+
*
|
|
138
|
+
* USAGE:
|
|
139
|
+
* // In API route or server action
|
|
140
|
+
* const token = request.headers.get('Authorization')?.replace('Bearer ', '');
|
|
141
|
+
* const userClient = createUserClient(token);
|
|
142
|
+
* const { data } = await userClient.from('posts').select('*');
|
|
143
|
+
* // This respects RLS - user only sees their allowed data
|
|
144
|
+
*/
|
|
145
|
+
export function createUserClient(accessToken: string): SupabaseClient {
|
|
146
|
+
return createClient(supabaseUrl, supabaseAnonKey, {
|
|
147
|
+
global: {
|
|
148
|
+
headers: {
|
|
149
|
+
Authorization: `Bearer ${accessToken}`,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
auth: {
|
|
153
|
+
persistSession: false,
|
|
154
|
+
autoRefreshToken: false,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// =============================================================================
|
|
160
|
+
// HELPER: VALIDATE USER BEFORE ADMIN OPERATIONS
|
|
161
|
+
// =============================================================================
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Validates that the current user has permission before admin operations.
|
|
165
|
+
* Always use this before using supabaseAdmin for user-triggered actions.
|
|
166
|
+
*
|
|
167
|
+
* @param userId - The user's ID to check
|
|
168
|
+
* @param requiredRole - The minimum role required (e.g., 'admin')
|
|
169
|
+
* @returns True if user has permission
|
|
170
|
+
*
|
|
171
|
+
* USAGE:
|
|
172
|
+
* const hasPermission = await validateUserPermission(userId, 'admin');
|
|
173
|
+
* if (!hasPermission) {
|
|
174
|
+
* return new Response('Forbidden', { status: 403 });
|
|
175
|
+
* }
|
|
176
|
+
* // Now safe to use supabaseAdmin
|
|
177
|
+
*/
|
|
178
|
+
export async function validateUserPermission(
|
|
179
|
+
userId: string,
|
|
180
|
+
requiredRole: string
|
|
181
|
+
): Promise<boolean> {
|
|
182
|
+
const admin = getSupabaseAdmin();
|
|
183
|
+
|
|
184
|
+
const { data: profile } = await admin
|
|
185
|
+
.from('profiles')
|
|
186
|
+
.select('role')
|
|
187
|
+
.eq('id', userId)
|
|
188
|
+
.single();
|
|
189
|
+
|
|
190
|
+
if (!profile) return false;
|
|
191
|
+
|
|
192
|
+
// Customize this based on your role hierarchy
|
|
193
|
+
const roleHierarchy: Record<string, number> = {
|
|
194
|
+
viewer: 0,
|
|
195
|
+
editor: 1,
|
|
196
|
+
admin: 2,
|
|
197
|
+
super_admin: 3,
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const userRoleLevel = roleHierarchy[profile.role] ?? 0;
|
|
201
|
+
const requiredRoleLevel = roleHierarchy[requiredRole] ?? 999;
|
|
202
|
+
|
|
203
|
+
return userRoleLevel >= requiredRoleLevel;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// =============================================================================
|
|
207
|
+
// TYPE DEFINITIONS (customize for your database)
|
|
208
|
+
// =============================================================================
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Generate types with:
|
|
212
|
+
* npx supabase gen types typescript --project-id your-project > types/database.ts
|
|
213
|
+
*
|
|
214
|
+
* Then import and use:
|
|
215
|
+
* import { Database } from '@/types/database';
|
|
216
|
+
* const supabase = createClient<Database>(url, key);
|
|
217
|
+
*/
|
|
218
|
+
|
|
219
|
+
// Example type for user profiles
|
|
220
|
+
export interface Profile {
|
|
221
|
+
id: string;
|
|
222
|
+
email: string;
|
|
223
|
+
role: 'viewer' | 'editor' | 'admin';
|
|
224
|
+
created_at: string;
|
|
225
|
+
}
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# Supabase Security Checklist
|
|
2
|
+
|
|
3
|
+
**Complete this checklist before launching your Supabase-powered app.**
|
|
4
|
+
|
|
5
|
+
Based on [CVE-2025-48757](https://byteiota.com/supabase-security-flaw-170-apps-exposed-by-missing-rls/) and common pentesting findings.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Critical: Row Level Security (RLS)
|
|
10
|
+
|
|
11
|
+
### 1. [ ] RLS is ENABLED on ALL tables
|
|
12
|
+
|
|
13
|
+
```sql
|
|
14
|
+
-- Check which tables DON'T have RLS
|
|
15
|
+
SELECT schemaname, tablename
|
|
16
|
+
FROM pg_tables
|
|
17
|
+
WHERE schemaname = 'public'
|
|
18
|
+
AND tablename NOT IN (
|
|
19
|
+
SELECT tablename::text FROM pg_class
|
|
20
|
+
WHERE relrowsecurity = true
|
|
21
|
+
);
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**If any tables appear, enable RLS immediately:**
|
|
25
|
+
```sql
|
|
26
|
+
ALTER TABLE table_name ENABLE ROW LEVEL SECURITY;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 2. [ ] Every table has at least one policy
|
|
30
|
+
|
|
31
|
+
```sql
|
|
32
|
+
-- Tables with RLS enabled but NO policies (locked to everyone!)
|
|
33
|
+
SELECT tablename FROM pg_tables
|
|
34
|
+
WHERE schemaname = 'public'
|
|
35
|
+
AND tablename IN (
|
|
36
|
+
SELECT tablename::text FROM pg_class WHERE relrowsecurity = true
|
|
37
|
+
)
|
|
38
|
+
AND tablename NOT IN (
|
|
39
|
+
SELECT tablename FROM pg_policies
|
|
40
|
+
);
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
### 3. [ ] Policies use `auth.uid()` not hardcoded values
|
|
44
|
+
|
|
45
|
+
**Good:**
|
|
46
|
+
```sql
|
|
47
|
+
USING (auth.uid() = user_id)
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**Bad:**
|
|
51
|
+
```sql
|
|
52
|
+
USING (user_id = 'some-uuid') -- Hardcoded!
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### 4. [ ] INSERT policies have `WITH CHECK`
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
-- Without WITH CHECK, users can insert data for other users!
|
|
59
|
+
CREATE POLICY "Users insert own data"
|
|
60
|
+
ON your_table FOR INSERT
|
|
61
|
+
WITH CHECK (auth.uid() = user_id); -- This is required!
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 5. [ ] UPDATE policies have both `USING` and `WITH CHECK`
|
|
65
|
+
|
|
66
|
+
```sql
|
|
67
|
+
-- USING: which rows can be updated
|
|
68
|
+
-- WITH CHECK: what the new values must satisfy
|
|
69
|
+
CREATE POLICY "Users update own data"
|
|
70
|
+
ON your_table FOR UPDATE
|
|
71
|
+
USING (auth.uid() = user_id) -- Can only update own rows
|
|
72
|
+
WITH CHECK (auth.uid() = user_id); -- Can't change user_id to someone else
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Critical: API Keys
|
|
78
|
+
|
|
79
|
+
### 6. [ ] `service_role` key is NEVER in frontend code
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Scan your codebase
|
|
83
|
+
npx ship-safe scan .
|
|
84
|
+
|
|
85
|
+
# Or manually grep
|
|
86
|
+
grep -r "service_role" ./src
|
|
87
|
+
grep -r "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9" ./src
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**The `service_role` key bypasses ALL RLS. It should only exist in:**
|
|
91
|
+
- Server-side code (API routes, edge functions)
|
|
92
|
+
- Environment variables on your server
|
|
93
|
+
- Never in client bundles
|
|
94
|
+
|
|
95
|
+
### 7. [ ] `anon` key is only used where RLS protects data
|
|
96
|
+
|
|
97
|
+
The `anon` key is designed to be public, but only if RLS is properly configured.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// Frontend: Use anon key (safe if RLS is set up)
|
|
101
|
+
const supabase = createClient(url, anonKey);
|
|
102
|
+
|
|
103
|
+
// Server: Use service_role key (for admin operations)
|
|
104
|
+
const supabaseAdmin = createClient(url, serviceRoleKey);
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## High: Authentication Settings
|
|
110
|
+
|
|
111
|
+
### 8. [ ] Email confirmations enabled (if using email auth)
|
|
112
|
+
|
|
113
|
+
Dashboard > Authentication > Providers > Email
|
|
114
|
+
- [ ] Confirm email = ON
|
|
115
|
+
- [ ] Secure email change = ON
|
|
116
|
+
|
|
117
|
+
### 9. [ ] Rate limiting on auth endpoints
|
|
118
|
+
|
|
119
|
+
Supabase has built-in rate limiting, but verify:
|
|
120
|
+
- Dashboard > Authentication > Rate Limits
|
|
121
|
+
- Default: 30 requests per hour for signup/signin
|
|
122
|
+
|
|
123
|
+
### 10. [ ] Leaked password protection enabled
|
|
124
|
+
|
|
125
|
+
Dashboard > Authentication > Providers > Email
|
|
126
|
+
- [ ] Check for leaked passwords = ON
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## High: Database Security
|
|
131
|
+
|
|
132
|
+
### 11. [ ] No sensitive tables exposed to PostgREST
|
|
133
|
+
|
|
134
|
+
By default, all tables in `public` schema are exposed via REST API.
|
|
135
|
+
|
|
136
|
+
```sql
|
|
137
|
+
-- Move sensitive tables to a private schema
|
|
138
|
+
ALTER TABLE sensitive_table SET SCHEMA private;
|
|
139
|
+
|
|
140
|
+
-- Or revoke access from anon/authenticated roles
|
|
141
|
+
REVOKE ALL ON sensitive_table FROM anon, authenticated;
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### 12. [ ] Functions with `SECURITY DEFINER` are reviewed
|
|
145
|
+
|
|
146
|
+
```sql
|
|
147
|
+
-- List all SECURITY DEFINER functions
|
|
148
|
+
SELECT proname, prosecdef
|
|
149
|
+
FROM pg_proc
|
|
150
|
+
WHERE prosecdef = true;
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`SECURITY DEFINER` functions run with the privileges of the creator, not the caller. Review each one for:
|
|
154
|
+
- SQL injection vulnerabilities
|
|
155
|
+
- Proper input validation
|
|
156
|
+
- Necessary privilege escalation
|
|
157
|
+
|
|
158
|
+
### 13. [ ] HTTP extension not exposed to anon users
|
|
159
|
+
|
|
160
|
+
```sql
|
|
161
|
+
-- Check if http extension functions are callable by anon
|
|
162
|
+
SELECT routine_name
|
|
163
|
+
FROM information_schema.routine_privileges
|
|
164
|
+
WHERE grantee = 'anon'
|
|
165
|
+
AND routine_schema = 'extensions';
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
If `http_get`, `http_post` appear, attackers can make SSRF requests.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Medium: Storage Security
|
|
173
|
+
|
|
174
|
+
### 14. [ ] Storage buckets have proper policies
|
|
175
|
+
|
|
176
|
+
Dashboard > Storage > Policies
|
|
177
|
+
|
|
178
|
+
Each bucket should have:
|
|
179
|
+
- SELECT policy (who can download)
|
|
180
|
+
- INSERT policy (who can upload)
|
|
181
|
+
- UPDATE policy (who can replace)
|
|
182
|
+
- DELETE policy (who can remove)
|
|
183
|
+
|
|
184
|
+
### 15. [ ] File type validation on uploads
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
const { error } = await supabase.storage
|
|
188
|
+
.from('avatars')
|
|
189
|
+
.upload(path, file, {
|
|
190
|
+
contentType: 'image/png', // Explicit content type
|
|
191
|
+
upsert: false // Prevent overwrites
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### 16. [ ] File size limits configured
|
|
196
|
+
|
|
197
|
+
Dashboard > Storage > Settings
|
|
198
|
+
- Set max file size per bucket
|
|
199
|
+
- Consider implementing client-side validation too
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Medium: Realtime Security
|
|
204
|
+
|
|
205
|
+
### 17. [ ] Realtime only enabled on necessary tables
|
|
206
|
+
|
|
207
|
+
Dashboard > Database > Replication
|
|
208
|
+
|
|
209
|
+
Only enable realtime for tables that need it. Each enabled table:
|
|
210
|
+
- Increases server load
|
|
211
|
+
- Broadcasts changes to subscribed clients
|
|
212
|
+
- Must have RLS to protect data
|
|
213
|
+
|
|
214
|
+
### 18. [ ] Broadcast/Presence channels authenticated
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
// Require authentication for realtime channels
|
|
218
|
+
const channel = supabase.channel('room', {
|
|
219
|
+
config: {
|
|
220
|
+
broadcast: { self: true },
|
|
221
|
+
presence: { key: user.id }
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Bonus: Monitoring & Alerts
|
|
229
|
+
|
|
230
|
+
### 19. [ ] Database logs enabled
|
|
231
|
+
|
|
232
|
+
Dashboard > Database > Logs
|
|
233
|
+
- Enable query logging for debugging
|
|
234
|
+
- Set up alerts for failed auth attempts
|
|
235
|
+
|
|
236
|
+
### 20. [ ] Supabase email alerts configured
|
|
237
|
+
|
|
238
|
+
Dashboard > Settings > Alerts
|
|
239
|
+
- Database approaching limits
|
|
240
|
+
- High error rates
|
|
241
|
+
- Unusual activity
|
|
242
|
+
|
|
243
|
+
---
|
|
244
|
+
|
|
245
|
+
## Quick Commands
|
|
246
|
+
|
|
247
|
+
```sql
|
|
248
|
+
-- Enable RLS on all public tables at once (CAREFUL!)
|
|
249
|
+
DO $$
|
|
250
|
+
DECLARE
|
|
251
|
+
t text;
|
|
252
|
+
BEGIN
|
|
253
|
+
FOR t IN
|
|
254
|
+
SELECT tablename FROM pg_tables WHERE schemaname = 'public'
|
|
255
|
+
LOOP
|
|
256
|
+
EXECUTE format('ALTER TABLE %I ENABLE ROW LEVEL SECURITY', t);
|
|
257
|
+
END LOOP;
|
|
258
|
+
END
|
|
259
|
+
$$;
|
|
260
|
+
|
|
261
|
+
-- List all policies
|
|
262
|
+
SELECT * FROM pg_policies;
|
|
263
|
+
|
|
264
|
+
-- Check RLS status for all tables
|
|
265
|
+
SELECT
|
|
266
|
+
schemaname,
|
|
267
|
+
tablename,
|
|
268
|
+
rowsecurity
|
|
269
|
+
FROM pg_tables
|
|
270
|
+
JOIN pg_class ON tablename = relname
|
|
271
|
+
WHERE schemaname = 'public';
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
**Remember: Supabase makes it easy to build fast, but security is still your responsibility.**
|
|
277
|
+
|
|
278
|
+
Run `npx ship-safe scan .` to check for leaked keys before every deploy.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ship-safe",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Security toolkit for vibe coders and indie hackers. Secure your MVP in 5 minutes.",
|
|
5
5
|
"main": "cli/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -23,7 +23,16 @@
|
|
|
23
23
|
"indie-hacker",
|
|
24
24
|
"vibe-coding",
|
|
25
25
|
"mvp",
|
|
26
|
-
"cli"
|
|
26
|
+
"cli",
|
|
27
|
+
"supabase",
|
|
28
|
+
"firebase",
|
|
29
|
+
"llm-security",
|
|
30
|
+
"prompt-injection",
|
|
31
|
+
"rate-limiting",
|
|
32
|
+
"owasp",
|
|
33
|
+
"jwt",
|
|
34
|
+
"cors",
|
|
35
|
+
"rls"
|
|
27
36
|
],
|
|
28
37
|
"author": "ship-safe contributors",
|
|
29
38
|
"license": "MIT",
|
package/snippets/README.md
CHANGED
|
@@ -6,40 +6,73 @@ This folder contains drop-in code snippets for securing your application. Each s
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
-
##
|
|
9
|
+
## Available Snippets
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
- Express.js middleware
|
|
13
|
-
- Next.js API route wrapper
|
|
14
|
-
- Upstash Redis implementation
|
|
11
|
+
### Rate Limiting
|
|
15
12
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
13
|
+
| File | Description |
|
|
14
|
+
|------|-------------|
|
|
15
|
+
| [upstash-ratelimit.ts](./rate-limiting/upstash-ratelimit.ts) | Production-ready rate limiting with Upstash Redis. Includes different limiters for API, auth, and AI endpoints. |
|
|
16
|
+
| [nextjs-middleware.ts](./rate-limiting/nextjs-middleware.ts) | In-memory rate limiting at the Next.js middleware level. Good for development or simple deployments. |
|
|
20
17
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
- XSS sanitization
|
|
18
|
+
**Quick Start:**
|
|
19
|
+
```typescript
|
|
20
|
+
import { apiRatelimit } from './rate-limiting/upstash-ratelimit';
|
|
25
21
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
const { success } = await apiRatelimit.limit(userId);
|
|
23
|
+
if (!success) {
|
|
24
|
+
return new Response('Too Many Requests', { status: 429 });
|
|
25
|
+
}
|
|
26
|
+
```
|
|
30
27
|
|
|
31
28
|
---
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
### Authentication
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
| File | Description |
|
|
33
|
+
|------|-------------|
|
|
34
|
+
| [jwt-checklist.md](./auth/jwt-checklist.md) | Complete JWT security checklist. Covers algorithms, token lifetime, storage, validation, and revocation. |
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
**Key Points:**
|
|
37
|
+
- Use RS256/ES256, not HS256 with weak secrets
|
|
38
|
+
- Access tokens: 15-60 minutes max
|
|
39
|
+
- Store in httpOnly cookies, not localStorage
|
|
40
|
+
- Always validate issuer and audience claims
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### API Security
|
|
45
|
+
|
|
46
|
+
| File | Description |
|
|
47
|
+
|------|-------------|
|
|
48
|
+
| [cors-config.ts](./api-security/cors-config.ts) | CORS configurations for Next.js, Express, Fastify, Hono, and Vercel Edge. |
|
|
49
|
+
| [input-validation.ts](./api-security/input-validation.ts) | Zod schemas and validation patterns for API endpoints. Includes file upload validation. |
|
|
50
|
+
| [api-security-checklist.md](./api-security/api-security-checklist.md) | Comprehensive API security checklist based on OWASP API Security Top 10. |
|
|
51
|
+
|
|
52
|
+
**Quick Start (CORS):**
|
|
53
|
+
```typescript
|
|
54
|
+
const ALLOWED_ORIGINS = ['https://yourapp.com'];
|
|
55
|
+
|
|
56
|
+
// Only allow specific origins
|
|
57
|
+
if (origin && ALLOWED_ORIGINS.includes(origin)) {
|
|
58
|
+
headers['Access-Control-Allow-Origin'] = origin;
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
**Quick Start (Validation):**
|
|
63
|
+
```typescript
|
|
64
|
+
import { z } from 'zod';
|
|
65
|
+
|
|
66
|
+
const schema = z.object({
|
|
67
|
+
email: z.string().email().max(255),
|
|
68
|
+
password: z.string().min(8).max(128),
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const result = schema.safeParse(body);
|
|
72
|
+
if (!result.success) {
|
|
73
|
+
return Response.json({ error: result.error.issues }, { status: 400 });
|
|
74
|
+
}
|
|
75
|
+
```
|
|
43
76
|
|
|
44
77
|
---
|
|
45
78
|
|
|
@@ -56,3 +89,34 @@ Each snippet follows this format:
|
|
|
56
89
|
|
|
57
90
|
[actual code with inline comments]
|
|
58
91
|
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Related Resources
|
|
96
|
+
|
|
97
|
+
- **[/configs](../configs/)** - Framework configs (Next.js headers, Supabase RLS, Firebase rules)
|
|
98
|
+
- **[/ai-defense](../ai-defense/)** - AI/LLM security (prompt injection, cost protection)
|
|
99
|
+
- **[/checklists](../checklists/)** - Security checklists (launch day)
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Contributing
|
|
104
|
+
|
|
105
|
+
Have a security snippet that saved your app? Add it here!
|
|
106
|
+
|
|
107
|
+
1. Create a new file in the appropriate subfolder
|
|
108
|
+
2. Add extensive comments explaining:
|
|
109
|
+
- What attack this prevents
|
|
110
|
+
- How to integrate it
|
|
111
|
+
- Common gotchas
|
|
112
|
+
3. Open a PR
|
|
113
|
+
|
|
114
|
+
---
|
|
115
|
+
|
|
116
|
+
## What's Next
|
|
117
|
+
|
|
118
|
+
Future additions planned:
|
|
119
|
+
- Webhook signature verification (Stripe, GitHub)
|
|
120
|
+
- OAuth state parameter handling
|
|
121
|
+
- CSRF protection patterns
|
|
122
|
+
- Content Security Policy builder
|