@sun-asterisk/sunlint 1.3.7 → 1.3.8

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 (38) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/config/defaults/default.json +2 -1
  3. package/config/rule-analysis-strategies.js +20 -0
  4. package/config/rules/enhanced-rules-registry.json +190 -35
  5. package/core/file-targeting-service.js +83 -7
  6. package/package.json +1 -1
  7. package/rules/common/C065_one_behavior_per_test/analyzer.js +851 -0
  8. package/rules/common/C065_one_behavior_per_test/config.json +95 -0
  9. package/rules/security/S037_cache_headers/README.md +128 -0
  10. package/rules/security/S037_cache_headers/analyzer.js +263 -0
  11. package/rules/security/S037_cache_headers/config.json +50 -0
  12. package/rules/security/S037_cache_headers/regex-based-analyzer.js +463 -0
  13. package/rules/security/S037_cache_headers/symbol-based-analyzer.js +546 -0
  14. package/rules/security/S038_no_version_headers/README.md +234 -0
  15. package/rules/security/S038_no_version_headers/analyzer.js +262 -0
  16. package/rules/security/S038_no_version_headers/config.json +49 -0
  17. package/rules/security/S038_no_version_headers/regex-based-analyzer.js +339 -0
  18. package/rules/security/S038_no_version_headers/symbol-based-analyzer.js +375 -0
  19. package/rules/security/S039_no_session_tokens_in_url/README.md +198 -0
  20. package/rules/security/S039_no_session_tokens_in_url/analyzer.js +262 -0
  21. package/rules/security/S039_no_session_tokens_in_url/config.json +92 -0
  22. package/rules/security/S039_no_session_tokens_in_url/regex-based-analyzer.js +337 -0
  23. package/rules/security/S039_no_session_tokens_in_url/symbol-based-analyzer.js +436 -0
  24. package/rules/security/S049_short_validity_tokens/analyzer.js +175 -0
  25. package/rules/security/S049_short_validity_tokens/config.json +124 -0
  26. package/rules/security/S049_short_validity_tokens/regex-based-analyzer.js +295 -0
  27. package/rules/security/S049_short_validity_tokens/symbol-based-analyzer.js +389 -0
  28. package/rules/security/S051_password_length_policy/analyzer.js +410 -0
  29. package/rules/security/S051_password_length_policy/config.json +83 -0
  30. package/rules/security/S052_weak_otp_entropy/analyzer.js +403 -0
  31. package/rules/security/S052_weak_otp_entropy/config.json +57 -0
  32. package/rules/security/S054_no_default_accounts/README.md +129 -0
  33. package/rules/security/S054_no_default_accounts/analyzer.js +792 -0
  34. package/rules/security/S054_no_default_accounts/config.json +101 -0
  35. package/rules/security/S056_log_injection_protection/analyzer.js +242 -0
  36. package/rules/security/S056_log_injection_protection/config.json +148 -0
  37. package/rules/security/S056_log_injection_protection/regex-based-analyzer.js +120 -0
  38. package/rules/security/S056_log_injection_protection/symbol-based-analyzer.js +287 -0
