ship-safe 6.1.1 → 6.3.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.
Files changed (49) hide show
  1. package/README.md +748 -641
  2. package/cli/agents/api-fuzzer.js +345 -345
  3. package/cli/agents/auth-bypass-agent.js +348 -348
  4. package/cli/agents/base-agent.js +272 -272
  5. package/cli/agents/cicd-scanner.js +236 -201
  6. package/cli/agents/config-auditor.js +521 -521
  7. package/cli/agents/deep-analyzer.js +6 -2
  8. package/cli/agents/git-history-scanner.js +170 -170
  9. package/cli/agents/html-reporter.js +568 -568
  10. package/cli/agents/index.js +85 -84
  11. package/cli/agents/injection-tester.js +500 -500
  12. package/cli/agents/legal-risk-agent.js +302 -0
  13. package/cli/agents/llm-redteam.js +251 -251
  14. package/cli/agents/mobile-scanner.js +231 -231
  15. package/cli/agents/orchestrator.js +322 -322
  16. package/cli/agents/pii-compliance-agent.js +301 -301
  17. package/cli/agents/scoring-engine.js +248 -248
  18. package/cli/agents/supabase-rls-agent.js +154 -154
  19. package/cli/agents/supply-chain-agent.js +650 -507
  20. package/cli/bin/ship-safe.js +464 -426
  21. package/cli/commands/agent.js +608 -608
  22. package/cli/commands/audit.js +1006 -980
  23. package/cli/commands/baseline.js +193 -193
  24. package/cli/commands/ci.js +342 -342
  25. package/cli/commands/deps.js +516 -516
  26. package/cli/commands/doctor.js +159 -159
  27. package/cli/commands/fix.js +218 -218
  28. package/cli/commands/hooks.js +268 -0
  29. package/cli/commands/init.js +407 -407
  30. package/cli/commands/legal.js +158 -0
  31. package/cli/commands/mcp.js +304 -304
  32. package/cli/commands/red-team.js +7 -1
  33. package/cli/commands/remediate.js +798 -798
  34. package/cli/commands/rotate.js +571 -571
  35. package/cli/commands/scan.js +569 -569
  36. package/cli/commands/score.js +449 -449
  37. package/cli/commands/watch.js +281 -281
  38. package/cli/hooks/patterns.js +313 -0
  39. package/cli/hooks/post-tool-use.js +140 -0
  40. package/cli/hooks/pre-tool-use.js +186 -0
  41. package/cli/index.js +73 -69
  42. package/cli/providers/llm-provider.js +397 -287
  43. package/cli/utils/autofix-rules.js +74 -74
  44. package/cli/utils/cache-manager.js +311 -311
  45. package/cli/utils/output.js +230 -230
  46. package/cli/utils/patterns.js +1121 -1121
  47. package/cli/utils/pdf-generator.js +94 -94
  48. package/package.json +69 -69
  49. package/configs/supabase/rls-templates.sql +0 -242
