praxis-sec 1.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.
Files changed (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/ai-defense/cost-protection.md +292 -0
  4. package/ai-defense/llm-security-checklist.md +324 -0
  5. package/ai-defense/prompt-injection-patterns.js +283 -0
  6. package/ai-defense/system-prompt-armor.md +327 -0
  7. package/checklists/launch-day.md +168 -0
  8. package/cli/agents/abom-generator.js +225 -0
  9. package/cli/agents/agent-attestation-agent.js +318 -0
  10. package/cli/agents/agent-config-scanner.js +787 -0
  11. package/cli/agents/agent-telemetry-agent.js +415 -0
  12. package/cli/agents/agentic-security-agent.js +296 -0
  13. package/cli/agents/agentic-supply-chain-agent.js +463 -0
  14. package/cli/agents/ai-infra-inventory-agent.js +449 -0
  15. package/cli/agents/api-fuzzer.js +345 -0
  16. package/cli/agents/auth-bypass-agent.js +348 -0
  17. package/cli/agents/base-agent.js +280 -0
  18. package/cli/agents/cicd-scanner.js +300 -0
  19. package/cli/agents/config-auditor.js +757 -0
  20. package/cli/agents/deep-analyzer.js +776 -0
  21. package/cli/agents/endpoint-agent-abuse-agent.js +404 -0
  22. package/cli/agents/exception-handler-agent.js +187 -0
  23. package/cli/agents/git-history-scanner.js +169 -0
  24. package/cli/agents/governance-audits.js +138 -0
  25. package/cli/agents/hermes-security-agent.js +536 -0
  26. package/cli/agents/html-reporter.js +1125 -0
  27. package/cli/agents/index.js +147 -0
  28. package/cli/agents/injection-tester.js +502 -0
  29. package/cli/agents/legal-risk-agent.js +328 -0
  30. package/cli/agents/llm-redteam.js +199 -0
  31. package/cli/agents/managed-agent-scanner.js +333 -0
  32. package/cli/agents/mcp-security-agent.js +588 -0
  33. package/cli/agents/memory-poisoning-agent.js +305 -0
  34. package/cli/agents/mobile-scanner.js +231 -0
  35. package/cli/agents/model-file-scanner.js +259 -0
  36. package/cli/agents/orchestrator.js +355 -0
  37. package/cli/agents/pii-compliance-agent.js +301 -0
  38. package/cli/agents/policy-engine.js +229 -0
  39. package/cli/agents/prompt-injection-prober.js +224 -0
  40. package/cli/agents/rag-security-agent.js +204 -0
  41. package/cli/agents/recon-agent.js +207 -0
  42. package/cli/agents/sbom-generator.js +265 -0
  43. package/cli/agents/scoring-engine.js +273 -0
  44. package/cli/agents/ssrf-prober.js +130 -0
  45. package/cli/agents/stateful-watcher.js +238 -0
  46. package/cli/agents/supabase-rls-agent.js +154 -0
  47. package/cli/agents/supply-chain-agent.js +857 -0
  48. package/cli/agents/swarm-orchestrator.js +200 -0
  49. package/cli/agents/verifier-agent.js +303 -0
  50. package/cli/agents/vibe-coding-agent.js +250 -0
  51. package/cli/bin/praxis.js +866 -0
  52. package/cli/commands/abom.js +73 -0
  53. package/cli/commands/agent-fix.js +1245 -0
  54. package/cli/commands/audit.js +1180 -0
  55. package/cli/commands/autofix.js +383 -0
  56. package/cli/commands/baseline.js +193 -0
  57. package/cli/commands/benchmark.js +327 -0
  58. package/cli/commands/checklist.js +223 -0
  59. package/cli/commands/ci.js +403 -0
  60. package/cli/commands/deps.js +516 -0
  61. package/cli/commands/diff.js +200 -0
  62. package/cli/commands/doctor.js +195 -0
  63. package/cli/commands/env-audit.js +349 -0
  64. package/cli/commands/fix.js +218 -0
  65. package/cli/commands/guard.js +396 -0
  66. package/cli/commands/hooks.js +278 -0
  67. package/cli/commands/init.js +514 -0
  68. package/cli/commands/legal.js +158 -0
  69. package/cli/commands/live-advisories.js +241 -0
  70. package/cli/commands/mcp.js +660 -0
  71. package/cli/commands/openclaw.js +386 -0
  72. package/cli/commands/red-team.js +350 -0
  73. package/cli/commands/redteam.js +78 -0
  74. package/cli/commands/remediate.js +797 -0
  75. package/cli/commands/rotate.js +768 -0
  76. package/cli/commands/rules.js +196 -0
  77. package/cli/commands/scan-mcp.js +534 -0
  78. package/cli/commands/scan-skill.js +588 -0
  79. package/cli/commands/scan-standard.js +251 -0
  80. package/cli/commands/scan.js +524 -0
  81. package/cli/commands/score.js +449 -0
  82. package/cli/commands/shell.js +514 -0
  83. package/cli/commands/team-report.js +398 -0
  84. package/cli/commands/undo.js +161 -0
  85. package/cli/commands/update-intel.js +126 -0
  86. package/cli/commands/vibe-check.js +276 -0
  87. package/cli/commands/watch.js +757 -0
  88. package/cli/commands/web.js +63 -0
  89. package/cli/core/ast/guardrail-detector.js +141 -0
  90. package/cli/core/ast/index.js +22 -0
  91. package/cli/core/ast/parser.js +676 -0
  92. package/cli/core/ast/scope-tree.js +287 -0
  93. package/cli/core/ast/taint-tracker.js +158 -0
  94. package/cli/core/branding.js +37 -0
  95. package/cli/core/env.js +38 -0
  96. package/cli/core/errors.js +61 -0
  97. package/cli/core/fs.js +62 -0
  98. package/cli/core/output/compliance.js +90 -0
  99. package/cli/core/output/html-theme.js +158 -0
  100. package/cli/core/output/index.js +57 -0
  101. package/cli/core/output/json.js +48 -0
  102. package/cli/core/output/sarif.js +240 -0
  103. package/cli/core/version.js +67 -0
  104. package/cli/core/web/jobs.js +183 -0
  105. package/cli/core/web/projects.js +146 -0
  106. package/cli/core/web/server.js +439 -0
  107. package/cli/data/atlas-knowledge.json +5640 -0
  108. package/cli/data/eaa-catalog.json +39 -0
  109. package/cli/data/known-mcps.json +26 -0
  110. package/cli/data/probes/prompt-injection-corpus.json +271 -0
  111. package/cli/data/threat-intel.json +85 -0
  112. package/cli/data/threatpacks/latest.json +41 -0
  113. package/cli/hooks/patterns.js +313 -0
  114. package/cli/hooks/post-tool-use.js +140 -0
  115. package/cli/hooks/pre-tool-use.js +186 -0
  116. package/cli/index.js +90 -0
  117. package/cli/providers/llm-provider.js +766 -0
  118. package/cli/utils/autofix-rules.js +74 -0
  119. package/cli/utils/cache-manager.js +310 -0
  120. package/cli/utils/compliance-map.js +191 -0
  121. package/cli/utils/entropy.js +132 -0
  122. package/cli/utils/fix-ledger.js +127 -0
  123. package/cli/utils/hermes-tool-registry.js +252 -0
  124. package/cli/utils/intel/cache.js +61 -0
  125. package/cli/utils/intel/http.js +88 -0
  126. package/cli/utils/intel/index.js +235 -0
  127. package/cli/utils/intel/merge.js +229 -0
  128. package/cli/utils/intel/sources/epss.js +54 -0
  129. package/cli/utils/intel/sources/ghsa.js +81 -0
  130. package/cli/utils/intel/sources/gitguardian.js +40 -0
  131. package/cli/utils/intel/sources/gitleaks.js +101 -0
  132. package/cli/utils/intel/sources/kev.js +38 -0
  133. package/cli/utils/intel/sources/nvd.js +84 -0
  134. package/cli/utils/intel/sources/osv.js +132 -0
  135. package/cli/utils/intel/sources/phylum.js +44 -0
  136. package/cli/utils/intel/sources/snyk.js +46 -0
  137. package/cli/utils/intel/sources/socket.js +69 -0
  138. package/cli/utils/intel/sources/sonatype.js +84 -0
  139. package/cli/utils/intel/sources/threatpack.js +69 -0
  140. package/cli/utils/mcp-trust.js +60 -0
  141. package/cli/utils/output.js +251 -0
  142. package/cli/utils/patterns.js +1130 -0
  143. package/cli/utils/pdf-generator.js +94 -0
  144. package/cli/utils/plugin-loader.js +364 -0
  145. package/cli/utils/rule-import.js +228 -0
  146. package/cli/utils/rule-registry.js +426 -0
  147. package/cli/utils/scan-fingerprint.js +109 -0
  148. package/cli/utils/scan-playbook.js +312 -0
  149. package/cli/utils/score-history.js +119 -0
  150. package/cli/utils/secrets-verifier.js +247 -0
  151. package/cli/utils/security-memory.js +296 -0
  152. package/cli/utils/standards/atlas-knowledge.js +87 -0
  153. package/cli/utils/standards/index.js +127 -0
  154. package/cli/utils/standards/sources/avid.js +45 -0
  155. package/cli/utils/standards/sources/eu-ai-act.js +89 -0
  156. package/cli/utils/standards/sources/google-saif.js +39 -0
  157. package/cli/utils/standards/sources/iso-42001.js +94 -0
  158. package/cli/utils/standards/sources/mitre-atlas.js +54 -0
  159. package/cli/utils/standards/sources/nist-ai-600-1.js +45 -0
  160. package/cli/utils/standards/sources/owasp-llm.js +45 -0
  161. package/cli/utils/standards/sources/owasp-ml.js +45 -0
  162. package/cli/utils/threat-intel.js +265 -0
  163. package/configs/firebase/firestore-rules.txt +215 -0
  164. package/configs/firebase/security-checklist.md +236 -0
  165. package/configs/firebase/storage-rules.txt +206 -0
  166. package/configs/gitignore-template +258 -0
  167. package/configs/nextjs-security-headers.js +220 -0
  168. package/configs/praxisignore-template +50 -0
  169. package/configs/supabase/secure-client.ts +225 -0
  170. package/configs/supabase/security-checklist.md +278 -0
  171. package/docs/THIRD_PARTY_NOTICES.md +26 -0
  172. package/docs/THREAT_INTEL.md +292 -0
  173. package/docs/USAGE.md +1205 -0
  174. package/docs/design/WEB-UI.md +82 -0
  175. package/package.json +71 -0
  176. package/scripts/check-determinism.mjs +119 -0
  177. package/snippets/README.md +122 -0
  178. package/snippets/api-security/api-security-checklist.md +412 -0
  179. package/snippets/api-security/cors-config.ts +322 -0
  180. package/snippets/api-security/input-validation.ts +430 -0
  181. package/snippets/auth/jwt-checklist.md +322 -0
  182. package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
  183. package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
@@ -0,0 +1,238 @@
1
+ /**
2
+ * StatefulWatcher — Persistent K2.6 Security Session
3
+ * ====================================================
4
+ *
5
+ * Keeps a Kimi K2.6 conversation thread open across file-change events.
6
+ * Each scan sends only the diff — not the full codebase — so the model
7
+ * builds understanding incrementally rather than restarting from scratch.
8
+ *
9
+ * Advantages over stateless watch:
10
+ * - No duplicate findings on repeated scans of unchanged files
11
+ * - Model understands which files are already clean vs. risky
12
+ * - Diffs are small → faster, cheaper per event
13
+ * - K2.6's 12h+ session length handles full work sessions without reset
14
+ *
15
+ * USAGE (via watch command):
16
+ * npx praxis-sec watch . --deep --stateful
17
+ * npx praxis-sec watch . --deep --stateful --provider kimi
18
+ */
19
+
20
+ import fs from 'fs';
21
+ import path from 'path';
22
+ import { autoDetectProvider } from '../providers/llm-provider.js';
23
+ import { createFinding } from './base-agent.js';
24
+
25
+ // Max chars of diff content per event
26
+ const MAX_DIFF_CHARS = 20_000;
27
+
28
+ // =============================================================================
29
+ // STATEFUL WATCHER
30
+ // =============================================================================
31
+
32
+ export class StatefulWatcher {
33
+ /**
34
+ * @param {object} options
35
+ * @param {object} options.provider — LLM provider (Kimi preferred)
36
+ * @param {string} options.rootPath
37
+ * @param {boolean} options.verbose
38
+ */
39
+ constructor(options = {}) {
40
+ this.provider = options.provider;
41
+ this.rootPath = options.rootPath;
42
+ this.verbose = options.verbose || false;
43
+
44
+ // Persistent conversation thread
45
+ this._messages = [];
46
+ this._scanCount = 0;
47
+ this._baselineSet = false;
48
+ }
49
+
50
+ static create(rootPath, options = {}) {
51
+ const providerName = typeof options.provider === 'string' ? options.provider : 'kimi';
52
+ const provider = autoDetectProvider(rootPath, { provider: providerName, model: options.model || 'kimi-k2.6' });
53
+ if (!provider) return null;
54
+ return new StatefulWatcher({ provider, rootPath, verbose: options.verbose });
55
+ }
56
+
57
+ /**
58
+ * Set the initial baseline — called once on watcher start.
59
+ * The model receives a codebase summary and primes its security context.
60
+ *
61
+ * @param {object} recon — Output from ReconAgent
62
+ * @param {string[]} files — All scannable files
63
+ */
64
+ async setBaseline(recon, files) {
65
+ const summary = this._buildReconSummary(recon);
66
+ const fileList = files
67
+ .slice(0, 200)
68
+ .map(f => path.relative(this.rootPath, f))
69
+ .join('\n');
70
+
71
+ const baselineMsg = `You are a persistent security monitor for this codebase. I will send you file changes as they happen. For each change, identify new security issues introduced by that specific change.
72
+
73
+ Project context:
74
+ ${summary}
75
+
76
+ File inventory (${files.length} total):
77
+ ${fileList}
78
+
79
+ Respond to each update with a JSON array of findings. Use this format:
80
+ [{"file":"<relative path>","line":<number>,"severity":"critical|high|medium|low","rule":"<rule-id>","title":"<title>","description":"<description>","remediation":"<fix>"}]
81
+
82
+ If no new issues are introduced by the change, respond with an empty array: []
83
+ Never include issues you already reported in previous messages.`;
84
+
85
+ this._messages.push({ role: 'user', content: baselineMsg });
86
+
87
+ try {
88
+ const ack = await this._callProvider('You are a security expert. Acknowledge you understand the codebase context.', this._messages);
89
+ this._messages.push({ role: 'assistant', content: ack });
90
+ this._baselineSet = true;
91
+ if (this.verbose) console.log(` [Stateful] Baseline set. Provider: ${this.provider.name}`);
92
+ } catch (err) {
93
+ if (this.verbose) console.log(` [Stateful] Baseline failed: ${err.message}`);
94
+ }
95
+ }
96
+
97
+ /**
98
+ * Analyze a set of changed files. Sends only diffs to the persistent session.
99
+ *
100
+ * @param {string[]} changedFiles — Absolute paths of changed files
101
+ * @returns {Promise<object[]>} — New findings introduced by this change
102
+ */
103
+ async analyzeChanges(changedFiles) {
104
+ if (!this._baselineSet) return [];
105
+ this._scanCount++;
106
+
107
+ const diffs = this._readChanges(changedFiles);
108
+ if (!diffs) return [];
109
+
110
+ const updateMsg = `Files changed (scan #${this._scanCount}):\n\n${diffs}\n\nWhat NEW security issues does this change introduce? Reply with the JSON findings array only.`;
111
+
112
+ this._messages.push({ role: 'user', content: updateMsg });
113
+
114
+ try {
115
+ const response = await this._callProvider(
116
+ 'You are a persistent security monitor. Report only NEW issues from the latest change.',
117
+ this._messages
118
+ );
119
+
120
+ this._messages.push({ role: 'assistant', content: response });
121
+
122
+ const findings = this._parseFindings(response, changedFiles[0]);
123
+ if (this.verbose && findings.length > 0) {
124
+ console.log(` [Stateful] Scan #${this._scanCount}: ${findings.length} new finding(s)`);
125
+ }
126
+ return findings;
127
+ } catch (err) {
128
+ if (this.verbose) console.log(` [Stateful] Scan failed: ${err.message}`);
129
+ return [];
130
+ }
131
+ }
132
+
133
+ _readChanges(changedFiles) {
134
+ const parts = [];
135
+ let totalChars = 0;
136
+
137
+ for (const filePath of changedFiles) {
138
+ if (totalChars >= MAX_DIFF_CHARS) break;
139
+ try {
140
+ const relPath = path.relative(this.rootPath, filePath);
141
+ const content = fs.readFileSync(filePath, 'utf-8');
142
+ const snippet = content.slice(0, Math.min(5000, MAX_DIFF_CHARS - totalChars));
143
+ parts.push(`### ${relPath}\n\`\`\`\n${snippet}\n\`\`\``);
144
+ totalChars += snippet.length;
145
+ } catch { /* skip */ }
146
+ }
147
+
148
+ return parts.length ? parts.join('\n\n') : null;
149
+ }
150
+
151
+ _buildReconSummary(recon) {
152
+ if (!recon) return 'No recon data.';
153
+ const parts = [];
154
+ if (recon.frameworks?.length) parts.push(`Frameworks: ${recon.frameworks.join(', ')}`);
155
+ if (recon.databases?.length) parts.push(`Databases: ${recon.databases.join(', ')}`);
156
+ if (recon.authPatterns?.length) parts.push(`Auth: ${recon.authPatterns.join(', ')}`);
157
+ if (recon.languages?.length) parts.push(`Languages: ${recon.languages.join(', ')}`);
158
+ return parts.join('\n') || 'General codebase.';
159
+ }
160
+
161
+ async _callProvider(systemPrompt, messages) {
162
+ // Use multi-turn messages if provider supports it (OpenAI format)
163
+ if (this.provider.baseUrl && typeof this.provider.complete === 'function') {
164
+ const response = await fetch(this.provider.baseUrl, {
165
+ method: 'POST',
166
+ headers: {
167
+ 'Authorization': `Bearer ${this.provider.apiKey}`,
168
+ 'Content-Type': 'application/json',
169
+ },
170
+ body: JSON.stringify({
171
+ model: this.provider.model,
172
+ max_tokens: 2048,
173
+ messages: [
174
+ { role: 'system', content: systemPrompt },
175
+ ...messages,
176
+ ],
177
+ }),
178
+ });
179
+
180
+ if (!response.ok) {
181
+ throw new Error(`${this.provider.name} API error: HTTP ${response.status}`);
182
+ }
183
+
184
+ const data = await response.json();
185
+ return data.choices?.[0]?.message?.content || '';
186
+ }
187
+
188
+ // Fallback: single-turn (for providers without persistent context)
189
+ const lastMsg = messages[messages.length - 1];
190
+ return this.provider.complete(systemPrompt, lastMsg?.content || '', { maxTokens: 2048 });
191
+ }
192
+
193
+ _parseFindings(text, refFile) {
194
+ const cleaned = text
195
+ .replace(/^```(?:json)?\s*/i, '')
196
+ .replace(/\s*```\s*$/i, '')
197
+ .trim();
198
+
199
+ try {
200
+ const raw = JSON.parse(cleaned);
201
+ if (!Array.isArray(raw)) return [];
202
+
203
+ return raw
204
+ .filter(r => r.title && r.severity)
205
+ .map(r => {
206
+ const filePath = r.file
207
+ ? path.resolve(this.rootPath, r.file)
208
+ : refFile || null;
209
+
210
+ return createFinding({
211
+ file: filePath,
212
+ line: r.line || 0,
213
+ severity: ['critical', 'high', 'medium', 'low', 'info'].includes(r.severity) ? r.severity : 'medium',
214
+ confidence: 'medium',
215
+ rule: r.rule || 'stateful:monitor',
216
+ title: r.title,
217
+ description: r.description || r.title,
218
+ matched: '',
219
+ remediation: r.remediation || '',
220
+ category: 'Stateful Monitor',
221
+ });
222
+ });
223
+ } catch {
224
+ return [];
225
+ }
226
+ }
227
+
228
+ getStats() {
229
+ return {
230
+ scanCount: this._scanCount,
231
+ provider: this.provider?.name || 'none',
232
+ model: this.provider?.model || 'unknown',
233
+ messageCount: this._messages.length,
234
+ };
235
+ }
236
+ }
237
+
238
+ export default StatefulWatcher;
@@ -0,0 +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
+ export 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}`, // praxis-ignore SQL_INJECTION_TEMPLATE_LITERAL — detector matched-string construction
146
+ fix: `Add: ALTER TABLE ${table} ENABLE ROW LEVEL SECURITY;\nThen create appropriate policies with CREATE POLICY.`, // praxis-ignore SQL_INJECTION_TEMPLATE_LITERAL — dynamic detector text, not a real query
147
+ }));
148
+ }
149
+
150
+ return findings;
151
+ }
152
+ }
153
+
154
+ export default SupabaseRLSAgent;