supasec 1.0.3 → 1.0.5
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/Feature-List.md +233 -0
- package/README.md +53 -12
- package/dist/cli.js +2 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +1 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +82 -26
- package/dist/commands/scan.js.map +1 -1
- package/dist/commands/snapshot.d.ts +32 -0
- package/dist/commands/snapshot.d.ts.map +1 -0
- package/dist/commands/snapshot.js +282 -0
- package/dist/commands/snapshot.js.map +1 -0
- package/dist/reporters/html.d.ts +3 -2
- package/dist/reporters/html.d.ts.map +1 -1
- package/dist/reporters/html.js +844 -538
- package/dist/reporters/html.js.map +1 -1
- package/dist/reporters/terminal.d.ts +38 -2
- package/dist/reporters/terminal.d.ts.map +1 -1
- package/dist/reporters/terminal.js +292 -131
- package/dist/reporters/terminal.js.map +1 -1
- package/dist/scanners/auth/analyzer.d.ts +40 -0
- package/dist/scanners/auth/analyzer.d.ts.map +1 -0
- package/dist/scanners/auth/analyzer.js +673 -0
- package/dist/scanners/auth/analyzer.js.map +1 -0
- package/dist/scanners/auth/index.d.ts +6 -0
- package/dist/scanners/auth/index.d.ts.map +1 -0
- package/dist/scanners/auth/index.js +22 -0
- package/dist/scanners/auth/index.js.map +1 -0
- package/dist/scanners/edge/analyzer.d.ts +35 -0
- package/dist/scanners/edge/analyzer.d.ts.map +1 -0
- package/dist/scanners/edge/analyzer.js +614 -0
- package/dist/scanners/edge/analyzer.js.map +1 -0
- package/dist/scanners/edge/index.d.ts +6 -0
- package/dist/scanners/edge/index.d.ts.map +1 -0
- package/dist/scanners/edge/index.js +22 -0
- package/dist/scanners/edge/index.js.map +1 -0
- package/dist/scanners/functions/analyzer.d.ts +41 -0
- package/dist/scanners/functions/analyzer.d.ts.map +1 -0
- package/dist/scanners/functions/analyzer.js +378 -0
- package/dist/scanners/functions/analyzer.js.map +1 -0
- package/dist/scanners/functions/index.d.ts +6 -0
- package/dist/scanners/functions/index.d.ts.map +1 -0
- package/dist/scanners/functions/index.js +22 -0
- package/dist/scanners/functions/index.js.map +1 -0
- package/dist/scanners/git/index.d.ts +6 -0
- package/dist/scanners/git/index.d.ts.map +1 -0
- package/dist/scanners/git/index.js +22 -0
- package/dist/scanners/git/index.js.map +1 -0
- package/dist/scanners/git/scanner.d.ts +22 -0
- package/dist/scanners/git/scanner.d.ts.map +1 -0
- package/dist/scanners/git/scanner.js +531 -0
- package/dist/scanners/git/scanner.js.map +1 -0
- package/dist/scanners/https/analyzer.d.ts +42 -0
- package/dist/scanners/https/analyzer.d.ts.map +1 -0
- package/dist/scanners/https/analyzer.js +470 -0
- package/dist/scanners/https/analyzer.js.map +1 -0
- package/dist/scanners/https/index.d.ts +8 -0
- package/dist/scanners/https/index.d.ts.map +1 -0
- package/dist/scanners/https/index.js +17 -0
- package/dist/scanners/https/index.js.map +1 -0
- package/dist/scanners/index.d.ts +6 -0
- package/dist/scanners/index.d.ts.map +1 -1
- package/dist/scanners/index.js +6 -0
- package/dist/scanners/index.js.map +1 -1
- package/dist/scanners/rls/fuzzer.d.ts +40 -0
- package/dist/scanners/rls/fuzzer.d.ts.map +1 -0
- package/dist/scanners/rls/fuzzer.js +360 -0
- package/dist/scanners/rls/fuzzer.js.map +1 -0
- package/dist/scanners/rls/index.d.ts +1 -0
- package/dist/scanners/rls/index.d.ts.map +1 -1
- package/dist/scanners/rls/index.js +1 -0
- package/dist/scanners/rls/index.js.map +1 -1
- package/dist/scanners/secrets/detector.d.ts.map +1 -1
- package/dist/scanners/secrets/detector.js +44 -12
- package/dist/scanners/secrets/detector.js.map +1 -1
- package/dist/scanners/secrets/index.d.ts +1 -0
- package/dist/scanners/secrets/index.d.ts.map +1 -1
- package/dist/scanners/secrets/index.js +4 -0
- package/dist/scanners/secrets/index.js.map +1 -1
- package/dist/scanners/secrets/patterns.d.ts +25 -0
- package/dist/scanners/secrets/patterns.d.ts.map +1 -1
- package/dist/scanners/secrets/patterns.js +138 -27
- package/dist/scanners/secrets/patterns.js.map +1 -1
- package/dist/scanners/storage/analyzer.d.ts +49 -0
- package/dist/scanners/storage/analyzer.d.ts.map +1 -0
- package/dist/scanners/storage/analyzer.js +438 -0
- package/dist/scanners/storage/analyzer.js.map +1 -0
- package/dist/scanners/storage/index.d.ts +6 -0
- package/dist/scanners/storage/index.d.ts.map +1 -0
- package/dist/scanners/storage/index.js +22 -0
- package/dist/scanners/storage/index.js.map +1 -0
- package/package.json +1 -1
- package/reports/{supasec-audityour-app-2026-01-28-17-09-24.html → supasec-audityour-app-2026-01-28-19-42-22.html} +51 -16
- package/reports/supasec-audityour-app-2026-01-28-19-49-18.html +1122 -0
- package/COMPLETION_REPORT.md +0 -324
- package/FIXES_SUMMARY.md +0 -224
- package/IMPLEMENTATION_NOTES.md +0 -305
- package/QUICK_REFERENCE.md +0 -185
- package/REPORTING.md +0 -217
- package/STATUS.md +0 -269
- package/reports/supasec---------app-2026-01-28-16-58-47.html +0 -804
- package/reports/supasec---------app-2026-01-28-17-06-43.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-07-23.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-08-00.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-08-20.html +0 -722
- package/reports/supasec---------app-2026-01-28-17-08-41.html +0 -722
- package/reports/supasec-au---your-app-2026-01-28-17-14-57.html +0 -715
- package/reports/supasec-au---your-app-2026-01-28-17-19-03.html +0 -715
- package/reports/supasec-ex-mple-com-2026-01-28-17-14-52.json +0 -229
- package/reports/supasec-ex-mple-com-2026-01-28-17-15-39.html +0 -715
- package/reports/supasec-ex-mple-com-2026-01-28-17-17-22.html +0 -715
- package/reports/supasec-example-com-2026-01-28-17-15-06.html +0 -715
- package/reports/supasec-my--------------name-com-2026-01-28-17-15-02.html +0 -715
- package/reports/supasec-st-ging-com-2026-01-28-17-16-17.html +0 -715
|
@@ -0,0 +1,673 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Auth Configuration Analyzer
|
|
4
|
+
* Scans for authentication and authorization misconfigurations
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.analyzeAuth = analyzeAuth;
|
|
8
|
+
exports.getMockAuthConfig = getMockAuthConfig;
|
|
9
|
+
const finding_js_1 = require("../../models/finding.js");
|
|
10
|
+
/**
|
|
11
|
+
* Analyze auth configuration for security issues
|
|
12
|
+
*/
|
|
13
|
+
async function analyzeAuth(options) {
|
|
14
|
+
const findings = [];
|
|
15
|
+
let findingCounter = 1;
|
|
16
|
+
let checksPerformed = 0;
|
|
17
|
+
let score = 100;
|
|
18
|
+
// Check 1: Email verification
|
|
19
|
+
checksPerformed++;
|
|
20
|
+
if (!options.config.emailVerificationRequired) {
|
|
21
|
+
findings.push(createNoEmailVerificationFinding(findingCounter++));
|
|
22
|
+
score -= 15;
|
|
23
|
+
}
|
|
24
|
+
// Check 2: Password policy
|
|
25
|
+
checksPerformed++;
|
|
26
|
+
if (options.config.passwordMinLength < 8) {
|
|
27
|
+
findings.push(createWeakPasswordPolicyFinding(options.config.passwordMinLength, findingCounter++));
|
|
28
|
+
score -= 10;
|
|
29
|
+
}
|
|
30
|
+
// Check 3: Password strength
|
|
31
|
+
checksPerformed++;
|
|
32
|
+
if (options.config.passwordStrength === 'weak') {
|
|
33
|
+
findings.push(createWeakPasswordStrengthFinding(findingCounter++));
|
|
34
|
+
score -= 10;
|
|
35
|
+
}
|
|
36
|
+
// Check 4: MFA
|
|
37
|
+
checksPerformed++;
|
|
38
|
+
if (!options.config.mfaEnabled) {
|
|
39
|
+
findings.push(createNoMFAFinding(findingCounter++));
|
|
40
|
+
score -= 10;
|
|
41
|
+
}
|
|
42
|
+
else if (!options.config.mfaEnforced) {
|
|
43
|
+
findings.push(createMFAOptionalFinding(findingCounter++));
|
|
44
|
+
score -= 5;
|
|
45
|
+
}
|
|
46
|
+
// Check 5: JWT expiry
|
|
47
|
+
checksPerformed++;
|
|
48
|
+
if (options.config.jwtExpirySeconds > 3600) {
|
|
49
|
+
findings.push(createLongJWTExpiryFinding(options.config.jwtExpirySeconds, findingCounter++));
|
|
50
|
+
score -= 5;
|
|
51
|
+
}
|
|
52
|
+
// Check 6: Refresh token rotation
|
|
53
|
+
checksPerformed++;
|
|
54
|
+
if (!options.config.refreshTokenRotation) {
|
|
55
|
+
findings.push(createNoTokenRotationFinding(findingCounter++));
|
|
56
|
+
score -= 10;
|
|
57
|
+
}
|
|
58
|
+
// Check 7: Session timeout
|
|
59
|
+
checksPerformed++;
|
|
60
|
+
if (options.config.sessionTimeoutSeconds > 86400) {
|
|
61
|
+
findings.push(createLongSessionTimeoutFinding(options.config.sessionTimeoutSeconds, findingCounter++));
|
|
62
|
+
score -= 5;
|
|
63
|
+
}
|
|
64
|
+
// Check 8: Anonymous sign-ins
|
|
65
|
+
checksPerformed++;
|
|
66
|
+
if (options.config.allowAnonymousSignIns) {
|
|
67
|
+
findings.push(createAnonymousSignInFinding(findingCounter++));
|
|
68
|
+
score -= 5;
|
|
69
|
+
}
|
|
70
|
+
// Check 9: Secure email change
|
|
71
|
+
checksPerformed++;
|
|
72
|
+
if (!options.config.secureEmailChange) {
|
|
73
|
+
findings.push(createInsecureEmailChangeFinding(findingCounter++));
|
|
74
|
+
score -= 10;
|
|
75
|
+
}
|
|
76
|
+
// Check 10: Password reuse prevention
|
|
77
|
+
checksPerformed++;
|
|
78
|
+
if (options.config.passwordReusePrevention < 3) {
|
|
79
|
+
findings.push(createNoPasswordReusePreventionFinding(findingCounter++));
|
|
80
|
+
score -= 5;
|
|
81
|
+
}
|
|
82
|
+
// Check 11: OAuth providers security
|
|
83
|
+
checksPerformed++;
|
|
84
|
+
const insecureProviders = options.config.providers.filter(p => p.toLowerCase().includes('facebook') ||
|
|
85
|
+
(p.toLowerCase().includes('twitter') && !p.toLowerCase().includes('oauth2')));
|
|
86
|
+
if (insecureProviders.length > 0) {
|
|
87
|
+
findings.push(createInsecureOAuthProviderFinding(insecureProviders, findingCounter++));
|
|
88
|
+
score -= 5;
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
findings,
|
|
92
|
+
checksPerformed,
|
|
93
|
+
misconfigurations: findings.length,
|
|
94
|
+
score: Math.max(0, score)
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Create finding for missing email verification
|
|
99
|
+
*/
|
|
100
|
+
function createNoEmailVerificationFinding(counter) {
|
|
101
|
+
return {
|
|
102
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
103
|
+
timestamp: new Date().toISOString(),
|
|
104
|
+
severity: 'HIGH',
|
|
105
|
+
category: 'auth',
|
|
106
|
+
subcategory: 'no_email_verification',
|
|
107
|
+
title: 'Email verification is not required',
|
|
108
|
+
description: 'New users can access the application without verifying their email address. This allows account takeover through email typos or fake emails.',
|
|
109
|
+
evidence: {
|
|
110
|
+
email_verification_required: false
|
|
111
|
+
},
|
|
112
|
+
impact: {
|
|
113
|
+
severity_score: 7.5,
|
|
114
|
+
description: 'Account takeover risk - unverified users can access the application',
|
|
115
|
+
affected_resources: ['auth.users', 'auth.config'],
|
|
116
|
+
compliance_violations: ['OWASP-A07-2021', 'NIST-800-63B']
|
|
117
|
+
},
|
|
118
|
+
remediation: {
|
|
119
|
+
summary: 'Enable email verification requirement',
|
|
120
|
+
priority: 'HIGH',
|
|
121
|
+
effort: 'LOW',
|
|
122
|
+
steps: [
|
|
123
|
+
{
|
|
124
|
+
order: 1,
|
|
125
|
+
action: 'Enable email confirmation in Supabase Dashboard',
|
|
126
|
+
code: 'Go to Authentication > Settings > Email > Enable "Confirm email"'
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
order: 2,
|
|
130
|
+
action: 'Update client code to handle unverified users',
|
|
131
|
+
code: `// Check if email is verified before allowing access
|
|
132
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
133
|
+
if (!user?.email_confirmed_at) {
|
|
134
|
+
// Redirect to verification pending page
|
|
135
|
+
}`
|
|
136
|
+
}
|
|
137
|
+
],
|
|
138
|
+
auto_fixable: true
|
|
139
|
+
},
|
|
140
|
+
references: [
|
|
141
|
+
{
|
|
142
|
+
title: 'Supabase Email Auth',
|
|
143
|
+
url: 'https://supabase.com/docs/guides/auth/auth-email'
|
|
144
|
+
}
|
|
145
|
+
],
|
|
146
|
+
false_positive_likelihood: 'LOW',
|
|
147
|
+
confidence: 0.95
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Create finding for weak password policy
|
|
152
|
+
*/
|
|
153
|
+
function createWeakPasswordPolicyFinding(minLength, counter) {
|
|
154
|
+
return {
|
|
155
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
156
|
+
timestamp: new Date().toISOString(),
|
|
157
|
+
severity: 'MEDIUM',
|
|
158
|
+
category: 'auth',
|
|
159
|
+
subcategory: 'weak_password_policy',
|
|
160
|
+
title: `Weak password minimum length (${minLength} characters)`,
|
|
161
|
+
description: `The password policy only requires ${minLength} characters. NIST recommends at least 8 characters for passwords.`,
|
|
162
|
+
evidence: {
|
|
163
|
+
password_min_length: minLength,
|
|
164
|
+
recommended_min_length: 8
|
|
165
|
+
},
|
|
166
|
+
impact: {
|
|
167
|
+
severity_score: 5.0,
|
|
168
|
+
description: 'Short passwords are easier to crack through brute force',
|
|
169
|
+
affected_resources: ['auth.config'],
|
|
170
|
+
compliance_violations: ['NIST-800-63B']
|
|
171
|
+
},
|
|
172
|
+
remediation: {
|
|
173
|
+
summary: 'Increase minimum password length to 8+ characters',
|
|
174
|
+
priority: 'MEDIUM',
|
|
175
|
+
effort: 'LOW',
|
|
176
|
+
steps: [
|
|
177
|
+
{
|
|
178
|
+
order: 1,
|
|
179
|
+
action: 'Update password policy in Supabase Dashboard',
|
|
180
|
+
code: 'Go to Authentication > Policies > Set "Minimum password length" to 8+'
|
|
181
|
+
}
|
|
182
|
+
],
|
|
183
|
+
auto_fixable: true
|
|
184
|
+
},
|
|
185
|
+
references: [
|
|
186
|
+
{
|
|
187
|
+
title: 'NIST Password Guidelines',
|
|
188
|
+
url: 'https://pages.nist.gov/800-63-3/sp800-63b.html#sec5'
|
|
189
|
+
}
|
|
190
|
+
],
|
|
191
|
+
false_positive_likelihood: 'LOW',
|
|
192
|
+
confidence: 0.9
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Create finding for weak password strength
|
|
197
|
+
*/
|
|
198
|
+
function createWeakPasswordStrengthFinding(counter) {
|
|
199
|
+
return {
|
|
200
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
201
|
+
timestamp: new Date().toISOString(),
|
|
202
|
+
severity: 'MEDIUM',
|
|
203
|
+
category: 'auth',
|
|
204
|
+
subcategory: 'weak_password_strength',
|
|
205
|
+
title: 'Password strength policy is set to weak',
|
|
206
|
+
description: 'The password strength policy is set to "weak", allowing common passwords and simple patterns.',
|
|
207
|
+
evidence: {
|
|
208
|
+
password_strength: 'weak'
|
|
209
|
+
},
|
|
210
|
+
impact: {
|
|
211
|
+
severity_score: 5.5,
|
|
212
|
+
description: 'Weak passwords increase risk of account compromise',
|
|
213
|
+
affected_resources: ['auth.config']
|
|
214
|
+
},
|
|
215
|
+
remediation: {
|
|
216
|
+
summary: 'Strengthen password policy',
|
|
217
|
+
priority: 'MEDIUM',
|
|
218
|
+
effort: 'LOW',
|
|
219
|
+
steps: [
|
|
220
|
+
{
|
|
221
|
+
order: 1,
|
|
222
|
+
action: 'Set password strength to "strong"',
|
|
223
|
+
code: 'Go to Authentication > Policies > Set "Password strength" to Strong'
|
|
224
|
+
}
|
|
225
|
+
],
|
|
226
|
+
auto_fixable: true
|
|
227
|
+
},
|
|
228
|
+
references: [
|
|
229
|
+
{
|
|
230
|
+
title: 'OWASP Password Security',
|
|
231
|
+
url: 'https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html'
|
|
232
|
+
}
|
|
233
|
+
],
|
|
234
|
+
false_positive_likelihood: 'LOW',
|
|
235
|
+
confidence: 0.9
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
/**
|
|
239
|
+
* Create finding for missing MFA
|
|
240
|
+
*/
|
|
241
|
+
function createNoMFAFinding(counter) {
|
|
242
|
+
return {
|
|
243
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
244
|
+
timestamp: new Date().toISOString(),
|
|
245
|
+
severity: 'MEDIUM',
|
|
246
|
+
category: 'auth',
|
|
247
|
+
subcategory: 'no_mfa',
|
|
248
|
+
title: 'Multi-factor authentication is not enabled',
|
|
249
|
+
description: 'MFA is not enabled for the project. Users can only authenticate with a single factor (password).',
|
|
250
|
+
evidence: {
|
|
251
|
+
mfa_enabled: false
|
|
252
|
+
},
|
|
253
|
+
impact: {
|
|
254
|
+
severity_score: 6.0,
|
|
255
|
+
description: 'Single factor authentication - password compromise leads to account takeover',
|
|
256
|
+
affected_resources: ['auth.config'],
|
|
257
|
+
compliance_violations: ['NIST-800-63B', 'SOC2-CC6.1']
|
|
258
|
+
},
|
|
259
|
+
remediation: {
|
|
260
|
+
summary: 'Enable multi-factor authentication',
|
|
261
|
+
priority: 'MEDIUM',
|
|
262
|
+
effort: 'MEDIUM',
|
|
263
|
+
steps: [
|
|
264
|
+
{
|
|
265
|
+
order: 1,
|
|
266
|
+
action: 'Enable MFA in Supabase Dashboard',
|
|
267
|
+
code: 'Go to Authentication > Settings > MFA > Enable MFA'
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
order: 2,
|
|
271
|
+
action: 'Implement MFA enrollment flow in your app',
|
|
272
|
+
code: `// Enroll MFA
|
|
273
|
+
const { data, error } = await supabase.auth.mfa.enroll({
|
|
274
|
+
factorType: 'totp'
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Verify MFA
|
|
278
|
+
const { data, error } = await supabase.auth.mfa.verify({
|
|
279
|
+
factorId,
|
|
280
|
+
code: '123456'
|
|
281
|
+
});`
|
|
282
|
+
}
|
|
283
|
+
],
|
|
284
|
+
auto_fixable: false
|
|
285
|
+
},
|
|
286
|
+
references: [
|
|
287
|
+
{
|
|
288
|
+
title: 'Supabase MFA Guide',
|
|
289
|
+
url: 'https://supabase.com/docs/guides/auth/auth-mfa'
|
|
290
|
+
}
|
|
291
|
+
],
|
|
292
|
+
false_positive_likelihood: 'MEDIUM',
|
|
293
|
+
confidence: 0.85
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Create finding for optional MFA
|
|
298
|
+
*/
|
|
299
|
+
function createMFAOptionalFinding(counter) {
|
|
300
|
+
return {
|
|
301
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
302
|
+
timestamp: new Date().toISOString(),
|
|
303
|
+
severity: 'LOW',
|
|
304
|
+
category: 'auth',
|
|
305
|
+
subcategory: 'mfa_optional',
|
|
306
|
+
title: 'Multi-factor authentication is optional',
|
|
307
|
+
description: 'MFA is enabled but not enforced. Users can choose whether to enable it.',
|
|
308
|
+
evidence: {
|
|
309
|
+
mfa_enabled: true,
|
|
310
|
+
mfa_enforced: false
|
|
311
|
+
},
|
|
312
|
+
impact: {
|
|
313
|
+
severity_score: 3.0,
|
|
314
|
+
description: 'Users may skip MFA, leaving accounts vulnerable',
|
|
315
|
+
affected_resources: ['auth.config']
|
|
316
|
+
},
|
|
317
|
+
remediation: {
|
|
318
|
+
summary: 'Consider enforcing MFA for sensitive operations',
|
|
319
|
+
priority: 'LOW',
|
|
320
|
+
effort: 'MEDIUM',
|
|
321
|
+
steps: [
|
|
322
|
+
{
|
|
323
|
+
order: 1,
|
|
324
|
+
action: 'Require MFA for sensitive operations',
|
|
325
|
+
code: `// Check MFA status before sensitive operations
|
|
326
|
+
const { data: { user } } = await supabase.auth.getUser();
|
|
327
|
+
const aal = user?.aal;
|
|
328
|
+
|
|
329
|
+
if (aal !== 'aal2') {
|
|
330
|
+
// Require MFA verification
|
|
331
|
+
await supabase.auth.mfa.challenge({ factorId });
|
|
332
|
+
}`
|
|
333
|
+
}
|
|
334
|
+
],
|
|
335
|
+
auto_fixable: false
|
|
336
|
+
},
|
|
337
|
+
references: [
|
|
338
|
+
{
|
|
339
|
+
title: 'Supabase AAL (Authenticator Assurance Level)',
|
|
340
|
+
url: 'https://supabase.com/docs/guides/auth/auth-mfa#enforcing-mfa-for-a-user'
|
|
341
|
+
}
|
|
342
|
+
],
|
|
343
|
+
false_positive_likelihood: 'HIGH',
|
|
344
|
+
confidence: 0.7
|
|
345
|
+
};
|
|
346
|
+
}
|
|
347
|
+
/**
|
|
348
|
+
* Create finding for long JWT expiry
|
|
349
|
+
*/
|
|
350
|
+
function createLongJWTExpiryFinding(expirySeconds, counter) {
|
|
351
|
+
const hours = Math.round(expirySeconds / 3600);
|
|
352
|
+
return {
|
|
353
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
354
|
+
timestamp: new Date().toISOString(),
|
|
355
|
+
severity: 'LOW',
|
|
356
|
+
category: 'auth',
|
|
357
|
+
subcategory: 'long_jwt_expiry',
|
|
358
|
+
title: `JWT tokens expire after ${hours} hours`,
|
|
359
|
+
description: `JWT tokens have a long expiry time (${hours} hours). Shorter expiry times reduce the window of opportunity for token theft.`,
|
|
360
|
+
evidence: {
|
|
361
|
+
jwt_expiry_seconds: expirySeconds,
|
|
362
|
+
jwt_expiry_hours: hours
|
|
363
|
+
},
|
|
364
|
+
impact: {
|
|
365
|
+
severity_score: 3.0,
|
|
366
|
+
description: 'Long-lived tokens increase risk if stolen',
|
|
367
|
+
affected_resources: ['auth.config']
|
|
368
|
+
},
|
|
369
|
+
remediation: {
|
|
370
|
+
summary: 'Reduce JWT expiry time to 1 hour or less',
|
|
371
|
+
priority: 'LOW',
|
|
372
|
+
effort: 'LOW',
|
|
373
|
+
steps: [
|
|
374
|
+
{
|
|
375
|
+
order: 1,
|
|
376
|
+
action: 'Set JWT expiry to 3600 seconds (1 hour)',
|
|
377
|
+
code: 'Go to Authentication > Settings > JWT Settings > JWT expiry limit: 3600'
|
|
378
|
+
}
|
|
379
|
+
],
|
|
380
|
+
auto_fixable: true
|
|
381
|
+
},
|
|
382
|
+
references: [
|
|
383
|
+
{
|
|
384
|
+
title: 'JWT Security Best Practices',
|
|
385
|
+
url: 'https://tools.ietf.org/html/rfc8725'
|
|
386
|
+
}
|
|
387
|
+
],
|
|
388
|
+
false_positive_likelihood: 'HIGH',
|
|
389
|
+
confidence: 0.6
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Create finding for no token rotation
|
|
394
|
+
*/
|
|
395
|
+
function createNoTokenRotationFinding(counter) {
|
|
396
|
+
return {
|
|
397
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
398
|
+
timestamp: new Date().toISOString(),
|
|
399
|
+
severity: 'MEDIUM',
|
|
400
|
+
category: 'auth',
|
|
401
|
+
subcategory: 'no_token_rotation',
|
|
402
|
+
title: 'Refresh token rotation is disabled',
|
|
403
|
+
description: 'Refresh tokens are not rotated on use. If a refresh token is stolen, it can be used indefinitely.',
|
|
404
|
+
evidence: {
|
|
405
|
+
refresh_token_rotation: false
|
|
406
|
+
},
|
|
407
|
+
impact: {
|
|
408
|
+
severity_score: 6.0,
|
|
409
|
+
description: 'Stolen refresh tokens remain valid indefinitely',
|
|
410
|
+
affected_resources: ['auth.config']
|
|
411
|
+
},
|
|
412
|
+
remediation: {
|
|
413
|
+
summary: 'Enable refresh token rotation',
|
|
414
|
+
priority: 'MEDIUM',
|
|
415
|
+
effort: 'LOW',
|
|
416
|
+
steps: [
|
|
417
|
+
{
|
|
418
|
+
order: 1,
|
|
419
|
+
action: 'Enable token rotation in Supabase Dashboard',
|
|
420
|
+
code: 'Go to Authentication > Settings > Security > Enable "Refresh token rotation"'
|
|
421
|
+
}
|
|
422
|
+
],
|
|
423
|
+
auto_fixable: true
|
|
424
|
+
},
|
|
425
|
+
references: [
|
|
426
|
+
{
|
|
427
|
+
title: 'OWASP Token Security',
|
|
428
|
+
url: 'https://cheatsheetseries.owasp.org/cheatsheets/JSON_Web_Token_for_Java_Cheat_Sheet.html'
|
|
429
|
+
}
|
|
430
|
+
],
|
|
431
|
+
false_positive_likelihood: 'LOW',
|
|
432
|
+
confidence: 0.9
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Create finding for long session timeout
|
|
437
|
+
*/
|
|
438
|
+
function createLongSessionTimeoutFinding(timeoutSeconds, counter) {
|
|
439
|
+
const days = Math.round(timeoutSeconds / 86400);
|
|
440
|
+
return {
|
|
441
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
442
|
+
timestamp: new Date().toISOString(),
|
|
443
|
+
severity: 'LOW',
|
|
444
|
+
category: 'auth',
|
|
445
|
+
subcategory: 'long_session_timeout',
|
|
446
|
+
title: `Sessions expire after ${days} days`,
|
|
447
|
+
description: `User sessions remain active for ${days} days. Shorter timeouts reduce risk on shared devices.`,
|
|
448
|
+
evidence: {
|
|
449
|
+
session_timeout_seconds: timeoutSeconds,
|
|
450
|
+
session_timeout_days: days
|
|
451
|
+
},
|
|
452
|
+
impact: {
|
|
453
|
+
severity_score: 2.5,
|
|
454
|
+
description: 'Long sessions increase risk on shared devices',
|
|
455
|
+
affected_resources: ['auth.config']
|
|
456
|
+
},
|
|
457
|
+
remediation: {
|
|
458
|
+
summary: 'Reduce session timeout to 24 hours or less',
|
|
459
|
+
priority: 'LOW',
|
|
460
|
+
effort: 'LOW',
|
|
461
|
+
steps: [
|
|
462
|
+
{
|
|
463
|
+
order: 1,
|
|
464
|
+
action: 'Set session timeout to 86400 seconds (24 hours)',
|
|
465
|
+
code: 'Go to Authentication > Settings > Sessions > Inactivity timeout: 86400'
|
|
466
|
+
}
|
|
467
|
+
],
|
|
468
|
+
auto_fixable: true
|
|
469
|
+
},
|
|
470
|
+
references: [
|
|
471
|
+
{
|
|
472
|
+
title: 'Session Management Best Practices',
|
|
473
|
+
url: 'https://cheatsheetseries.owasp.org/cheatsheets/Session_Management_Cheat_Sheet.html'
|
|
474
|
+
}
|
|
475
|
+
],
|
|
476
|
+
false_positive_likelihood: 'HIGH',
|
|
477
|
+
confidence: 0.6
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
/**
|
|
481
|
+
* Create finding for anonymous sign-ins
|
|
482
|
+
*/
|
|
483
|
+
function createAnonymousSignInFinding(counter) {
|
|
484
|
+
return {
|
|
485
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
486
|
+
timestamp: new Date().toISOString(),
|
|
487
|
+
severity: 'LOW',
|
|
488
|
+
category: 'auth',
|
|
489
|
+
subcategory: 'anonymous_signin',
|
|
490
|
+
title: 'Anonymous sign-ins are enabled',
|
|
491
|
+
description: 'Anonymous sign-ins allow users to access the application without providing any credentials.',
|
|
492
|
+
evidence: {
|
|
493
|
+
allow_anonymous_signins: true
|
|
494
|
+
},
|
|
495
|
+
impact: {
|
|
496
|
+
severity_score: 3.0,
|
|
497
|
+
description: 'Anonymous users may abuse resources or access restricted features',
|
|
498
|
+
affected_resources: ['auth.config']
|
|
499
|
+
},
|
|
500
|
+
remediation: {
|
|
501
|
+
summary: 'Review if anonymous sign-ins are necessary',
|
|
502
|
+
priority: 'LOW',
|
|
503
|
+
effort: 'LOW',
|
|
504
|
+
steps: [
|
|
505
|
+
{
|
|
506
|
+
order: 1,
|
|
507
|
+
action: 'Disable anonymous sign-ins if not needed',
|
|
508
|
+
code: 'Go to Authentication > Settings > Enable anonymous sign-ins: OFF'
|
|
509
|
+
}
|
|
510
|
+
],
|
|
511
|
+
auto_fixable: true
|
|
512
|
+
},
|
|
513
|
+
references: [
|
|
514
|
+
{
|
|
515
|
+
title: 'Supabase Anonymous Sign-Ins',
|
|
516
|
+
url: 'https://supabase.com/docs/guides/auth/auth-anonymous'
|
|
517
|
+
}
|
|
518
|
+
],
|
|
519
|
+
false_positive_likelihood: 'HIGH',
|
|
520
|
+
confidence: 0.7
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Create finding for insecure email change
|
|
525
|
+
*/
|
|
526
|
+
function createInsecureEmailChangeFinding(counter) {
|
|
527
|
+
return {
|
|
528
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
529
|
+
timestamp: new Date().toISOString(),
|
|
530
|
+
severity: 'HIGH',
|
|
531
|
+
category: 'auth',
|
|
532
|
+
subcategory: 'insecure_email_change',
|
|
533
|
+
title: 'Secure email change is disabled',
|
|
534
|
+
description: 'Users can change their email address without verification. This enables account takeover.',
|
|
535
|
+
evidence: {
|
|
536
|
+
secure_email_change: false
|
|
537
|
+
},
|
|
538
|
+
impact: {
|
|
539
|
+
severity_score: 8.0,
|
|
540
|
+
description: 'Account takeover through email change - attacker can lock out legitimate user',
|
|
541
|
+
affected_resources: ['auth.config'],
|
|
542
|
+
compliance_violations: ['OWASP-A07-2021']
|
|
543
|
+
},
|
|
544
|
+
remediation: {
|
|
545
|
+
summary: 'Enable secure email change requiring confirmation',
|
|
546
|
+
priority: 'HIGH',
|
|
547
|
+
effort: 'LOW',
|
|
548
|
+
steps: [
|
|
549
|
+
{
|
|
550
|
+
order: 1,
|
|
551
|
+
action: 'Enable secure email change',
|
|
552
|
+
code: 'Go to Authentication > Settings > Email > Enable "Secure email change"'
|
|
553
|
+
}
|
|
554
|
+
],
|
|
555
|
+
auto_fixable: true
|
|
556
|
+
},
|
|
557
|
+
references: [
|
|
558
|
+
{
|
|
559
|
+
title: 'Supabase Email Security',
|
|
560
|
+
url: 'https://supabase.com/docs/guides/auth/auth-email'
|
|
561
|
+
}
|
|
562
|
+
],
|
|
563
|
+
false_positive_likelihood: 'LOW',
|
|
564
|
+
confidence: 0.95
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
/**
|
|
568
|
+
* Create finding for no password reuse prevention
|
|
569
|
+
*/
|
|
570
|
+
function createNoPasswordReusePreventionFinding(counter) {
|
|
571
|
+
return {
|
|
572
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
573
|
+
timestamp: new Date().toISOString(),
|
|
574
|
+
severity: 'LOW',
|
|
575
|
+
category: 'auth',
|
|
576
|
+
subcategory: 'password_reuse',
|
|
577
|
+
title: 'Password reuse prevention is weak',
|
|
578
|
+
description: 'The system does not prevent users from reusing recent passwords.',
|
|
579
|
+
evidence: {
|
|
580
|
+
password_reuse_prevention: 'insufficient'
|
|
581
|
+
},
|
|
582
|
+
impact: {
|
|
583
|
+
severity_score: 3.0,
|
|
584
|
+
description: 'Users can cycle through same passwords, defeating password rotation policies',
|
|
585
|
+
affected_resources: ['auth.config']
|
|
586
|
+
},
|
|
587
|
+
remediation: {
|
|
588
|
+
summary: 'Increase password history retention',
|
|
589
|
+
priority: 'LOW',
|
|
590
|
+
effort: 'LOW',
|
|
591
|
+
steps: [
|
|
592
|
+
{
|
|
593
|
+
order: 1,
|
|
594
|
+
action: 'Set password history to at least 3 previous passwords',
|
|
595
|
+
code: 'Configure password history in your auth policies'
|
|
596
|
+
}
|
|
597
|
+
],
|
|
598
|
+
auto_fixable: false
|
|
599
|
+
},
|
|
600
|
+
references: [
|
|
601
|
+
{
|
|
602
|
+
title: 'NIST Password Guidelines',
|
|
603
|
+
url: 'https://pages.nist.gov/800-63-3/sp800-63b.html#sec5'
|
|
604
|
+
}
|
|
605
|
+
],
|
|
606
|
+
false_positive_likelihood: 'HIGH',
|
|
607
|
+
confidence: 0.6
|
|
608
|
+
};
|
|
609
|
+
}
|
|
610
|
+
/**
|
|
611
|
+
* Create finding for insecure OAuth providers
|
|
612
|
+
*/
|
|
613
|
+
function createInsecureOAuthProviderFinding(providers, counter) {
|
|
614
|
+
return {
|
|
615
|
+
finding_id: (0, finding_js_1.generateFindingId)('auth', counter),
|
|
616
|
+
timestamp: new Date().toISOString(),
|
|
617
|
+
severity: 'LOW',
|
|
618
|
+
category: 'auth',
|
|
619
|
+
subcategory: 'insecure_oauth',
|
|
620
|
+
title: 'Potentially insecure OAuth providers configured',
|
|
621
|
+
description: `The following OAuth providers may have weaker security: ${providers.join(', ')}. Consider using OAuth 2.0 providers with PKCE.`,
|
|
622
|
+
evidence: {
|
|
623
|
+
providers
|
|
624
|
+
},
|
|
625
|
+
impact: {
|
|
626
|
+
severity_score: 2.5,
|
|
627
|
+
description: 'Some OAuth providers have weaker security guarantees',
|
|
628
|
+
affected_resources: ['auth.config']
|
|
629
|
+
},
|
|
630
|
+
remediation: {
|
|
631
|
+
summary: 'Review OAuth provider security',
|
|
632
|
+
priority: 'LOW',
|
|
633
|
+
effort: 'MEDIUM',
|
|
634
|
+
steps: [
|
|
635
|
+
{
|
|
636
|
+
order: 1,
|
|
637
|
+
action: 'Use OAuth 2.0 with PKCE',
|
|
638
|
+
code: 'Configure providers to use OAuth 2.0 with PKCE flow'
|
|
639
|
+
}
|
|
640
|
+
],
|
|
641
|
+
auto_fixable: false
|
|
642
|
+
},
|
|
643
|
+
references: [
|
|
644
|
+
{
|
|
645
|
+
title: 'OAuth 2.0 Security Best Practices',
|
|
646
|
+
url: 'https://tools.ietf.org/html/rfc6819'
|
|
647
|
+
}
|
|
648
|
+
],
|
|
649
|
+
false_positive_likelihood: 'HIGH',
|
|
650
|
+
confidence: 0.6
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
/**
|
|
654
|
+
* Mock auth config for testing
|
|
655
|
+
*/
|
|
656
|
+
function getMockAuthConfig() {
|
|
657
|
+
return {
|
|
658
|
+
emailVerificationRequired: false,
|
|
659
|
+
emailConfirmationRequired: false,
|
|
660
|
+
passwordMinLength: 6,
|
|
661
|
+
passwordStrength: 'weak',
|
|
662
|
+
mfaEnabled: false,
|
|
663
|
+
mfaEnforced: false,
|
|
664
|
+
providers: ['google', 'github', 'facebook'],
|
|
665
|
+
jwtExpirySeconds: 7200,
|
|
666
|
+
refreshTokenRotation: false,
|
|
667
|
+
sessionTimeoutSeconds: 604800,
|
|
668
|
+
allowAnonymousSignIns: true,
|
|
669
|
+
secureEmailChange: false,
|
|
670
|
+
passwordReusePrevention: 0
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
//# sourceMappingURL=analyzer.js.map
|