@soulbatical/tetra-dev-toolkit 1.11.0 → 1.12.1

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,188 @@
1
+ /**
2
+ * Mixed Database Usage Detection — HARD BLOCK
3
+ *
4
+ * Controllers MUST use only ONE type of database helper.
5
+ * The DB helper must match the controller naming convention:
6
+ *
7
+ * AdminXxxController → adminDB(req) only
8
+ * UserXxxController → userDB(req) only
9
+ * PublicXxxController → publicDB() only
10
+ * SystemXxxController → systemDB(context) only
11
+ * SuperadminXxxController → superadminDB(req) only
12
+ * WebhookXxxController → systemDB(context) only (webhooks have no user session)
13
+ *
14
+ * Violations:
15
+ * - CRITICAL: Controller uses systemDB when user context is available (should be adminDB/userDB)
16
+ * - HIGH: Controller mixes multiple DB helper types
17
+ * - MEDIUM: Controller DB helper doesn't match naming convention
18
+ *
19
+ * Reference: stella_howto_get slug="tetra-architecture-guide"
20
+ */
21
+
22
+ import { readFileSync, existsSync } from 'fs'
23
+ import { join, basename, dirname } from 'path'
24
+ import { glob } from 'glob'
25
+
26
+ export const meta = {
27
+ id: 'mixed-db-usage',
28
+ name: 'Mixed DB Usage',
29
+ category: 'security',
30
+ severity: 'critical',
31
+ description: 'Controllers must use exactly ONE DB helper type matching their naming convention. Detects systemDB misuse when user context is available.'
32
+ }
33
+
34
+ const DB_PATTERNS = {
35
+ systemDB: { level: 'SYSTEM', pattern: /systemDB\s*\(/g, desc: 'System-level (cron, webhooks)' },
36
+ adminDB: { level: 'ADMIN', pattern: /adminDB\s*\(/g, desc: 'Admin operations (org-scoped)' },
37
+ userDB: { level: 'USER', pattern: /userDB\s*\(/g, desc: 'User-specific operations' },
38
+ publicDB: { level: 'PUBLIC', pattern: /publicDB\s*\(/g, desc: 'Public/unauthenticated' },
39
+ superadminDB: { level: 'SUPERADMIN', pattern: /superadminDB\s*\(/g, desc: 'Cross-org superadmin' }
40
+ }
41
+
42
+ /**
43
+ * Determine expected DB level from controller filename
44
+ */
45
+ function getExpectedLevel(fileName) {
46
+ const lower = fileName.toLowerCase()
47
+ // Order matters — check compound names first
48
+ if (lower.includes('system')) return 'SYSTEM'
49
+ if (lower.includes('superadmin')) return 'SUPERADMIN'
50
+ if (lower.includes('webhook')) return 'SYSTEM' // webhooks have no user session
51
+ if (lower.includes('cron')) return 'SYSTEM'
52
+ if (lower.includes('admin')) return 'ADMIN'
53
+ if (lower.includes('user')) return 'USER'
54
+ if (lower.includes('public')) return 'PUBLIC'
55
+ return null
56
+ }
57
+
58
+ /**
59
+ * Check if file has user authentication context available
60
+ */
61
+ function hasUserContext(content) {
62
+ return content.includes('AuthenticatedRequest') ||
63
+ content.includes('req.userToken') ||
64
+ content.includes('req.user') ||
65
+ content.includes('authenticateToken')
66
+ }
67
+
68
+ export async function run(config, projectRoot) {
69
+ const results = {
70
+ name: 'Mixed DB Usage',
71
+ slug: 'mixed-db-usage',
72
+ passed: true,
73
+ skipped: false,
74
+ findings: [],
75
+ summary: { total: 0, critical: 0, high: 0, medium: 0, low: 0 },
76
+ details: { controllersChecked: 0, violations: 0 }
77
+ }
78
+
79
+ const files = await glob('**/*[Cc]ontroller*.ts', {
80
+ cwd: projectRoot,
81
+ ignore: [
82
+ '**/node_modules/**',
83
+ '**/.next/**',
84
+ '**/dist/**',
85
+ '**/build/**',
86
+ ...(config.ignore || []),
87
+ '**/*.test.ts',
88
+ '**/*.spec.ts',
89
+ '**/*.d.ts'
90
+ ]
91
+ })
92
+
93
+ if (files.length === 0) {
94
+ results.skipped = true
95
+ results.skipReason = 'No controller files found'
96
+ return results
97
+ }
98
+
99
+ for (const file of files) {
100
+ const fullPath = join(projectRoot, file)
101
+ let content
102
+ try { content = readFileSync(fullPath, 'utf-8') } catch { continue }
103
+
104
+ results.details.controllersChecked++
105
+ const fileName = basename(file)
106
+
107
+ // Detect all DB usages in this file
108
+ const dbUsages = new Map() // level -> [{type, line, code}]
109
+ const lines = content.split('\n')
110
+
111
+ for (const [dbType, cfg] of Object.entries(DB_PATTERNS)) {
112
+ lines.forEach((line, idx) => {
113
+ // Reset regex lastIndex
114
+ cfg.pattern.lastIndex = 0
115
+ if (cfg.pattern.test(line)) {
116
+ const level = cfg.level
117
+ if (!dbUsages.has(level)) dbUsages.set(level, [])
118
+ dbUsages.get(level).push({
119
+ type: dbType,
120
+ line: idx + 1,
121
+ code: line.trim().substring(0, 120)
122
+ })
123
+ }
124
+ })
125
+ }
126
+
127
+ if (dbUsages.size === 0) continue
128
+
129
+ const expectedLevel = getExpectedLevel(fileName)
130
+ const usedLevels = [...dbUsages.keys()]
131
+ const userContextAvailable = hasUserContext(content)
132
+
133
+ // CRITICAL: systemDB used when user context is available and it's not a system controller
134
+ if (dbUsages.has('SYSTEM') && userContextAvailable && expectedLevel !== 'SYSTEM') {
135
+ results.passed = false
136
+ const locs = dbUsages.get('SYSTEM')
137
+ for (const loc of locs) {
138
+ results.findings.push({
139
+ file,
140
+ line: loc.line,
141
+ type: 'systemDB with user context',
142
+ severity: 'critical',
143
+ message: `${fileName} uses systemDB() but has user authentication context → MUST use ${expectedLevel === 'ADMIN' ? 'adminDB(req)' : expectedLevel === 'USER' ? 'userDB(req)' : 'adminDB(req) or userDB(req)'}`,
144
+ snippet: loc.code,
145
+ fix: `Replace systemDB('context') with ${expectedLevel === 'ADMIN' ? 'adminDB(req)' : 'userDB(req)'}. systemDB bypasses RLS — user operations MUST go through RLS. See: stella_howto_get slug="tetra-architecture-guide"`
146
+ })
147
+ results.summary.critical++
148
+ results.summary.total++
149
+ }
150
+ }
151
+
152
+ // HIGH: Multiple DB helper types in one controller
153
+ if (usedLevels.length > 1) {
154
+ results.passed = false
155
+ results.findings.push({
156
+ file,
157
+ line: 1,
158
+ type: 'mixed db usage',
159
+ severity: 'high',
160
+ message: `${fileName} mixes ${usedLevels.join(' + ')} database helpers. Controllers MUST use exactly ONE DB helper type.`,
161
+ fix: `Split into separate controllers per DB level, or refactor services to accept injected supabase client. Admin + System mix → move system ops to a separate SystemXxxController.`
162
+ })
163
+ results.summary.high++
164
+ results.summary.total++
165
+ }
166
+
167
+ // MEDIUM: DB helper doesn't match naming convention
168
+ if (expectedLevel && usedLevels.length === 1 && !usedLevels.includes(expectedLevel)) {
169
+ const actualLevel = usedLevels[0]
170
+ // Don't double-report if already caught as critical
171
+ if (!(actualLevel === 'SYSTEM' && userContextAvailable)) {
172
+ results.findings.push({
173
+ file,
174
+ line: 1,
175
+ type: 'naming mismatch',
176
+ severity: 'medium',
177
+ message: `${fileName} implies ${expectedLevel} but uses ${actualLevel}. Either rename the controller or change the DB helper.`,
178
+ fix: `Rename to match DB level, or change DB helper to match controller naming convention.`
179
+ })
180
+ results.summary.medium++
181
+ results.summary.total++
182
+ }
183
+ }
184
+ }
185
+
186
+ results.details.violations = results.findings.length
187
+ return results
188
+ }
package/lib/runner.js CHANGED
@@ -13,6 +13,7 @@ import * as deprecatedSupabaseAdmin from './checks/security/deprecated-supabase-
13
13
  import * as directSupabaseClient from './checks/security/direct-supabase-client.js'
14
14
  import * as frontendSupabaseQueries from './checks/security/frontend-supabase-queries.js'
15
15
  import * as tetraCoreCompliance from './checks/security/tetra-core-compliance.js'
16
+ import * as mixedDbUsage from './checks/security/mixed-db-usage.js'
16
17
  import * as systemdbWhitelist from './checks/security/systemdb-whitelist.js'
17
18
  import * as huskyHooks from './checks/stability/husky-hooks.js'
18
19
  import * as ciPipeline from './checks/stability/ci-pipeline.js'
@@ -37,6 +38,7 @@ const ALL_CHECKS = {
37
38
  directSupabaseClient,
38
39
  frontendSupabaseQueries,
39
40
  tetraCoreCompliance,
41
+ mixedDbUsage,
40
42
  systemdbWhitelist,
41
43
  gitignoreValidation
42
44
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@soulbatical/tetra-dev-toolkit",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
4
4
  "publishConfig": {
5
5
  "access": "restricted"
6
6
  },