@@ -1,154 +1,154 @@
1
- /**
2
- * SupabaseRLSAgent
3
- * =================
4
- *
5
- * Detects missing or weak Row Level Security (RLS) in Supabase projects.
6
- * Checks SQL migrations, client-side service_role key usage,
7
- * unprotected storage operations, and anon-key data mutations.
8
- */
9
-
10
- import fs from 'fs';
11
- import path from 'path';
12
- import { BaseAgent, createFinding } from './base-agent.js';
13
-
14
- // Patterns for client-side code
15
- const CLIENT_PATTERNS = [
16
- {
17
- rule: 'SUPABASE_SERVICE_KEY_CLIENT',
18
- title: 'Supabase: Service Role Key in Client Code',
19
- regex: /SUPABASE_SERVICE_ROLE_KEY|service_role_key|serviceRoleKey|supabaseAdmin/g,
20
- severity: 'critical',
21
- cwe: 'CWE-798',
22
- owasp: 'A07:2021',
23
- description: 'Service role key bypasses RLS entirely. Never expose it in client-side code.',
24
- fix: 'Use the anon key on the client. Move service_role operations to a backend/edge function.',
25
- },
26
- {
27
- rule: 'SUPABASE_RLS_DISABLED',
28
- title: 'Supabase: RLS Bypass via .rpc() or Admin Client',
29
- regex: /\.rpc\s*\(\s*['"][^'"]+['"]/g,
30
- severity: 'high',
31
- cwe: 'CWE-284',
32
- owasp: 'A01:2021',
33
- confidence: 'medium',
34
- description: 'Supabase .rpc() calls execute database functions that may bypass RLS policies.',
35
- fix: 'Ensure the underlying SQL function uses SECURITY DEFINER carefully, or set search_path.',
36
- },
37
- {
38
- rule: 'SUPABASE_PUBLIC_ANON_INSERT',
39
- title: 'Supabase: Unguarded Insert/Update/Delete',
40
- regex: /supabase\s*\.from\s*\(\s*['"][^'"]+['"]\s*\)\s*\.(?:insert|update|delete|upsert)\s*\(/g,
41
- severity: 'high',
42
- cwe: 'CWE-284',
43
- owasp: 'A01:2021',
44
- confidence: 'medium',
45
- description: 'Supabase data mutation without visible auth check. Ensure RLS policies protect this table.',
46
- fix: 'Verify RLS is enabled on the table and policies restrict mutations to authenticated users.',
47
- },
48
- {
49
- rule: 'SUPABASE_UNPROTECTED_STORAGE',
50
- title: 'Supabase: Storage Operation Without Auth',
51
- regex: /supabase\s*\.storage\s*\.from\s*\(\s*['"][^'"]+['"]\s*\)\s*\.(?:upload|remove|move|createSignedUrl|list)\s*\(/g,
52
- severity: 'medium',
53
- cwe: 'CWE-284',
54
- owasp: 'A01:2021',
55
- confidence: 'medium',
56
- description: 'Supabase storage operation detected. Ensure storage policies restrict access.',
57
- fix: 'Configure storage bucket policies to require authentication.',
58
- },
59
- ];
60
-
61
- // Client-side directories (findings here are more severe)
62
- const CLIENT_DIRS = /(?:^|[/\\])(?:src|pages|app|components|hooks|lib|utils)[/\\]/i;
63
-
64
- export class SupabaseRLSAgent extends BaseAgent {
65
- constructor() {
66
- super('SupabaseRLSAgent', 'Supabase Row Level Security audit', 'auth');
67
- }
68
-
69
- shouldRun(recon) {
70
- return recon?.databases?.includes('supabase') ||
71
- recon?.authPatterns?.includes('supabase-auth') ||
72
- false;
73
- }
74
-
75
- async analyze(context) {
76
- const { rootPath, files } = context;
77
- let findings = [];
78
-
79
- // ── 1. Scan client-side code for Supabase security issues ─────────────────
80
- const codeFiles = files.filter(f => {
81
- const ext = path.extname(f).toLowerCase();
82
- return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.vue', '.svelte'].includes(ext);
83
- });
84
-
85
- for (const file of codeFiles) {
86
- const fileFindings = this.scanFileWithPatterns(file, CLIENT_PATTERNS);
87
- // Elevate severity for findings in client-side directories
88
- const relPath = path.relative(rootPath, file).replace(/\\/g, '/');
89
- if (CLIENT_DIRS.test(relPath)) {
90
- for (const f of fileFindings) {
91
- if (f.rule === 'SUPABASE_SERVICE_KEY_CLIENT') {
92
- f.severity = 'critical';
93
- }
94
- }
95
- }
96
- findings = findings.concat(fileFindings);
97
- }
98
-
99
- // ── 2. Scan SQL migrations for missing RLS ────────────────────────────────
100
- const sqlFiles = files.filter(f => path.extname(f).toLowerCase() === '.sql');
101
- const tablesWithRLS = new Set();
102
- const tablesWithoutRLS = [];
103
-
104
- for (const file of sqlFiles) {
105
- const content = this.readFile(file);
106
- if (!content) continue;
107
-
108
- // Find tables that have RLS enabled
109
- const rlsMatches = content.matchAll(/ALTER\s+TABLE\s+(?:(?:public|auth|storage)\.)?["']?(\w+)["']?\s+ENABLE\s+ROW\s+LEVEL\s+SECURITY/gi);
110
- for (const m of rlsMatches) {
111
- tablesWithRLS.add(m[1].toLowerCase());
112
- }
113
-
114
- // Find CREATE TABLE statements
115
- const createMatches = content.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(?:public|auth|storage)\.)?["']?(\w+)["']?/gi);
116
- for (const m of createMatches) {
117
- const tableName = m[1].toLowerCase();
118
- // Skip Supabase internal tables
119
- if (['_prisma_migrations', 'schema_migrations', 'knex_migrations'].includes(tableName)) continue;
120
-
121
- // Check if RLS is enabled in the same file
122
- const rlsInFile = new RegExp(
123
- `ALTER\\s+TABLE\\s+(?:(?:public|auth|storage)\\.)?["']?${tableName}["']?\\s+ENABLE\\s+ROW\\s+LEVEL\\s+SECURITY`,
124
- 'gi'
125
- ).test(content);
126
-
127
- if (!rlsInFile && !tablesWithRLS.has(tableName)) {
128
- tablesWithoutRLS.push({ table: tableName, file });
129
- }
130
- }
131
- }
132
-
133
- // Report tables missing RLS
134
- for (const { table, file } of tablesWithoutRLS) {
135
- // Double-check across all SQL files
136
- if (tablesWithRLS.has(table)) continue;
137
- findings.push(createFinding({
138
- file,
139
- line: 0,
140
- severity: 'critical',
141
- category: 'auth',
142
- rule: 'SUPABASE_NO_RLS_POLICY',
143
- title: `Supabase: Table "${table}" Missing RLS`,
144
- description: `Table "${table}" is created without enabling Row Level Security. Any user with the anon key can read/write all rows.`,
145
- matched: `CREATE TABLE ${table}`,
146
- fix: `Add: ALTER TABLE ${table} ENABLE ROW LEVEL SECURITY;\nThen create appropriate policies with CREATE POLICY.`,
147
- }));
148
- }
149
-
150
- return findings;
151
- }
152
- }
153
-
154
- export default SupabaseRLSAgent;
1
+ /**
2
+ * SupabaseRLSAgent
3
+ * =================
4
+ *
5
+ * Detects missing or weak Row Level Security (RLS) in Supabase projects.
6
+ * Checks SQL migrations, client-side service_role key usage,
7
+ * unprotected storage operations, and anon-key data mutations.
8
+ */
9
+
10
+ import fs from 'fs';
11
+ import path from 'path';
12
+ import { BaseAgent, createFinding } from './base-agent.js';
13
+
14
+ // Patterns for client-side code
15
+ const CLIENT_PATTERNS = [
16
+ {
17
+ rule: 'SUPABASE_SERVICE_KEY_CLIENT',
18
+ title: 'Supabase: Service Role Key in Client Code',
19
+ regex: /SUPABASE_SERVICE_ROLE_KEY|service_role_key|serviceRoleKey|supabaseAdmin/g,
20
+ severity: 'critical',
21
+ cwe: 'CWE-798',
22
+ owasp: 'A07:2021',
23
+ description: 'Service role key bypasses RLS entirely. Never expose it in client-side code.',
24
+ fix: 'Use the anon key on the client. Move service_role operations to a backend/edge function.',
25
+ },
26
+ {
27
+ rule: 'SUPABASE_RLS_DISABLED',
28
+ title: 'Supabase: RLS Bypass via .rpc() or Admin Client',
29
+ regex: /\.rpc\s*\(\s*['"][^'"]+['"]/g,
30
+ severity: 'high',
31
+ cwe: 'CWE-284',
32
+ owasp: 'A01:2021',
33
+ confidence: 'medium',
34
+ description: 'Supabase .rpc() calls execute database functions that may bypass RLS policies.',
35
+ fix: 'Ensure the underlying SQL function uses SECURITY DEFINER carefully, or set search_path.',
36
+ },
37
+ {
38
+ rule: 'SUPABASE_PUBLIC_ANON_INSERT',
39
+ title: 'Supabase: Unguarded Insert/Update/Delete',
40
+ regex: /supabase\s*\.from\s*\(\s*['"][^'"]+['"]\s*\)\s*\.(?:insert|update|delete|upsert)\s*\(/g,
41
+ severity: 'high',
42
+ cwe: 'CWE-284',
43
+ owasp: 'A01:2021',
44
+ confidence: 'medium',
45
+ description: 'Supabase data mutation without visible auth check. Ensure RLS policies protect this table.',
46
+ fix: 'Verify RLS is enabled on the table and policies restrict mutations to authenticated users.',
47
+ },
48
+ {
49
+ rule: 'SUPABASE_UNPROTECTED_STORAGE',
50
+ title: 'Supabase: Storage Operation Without Auth',
51
+ regex: /supabase\s*\.storage\s*\.from\s*\(\s*['"][^'"]+['"]\s*\)\s*\.(?:upload|remove|move|createSignedUrl|list)\s*\(/g,
52
+ severity: 'medium',
53
+ cwe: 'CWE-284',
54
+ owasp: 'A01:2021',
55
+ confidence: 'medium',
56
+ description: 'Supabase storage operation detected. Ensure storage policies restrict access.',
57
+ fix: 'Configure storage bucket policies to require authentication.',
58
+ },
59
+ ];
60
+
61
+ // Client-side directories (findings here are more severe)
62
+ const CLIENT_DIRS = /(?:^|[/\\])(?:src|pages|app|components|hooks|lib|utils)[/\\]/i;
63
+
64
+ export class SupabaseRLSAgent extends BaseAgent {
65
+ constructor() {
66
+ super('SupabaseRLSAgent', 'Supabase Row Level Security audit', 'auth');
67
+ }
68
+
69
+ shouldRun(recon) {
70
+ return recon?.databases?.includes('supabase') ||
71
+ recon?.authPatterns?.includes('supabase-auth') ||
72
+ false;
73
+ }
74
+
75
+ async analyze(context) {
76
+ const { rootPath, files } = context;
77
+ let findings = [];
78
+
79
+ // ── 1. Scan client-side code for Supabase security issues ─────────────────
80
+ const codeFiles = files.filter(f => {
81
+ const ext = path.extname(f).toLowerCase();
82
+ return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.vue', '.svelte'].includes(ext);
83
+ });
84
+
85
+ for (const file of codeFiles) {
86
+ const fileFindings = this.scanFileWithPatterns(file, CLIENT_PATTERNS);
87
+ // Elevate severity for findings in client-side directories
88
+ const relPath = path.relative(rootPath, file).replace(/\\/g, '/');
89
+ if (CLIENT_DIRS.test(relPath)) {
90
+ for (const f of fileFindings) {
91
+ if (f.rule === 'SUPABASE_SERVICE_KEY_CLIENT') {
92
+ f.severity = 'critical';
93
+ }
94
+ }
95
+ }
96
+ findings = findings.concat(fileFindings);
97
+ }
98
+
99
+ // ── 2. Scan SQL migrations for missing RLS ────────────────────────────────
100
+ const sqlFiles = files.filter(f => path.extname(f).toLowerCase() === '.sql');
101
+ const tablesWithRLS = new Set();
102
+ const tablesWithoutRLS = [];
103
+
104
+ for (const file of sqlFiles) {
105
+ const content = this.readFile(file);
106
+ if (!content) continue;
107
+
108
+ // Find tables that have RLS enabled
109
+ const rlsMatches = content.matchAll(/ALTER\s+TABLE\s+(?:(?:public|auth|storage)\.)?["']?(\w+)["']?\s+ENABLE\s+ROW\s+LEVEL\s+SECURITY/gi);
110
+ for (const m of rlsMatches) {
111
+ tablesWithRLS.add(m[1].toLowerCase());
112
+ }
113
+
114
+ // Find CREATE TABLE statements
115
+ const createMatches = content.matchAll(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?(?:(?:public|auth|storage)\.)?["']?(\w+)["']?/gi);
116
+ for (const m of createMatches) {
117
+ const tableName = m[1].toLowerCase();
118
+ // Skip Supabase internal tables
119
+ if (['_prisma_migrations', 'schema_migrations', 'knex_migrations'].includes(tableName)) continue;
120
+
121
+ // Check if RLS is enabled in the same file
122
+ const rlsInFile = new RegExp(
123
+ `ALTER\\s+TABLE\\s+(?:(?:public|auth|storage)\\.)?["']?${tableName}["']?\\s+ENABLE\\s+ROW\\s+LEVEL\\s+SECURITY`,
124
+ 'gi'
125
+ ).test(content);
126
+
127
+ if (!rlsInFile && !tablesWithRLS.has(tableName)) {
128
+ tablesWithoutRLS.push({ table: tableName, file });
129
+ }
130
+ }
131
+ }
132
+
133
+ // Report tables missing RLS
134
+ for (const { table, file } of tablesWithoutRLS) {
135
+ // Double-check across all SQL files
136
+ if (tablesWithRLS.has(table)) continue;
137
+ findings.push(createFinding({
138
+ file,
139
+ line: 0,
140
+ severity: 'critical',
141
+ category: 'auth',
142
+ rule: 'SUPABASE_NO_RLS_POLICY',
143
+ title: `Supabase: Table "${table}" Missing RLS`,
144
+ description: `Table "${table}" is created without enabling Row Level Security. Any user with the anon key can read/write all rows.`,
145
+ matched: `CREATE TABLE ${table}`,
146
+ fix: `Add: ALTER TABLE ${table} ENABLE ROW LEVEL SECURITY;\nThen create appropriate policies with CREATE POLICY.`,
147
+ }));
148
+ }
149
+
150
+ return findings;
151
+ }
152
+ }
153
+
154
+ export default SupabaseRLSAgent;