@@ -0,0 +1,403 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * S052: OTP must have ≥20-bit entropy (≥6 digits) and use CSPRNG
6
+ *
7
+ * Detects:
8
+ * 1. Weak RNG APIs (Math.random, Random()) in OTP context
9
+ * 2. Short OTP codes (4 digits)
10
+ * 3. Non-CSPRNG usage in OTP generation
11
+ * 4. Policy violations (logging OTP, no TTL, etc.)
12
+ *
13
+ * Uses hybrid approach: AST for context-aware detection + regex for patterns
14
+ */
15
+ class S052WeakOtpEntropyAnalyzer {
16
+ constructor(config = null) {
17
+ this.ruleId = 'S052';
18
+ this.loadConfig(config);
19
+
20
+ // Compile regex patterns for performance
21
+ this.compiledPatterns = this.compilePatterns();
22
+ this.verbose = false;
23
+ }
24
+
25
+ loadConfig(config) {
26
+ try {
27
+ if (config && config.options) {
28
+ this.config = config;
29
+ this.otpIdentifiers = config.options.otpIdentifiers || [];
30
+ this.bannedRngApis = config.options.bannedRngApis || {};
31
+ this.allowedRngApis = config.options.allowedRngApis || {};
32
+ this.lengthChecks = config.options.lengthChecks || {};
33
+ this.policy = config.options.policy || {};
34
+ this.detectionHeuristics = config.options.detectionHeuristics || {};
35
+ this.allowlist = config.options.allowlist || { paths: [] };
36
+ this.thresholds = config.options.thresholds || {};
37
+ } else {
38
+ // Load from config file
39
+ const configPath = path.join(__dirname, 'config.json');
40
+ const configData = JSON.parse(fs.readFileSync(configPath, 'utf8'));
41
+ this.loadConfig(configData);
42
+ }
43
+ } catch (error) {
44
+ console.warn(`[S052] Failed to load config: ${error.message}`);
45
+ this.initializeDefaultConfig();
46
+ }
47
+ }
48
+
49
+ initializeDefaultConfig() {
50
+ this.otpIdentifiers = ['otp', 'oneTime', 'passcode', 'verificationCode', 'pin'];
51
+ this.bannedRngApis = {
52
+ typescript: ['Math\\.random\\s*\\('],
53
+ javascript: ['Math\\.random\\s*\\(']
54
+ };
55
+ this.allowedRngApis = {
56
+ node: ['crypto\\.randomInt\\s*\\(', 'crypto\\.randomBytes\\s*\\(']
57
+ };
58
+ this.lengthChecks = {
59
+ numericMinDigits: 6,
60
+ regexBadNumeric4: '\\\\b\\\\d{4}\\\\b'
61
+ };
62
+ this.policy = {
63
+ requireCsprng: true,
64
+ forbidNonCryptoRng: true,
65
+ forbidFourDigitOtp: true
66
+ };
67
+ this.detectionHeuristics = {
68
+ variableNameMatchBoost: true,
69
+ builderFunctions: ['generateOtp', 'issueOtp', 'createCode']
70
+ };
71
+ this.allowlist = { paths: ['test/', 'tests/', '__tests__/'] };
72
+ this.thresholds = { maxBannedRngUsages: 0, maxShortOtpPatterns: 0 };
73
+ }
74
+
75
+ compilePatterns() {
76
+ const patterns = {
77
+ bannedRng: {},
78
+ allowedRng: {},
79
+ otpContext: new RegExp(`\\b(${this.otpIdentifiers.join('|')})\\b`, 'gi'),
80
+ fourDigitOtp: /\b\d{4}\b/g,
81
+ shortAlphanumeric: /\b[a-zA-Z0-9]{1,5}\b/g,
82
+ builderFunctions: new RegExp(`\\b(${this.detectionHeuristics.builderFunctions?.join('|') || 'generateOtp'})\\s*\\(`, 'gi')
83
+ };
84
+
85
+ // Compile banned RNG patterns by language
86
+ for (const [lang, regexes] of Object.entries(this.bannedRngApis)) {
87
+ patterns.bannedRng[lang] = regexes.map(regex => new RegExp(regex, 'gi'));
88
+ }
89
+
90
+ // Compile allowed RNG patterns by language
91
+ for (const [lang, regexes] of Object.entries(this.allowedRngApis)) {
92
+ patterns.allowedRng[lang] = regexes.map(regex => new RegExp(regex, 'gi'));
93
+ }
94
+
95
+ return patterns;
96
+ }
97
+
98
+ analyze(files, language, options = {}) {
99
+ this.verbose = options.verbose || false;
100
+ const violations = [];
101
+
102
+ if (this.verbose) {
103
+ console.log(`[DEBUG] 🎯 S052 ANALYZE: Starting OTP entropy analysis`);
104
+ }
105
+
106
+ if (!Array.isArray(files)) {
107
+ files = [files];
108
+ }
109
+
110
+ for (const filePath of files) {
111
+ if (this.verbose) {
112
+ console.log(`[DEBUG] 🎯 S052: Analyzing ${filePath.split('/').pop()}`);
113
+ }
114
+
115
+ try {
116
+ const content = fs.readFileSync(filePath, 'utf8');
117
+ const fileExtension = path.extname(filePath);
118
+ const fileName = path.basename(filePath);
119
+ const fileViolations = this.analyzeFile(filePath, content, fileExtension, fileName);
120
+ violations.push(...fileViolations);
121
+ } catch (error) {
122
+ console.warn(`[S052] Error analyzing ${filePath}: ${error.message}`);
123
+ }
124
+ }
125
+
126
+ if (this.verbose) {
127
+ console.log(`[DEBUG] 🎯 S052: Found ${violations.length} OTP entropy violations`);
128
+ }
129
+
130
+ return violations;
131
+ }
132
+
133
+ // Alias methods for different engines
134
+ run(filePath, content, options = {}) {
135
+ this.verbose = options.verbose || false;
136
+ const fileExtension = path.extname(filePath);
137
+ const fileName = path.basename(filePath);
138
+ return this.analyzeFile(filePath, content, fileExtension, fileName);
139
+ }
140
+
141
+ runAnalysis(filePath, content, options = {}) {
142
+ return this.run(filePath, content, options);
143
+ }
144
+
145
+ runEnhancedAnalysis(filePath, content, language, options = {}) {
146
+ return this.run(filePath, content, options);
147
+ }
148
+
149
+ analyzeFile(filePath, content, fileExtension, fileName) {
150
+ const language = this.detectLanguage(fileExtension, fileName);
151
+ if (!language) {
152
+ return [];
153
+ }
154
+
155
+ // Check if file is exempted
156
+ const isExempted = this.isExemptedFile(filePath);
157
+ if (isExempted && this.verbose) {
158
+ console.log(`[DEBUG] 🔍 S052: Analyzing exempted file: ${fileName}`);
159
+ }
160
+
161
+ return this.analyzeWithHybridApproach(filePath, content, language, isExempted);
162
+ }
163
+
164
+ detectLanguage(fileExtension, fileName) {
165
+ const extensions = {
166
+ '.ts': 'typescript',
167
+ '.tsx': 'typescript',
168
+ '.js': 'javascript',
169
+ '.jsx': 'javascript',
170
+ '.mjs': 'javascript',
171
+ '.java': 'java',
172
+ '.kt': 'kotlin',
173
+ '.dart': 'dart'
174
+ };
175
+
176
+ return extensions[fileExtension] || null;
177
+ }
178
+
179
+ isExemptedFile(filePath) {
180
+ const allowedPaths = this.allowlist.paths || [];
181
+ return allowedPaths.some(path => filePath.includes(path));
182
+ }
183
+
184
+ analyzeWithHybridApproach(filePath, content, language, isExempted) {
185
+ const violations = [];
186
+ const lines = content.split('\n');
187
+
188
+ if (this.verbose) {
189
+ console.log(`[DEBUG] 🎯 S052: Starting hybrid analysis (AST + regex) of ${lines.length} lines`);
190
+ }
191
+
192
+ // Step 1: Quick regex scan for obvious patterns
193
+ const regexViolations = this.scanWithRegex(content, lines, filePath, language, isExempted);
194
+ violations.push(...regexViolations);
195
+
196
+ // Step 2: Context-aware AST analysis (for TypeScript/JavaScript)
197
+ if (['typescript', 'javascript'].includes(language)) {
198
+ const astViolations = this.scanWithAst(content, lines, filePath, isExempted);
199
+ violations.push(...astViolations);
200
+ }
201
+
202
+ if (this.verbose) {
203
+ console.log(`[DEBUG] 🎯 S052: Found ${violations.length} total violations (regex + AST)`);
204
+ }
205
+
206
+ return violations;
207
+ }
208
+
209
+ scanWithRegex(content, lines, filePath, language, isExempted) {
210
+ const violations = [];
211
+
212
+ // 1. Check for banned RNG APIs
213
+ if (this.policy.forbidNonCryptoRng && this.compiledPatterns.bannedRng[language]) {
214
+ for (const pattern of this.compiledPatterns.bannedRng[language]) {
215
+ const matches = [...content.matchAll(pattern)];
216
+ for (const match of matches) {
217
+ const lineNumber = this.getLineNumber(content, match.index);
218
+
219
+ // Check if in OTP context
220
+ const lineContent = lines[lineNumber - 1];
221
+ const isOtpContext = this.isInOtpContext(lineContent, lines, lineNumber);
222
+
223
+ if (isOtpContext) {
224
+ violations.push({
225
+ ruleId: this.ruleId,
226
+ message: `Weak RNG "${match[0]}" detected in OTP context - use CSPRNG instead`,
227
+ severity: 'error',
228
+ line: lineNumber,
229
+ column: this.getColumnNumber(content, match.index),
230
+ filePath: filePath,
231
+ context: {
232
+ violationType: 'weak_rng_in_otp',
233
+ evidence: lineContent.trim(),
234
+ recommendation: 'Use crypto.randomInt() or crypto.randomBytes() for OTP generation'
235
+ }
236
+ });
237
+ }
238
+ }
239
+ }
240
+ }
241
+
242
+ // 2. Check for 4-digit OTP patterns
243
+ if (this.policy.forbidFourDigitOtp) {
244
+ const fourDigitMatches = [...content.matchAll(this.compiledPatterns.fourDigitOtp)];
245
+ for (const match of fourDigitMatches) {
246
+ const lineNumber = this.getLineNumber(content, match.index);
247
+ const lineContent = lines[lineNumber - 1];
248
+
249
+ // Check if this looks like an OTP (not just any 4 digits)
250
+ const isOtpContext = this.isInOtpContext(lineContent, lines, lineNumber);
251
+ const looks4DigitOtp = /\b(otp|code|pin).*\d{4}|\d{4}.*(otp|code|pin)/i.test(lineContent);
252
+
253
+ if (isOtpContext && looks4DigitOtp) {
254
+ violations.push({
255
+ ruleId: this.ruleId,
256
+ message: `4-digit OTP detected - insufficient entropy, use ≥6 digits`,
257
+ severity: 'error',
258
+ line: lineNumber,
259
+ column: this.getColumnNumber(content, match.index),
260
+ filePath: filePath,
261
+ context: {
262
+ violationType: 'insufficient_otp_entropy',
263
+ evidence: lineContent.trim(),
264
+ recommendation: 'Use at least 6 digits for OTP to achieve ≥20-bit entropy'
265
+ }
266
+ });
267
+ }
268
+ }
269
+ }
270
+
271
+ return violations;
272
+ }
273
+
274
+ scanWithAst(content, lines, filePath, isExempted) {
275
+ // AST analysis for TypeScript/JavaScript
276
+ // This would be more sophisticated context-aware detection
277
+ const violations = [];
278
+
279
+ // For now, implement simple variable/function name based detection
280
+ // TODO: Integrate with ts-morph for full AST analysis if needed
281
+
282
+ for (let i = 0; i < lines.length; i++) {
283
+ const line = lines[i];
284
+ const lineNumber = i + 1;
285
+
286
+ // Check for OTP generation functions with weak patterns
287
+ const otpFunctionMatch = line.match(/function\s+(\w*(?:otp|code|pin)\w*)\s*\(/i);
288
+ if (otpFunctionMatch) {
289
+ // Look ahead for Math.random usage in function body
290
+ const functionBody = this.extractFunctionBody(lines, i);
291
+ if (functionBody && /Math\.random\s*\(/.test(functionBody)) {
292
+ violations.push({
293
+ ruleId: this.ruleId,
294
+ message: `OTP generation function "${otpFunctionMatch[1]}" uses weak Math.random()`,
295
+ severity: 'error',
296
+ line: lineNumber,
297
+ column: otpFunctionMatch.index + 1,
298
+ filePath: filePath,
299
+ context: {
300
+ violationType: 'weak_rng_in_otp_function',
301
+ evidence: line.trim(),
302
+ recommendation: 'Replace Math.random() with crypto.randomInt() or crypto.randomBytes()'
303
+ }
304
+ });
305
+ }
306
+ }
307
+ }
308
+
309
+ return violations;
310
+ }
311
+
312
+ isInOtpContext(lineContent, lines, lineNumber) {
313
+ // Skip if it's clearly not OTP context (e.g., generateSessionId, generateRequestId)
314
+ if (/generate(Session|Request|Unique|Random)Id/i.test(lineContent)) {
315
+ return false;
316
+ }
317
+
318
+ // Skip if it's in a comment
319
+ if (/^\s*\/\//.test(lineContent.trim()) || /\/\*.*\*\//.test(lineContent)) {
320
+ return false;
321
+ }
322
+
323
+ // Skip if it's dummy/test data (static values in objects)
324
+ if (/:\s*['"`]\d+['"`]\s*[,}]/.test(lineContent)) {
325
+ return false; // Static hardcoded values like pin: '1000'
326
+ }
327
+
328
+ // Skip if it's object property assignment with literal values
329
+ if (/\w+\s*:\s*['"`]\w*['"`]/.test(lineContent)) {
330
+ return false;
331
+ }
332
+
333
+ // Check current line for direct OTP context (must be variable assignment or function call)
334
+ const directOtpMatch = /\b(otp|oneTime|one_time|passcode|verificationCode|verifyCode|confirmCode|pin|totp|hotp|resetCode|activationCode)\s*[:=]/.test(lineContent);
335
+ if (directOtpMatch && !/:\s*['"`]/.test(lineContent)) {
336
+ // Only if it's NOT a static value assignment
337
+ return true;
338
+ }
339
+
340
+ // Check for OTP function definition
341
+ const otpFunctionMatch = /function\s+\w*(otp|passcode|pin|code|verify|confirm|reset|activation)\w*/i.test(lineContent);
342
+ if (otpFunctionMatch) {
343
+ return true;
344
+ }
345
+
346
+ // Check surrounding lines for context (more restrictive)
347
+ const contextRange = 2; // Reduced from 3
348
+ const start = Math.max(0, lineNumber - contextRange - 1);
349
+ const end = Math.min(lines.length, lineNumber + contextRange);
350
+
351
+ for (let i = start; i < end; i++) {
352
+ const contextLine = lines[i];
353
+ // Look for actual OTP assignment or return statements (not static values)
354
+ if (/\b(otp|passcode|pin)\s*[:=]|\breturn\s+\w*(otp|passcode|pin)/i.test(contextLine) &&
355
+ !/:\s*['"`]\w*['"`]/.test(contextLine)) {
356
+ return true;
357
+ }
358
+ }
359
+
360
+ return false;
361
+ }
362
+
363
+ extractFunctionBody(lines, startLine) {
364
+ // Simple function body extraction (could be improved with proper parsing)
365
+ let braceCount = 0;
366
+ let inFunction = false;
367
+ let body = '';
368
+
369
+ for (let i = startLine; i < Math.min(lines.length, startLine + 20); i++) {
370
+ const line = lines[i];
371
+
372
+ for (const char of line) {
373
+ if (char === '{') {
374
+ braceCount++;
375
+ inFunction = true;
376
+ } else if (char === '}') {
377
+ braceCount--;
378
+ if (braceCount === 0 && inFunction) {
379
+ return body;
380
+ }
381
+ }
382
+ }
383
+
384
+ if (inFunction) {
385
+ body += line + '\n';
386
+ }
387
+ }
388
+
389
+ return body;
390
+ }
391
+
392
+ getLineNumber(content, index) {
393
+ return content.substring(0, index).split('\n').length;
394
+ }
395
+
396
+ getColumnNumber(content, index) {
397
+ const beforeIndex = content.substring(0, index);
398
+ const lastNewlineIndex = beforeIndex.lastIndexOf('\n');
399
+ return index - lastNewlineIndex;
400
+ }
401
+ }
402
+
403
+ module.exports = S052WeakOtpEntropyAnalyzer;
@@ -0,0 +1,57 @@
1
+ {
2
+ "ruleId": "S052",
3
+ "name": "OTP must have ≥20-bit entropy (≥6 digits) and use CSPRNG",
4
+ "description": "Prevent guessable OTP by enforcing CSPRNG and minimal entropy. Ban non-crypto RNG and too-short codes.",
5
+ "category": "security",
6
+ "severity": "error",
7
+ "options": {
8
+ "otpIdentifiers": [
9
+ "otp","oneTime","one_time","passcode","verificationCode","verifyCode",
10
+ "confirmCode","pin","totp","hotp","resetCode","activationCode"
11
+ ],
12
+ "bannedRngApis": {
13
+ "typescript": ["Math\\.random\\s*\\("],
14
+ "javascript": ["Math\\.random\\s*\\("],
15
+ "node": ["crypto\\.pseudoRandomBytes\\s*\\("],
16
+ "java": ["new\\s+Random\\s*\\(","ThreadLocalRandom\\.current\\s*\\("],
17
+ "kotlin": ["kotlin\\.random\\.Random(\\.Default)?"],
18
+ "dart": ["new\\s+Random\\s*\\(","Random\\s*\\("]
19
+ },
20
+ "allowedRngApis": {
21
+ "node": ["crypto\\.randomInt\\s*\\(","crypto\\.randomBytes\\s*\\("],
22
+ "java": ["new\\s+SecureRandom\\s*\\("],
23
+ "kotlin": ["java\\.security\\.SecureRandom"],
24
+ "dart": ["Random\\.secure\\s*\\("]
25
+ },
26
+ "lengthChecks": {
27
+ "numericMinDigits": 6,
28
+ "alphanumericMinLength": 6,
29
+ "regexBadNumeric4": "\\\\b\\\\d{4}\\\\b"
30
+ },
31
+ "totpHotpHints": {
32
+ "requireStandardLib": true,
33
+ "minSecretBits": 128,
34
+ "maxTimeStepSeconds": 30,
35
+ "maxWindow": 1
36
+ },
37
+ "policy": {
38
+ "requireCsprng": true,
39
+ "forbidNonCryptoRng": true,
40
+ "forbidFourDigitOtp": true,
41
+ "requireNoLoggingOfOtp": true,
42
+ "requireSingleUseAndTtl": true
43
+ },
44
+ "detectionHeuristics": {
45
+ "variableNameMatchBoost": true,
46
+ "builderFunctions": ["generateOtp","issueOtp","createCode","sendOtp","totp","hotp"]
47
+ },
48
+ "allowlist": {
49
+ "paths": ["test/","tests/","__tests__/","fixtures/","mocks/"],
50
+ "notes": "Trong test vẫn cấm 4-digit OTP nếu test chạy e2e với hệ thật."
51
+ },
52
+ "thresholds": {
53
+ "maxBannedRngUsages": 0,
54
+ "maxShortOtpPatterns": 0
55
+ }
56
+ }
57
+ }
@@ -0,0 +1,129 @@
1
+ # Rule S054 - Disallow Default/Built-in Accounts
2
+
3
+ ## Mục tiêu
4
+ Ngăn chặn tấn công brute-force và đảm bảo tính truy vết, minh bạch trong kiểm toán. Tránh các tài khoản dễ đoán và dùng chung không rõ danh tính.
5
+
6
+ ## Chi tiết
7
+ - Không sử dụng tài khoản mặc định hoặc tài khoản có tên phổ biến (admin, root, sa, test, guest, ...)
8
+ - Mỗi người dùng phải có tài khoản riêng biệt với quyền hạn được giới hạn theo vai trò
9
+ - Bắt buộc đổi mật khẩu mặc định khi khởi tạo hệ thống hoặc khi user được tạo mới
10
+ - Hệ thống cần log mọi lần đăng nhập và truy cập tài nguyên theo từng user cụ thể
11
+
12
+ ## Phạm vi kiểm tra
13
+
14
+ ### 1. SQL/Seeder/Migration
15
+ - `INSERT INTO users/accounts` with blocked usernames
16
+ - `CREATE USER` statements
17
+ - `GRANT/REVOKE` permissions to default accounts
18
+
19
+ ### 2. Code/Application
20
+ - `createUser()`, `new User()`, `addUser()` with blocked usernames
21
+ - User registration/creation functions
22
+ - Authentication/authorization code
23
+
24
+ ### 3. Infrastructure as Code (IaC)
25
+ - **Terraform**: `username = "admin"`, `user = "root"`
26
+ - **Helm/Values**: `adminUser:`, `defaultPass:`
27
+ - **Docker/Compose**: `POSTGRES_USER=postgres`, `ENV USER=admin`
28
+ - **Kubernetes**: `serviceAccount: default`, `user: admin`
29
+
30
+ ### 4. Configuration Files
31
+ - Database configs: `db.username=admin`
32
+ - Application configs: `auth.user=root`
33
+ - Environment files: `.env` with default credentials
34
+
35
+ ### 5. Documentation
36
+ - README/docs with exposed default credentials
37
+ - Login examples with real default accounts
38
+
39
+ ## Blocked Usernames
40
+ ```
41
+ admin, root, sa, test, guest, operator, super, superuser, sys,
42
+ postgres, mysql, mssql, oracle, elastic, kibana, grafana,
43
+ administrator, demo, example, default, public, anonymous,
44
+ user, password, service, support, backup, monitor
45
+ ```
46
+
47
+ ## Password Smells
48
+ ```
49
+ password, 123456, admin, Admin@123, Password1, changeme,
50
+ default, qwerty, letmein, welcome, secret, pass123,
51
+ root, toor, administrator, guest
52
+ ```
53
+
54
+ ## Exemptions
55
+ - Test directories: `test/`, `tests/`, `__tests__/`, `e2e/`, `spec/`
56
+ - Mock/fixture data
57
+ - Documentation examples (with warnings)
58
+
59
+ ## Examples
60
+
61
+ ### ❌ Violations
62
+
63
+ **SQL:**
64
+ ```sql
65
+ INSERT INTO users (username, password) VALUES ('admin', 'password123');
66
+ CREATE USER 'root'@'localhost' IDENTIFIED BY 'admin';
67
+ ```
68
+
69
+ **Code:**
70
+ ```javascript
71
+ createUser({ username: 'admin', password: 'default' });
72
+ const user = new User('test', 'password');
73
+ ```
74
+
75
+ **Terraform:**
76
+ ```hcl
77
+ username = "admin"
78
+ admin_username = "root"
79
+ ```
80
+
81
+ **Docker:**
82
+ ```dockerfile
83
+ ENV POSTGRES_USER=postgres
84
+ ENV MYSQL_USER=root
85
+ ```
86
+
87
+ **Config:**
88
+ ```properties
89
+ spring.datasource.username=admin
90
+ db.username=root
91
+ ```
92
+
93
+ ### ✅ Correct Usage
94
+
95
+ **SQL:**
96
+ ```sql
97
+ INSERT INTO users (username, password) VALUES ('john.doe', '${HASHED_PASSWORD}');
98
+ CREATE USER '${DB_USERNAME}'@'localhost' IDENTIFIED BY '${DB_PASSWORD}';
99
+ ```
100
+
101
+ **Code:**
102
+ ```javascript
103
+ createUser({ username: userInput.username, password: hashedPassword });
104
+ const user = new User(registrationForm.username, securePassword);
105
+ ```
106
+
107
+ **Terraform:**
108
+ ```hcl
109
+ username = var.database_username
110
+ admin_username = var.admin_user
111
+ ```
112
+
113
+ **Docker:**
114
+ ```dockerfile
115
+ ENV POSTGRES_USER=${DB_USERNAME}
116
+ ENV MYSQL_USER=${DATABASE_USER}
117
+ ```
118
+
119
+ ## Policy Requirements
120
+ - ✅ `requirePerUserAccount`: true
121
+ - ✅ `requireInitialPasswordChange`: true
122
+ - ✅ `forbidWellKnownServiceAccountsInAppDB`: true
123
+ - ✅ `allowOnlyInEphemeralTests`: true
124
+ - ✅ `mustDisableBuiltInsOnInfra`: true
125
+
126
+ ## Thresholds
127
+ - Production: `maxFindings = 0`
128
+ - Test paths: `maxInAllowedPaths = 2`
129
+ - Password smells: `maxPasswordSmells = 0`