@sun-asterisk/sunlint 1.3.35 → 1.3.37
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/cli.js +33 -0
- package/config/rules/enhanced-rules-registry.json +354 -98
- package/config/rules/rules-registry-generated.json +197 -171
- package/core/architecture-integration.js +115 -17
- package/core/cli-action-handler.js +110 -26
- package/core/cli-program.js +14 -3
- package/core/github-annotate-service.js +62 -0
- package/core/impact-integration.js +309 -176
- package/core/init-command.js +227 -0
- package/core/output-service.js +53 -5
- package/core/summary-report-service.js +46 -0
- package/core/unified-rule-registry.js +2 -1
- package/engines/eslint-engine.js +6 -0
- package/engines/impact/core/detectors/database-detector.js +1 -1
- package/engines/impact/core/detectors/endpoint-detector.js +1 -1
- package/engines/impact/core/report-generator.js +235 -73
- package/origin-rules/security-en.md +470 -282
- package/package.json +1 -1
- package/rules/security/S001_backend_auth_communications/dart/analyzer.js +44 -0
- package/rules/security/S001_backend_auth_communications/index.js +87 -0
- package/rules/security/S001_backend_auth_communications/typescript/analyzer.js +164 -0
- package/rules/security/S002_os_command_injection/dart/analyzer.js +44 -0
- package/rules/security/S002_os_command_injection/index.js +87 -0
- package/rules/security/S002_os_command_injection/typescript/analyzer.js +194 -0
- package/rules/security/S008_svg_content_validation/dart/analyzer.js +44 -0
- package/rules/security/S008_svg_content_validation/index.js +87 -0
- package/rules/security/S008_svg_content_validation/typescript/analyzer.js +216 -0
- package/rules/security/S018_no_sensitive_browser_storage/dart/analyzer.js +44 -0
- package/rules/security/S018_no_sensitive_browser_storage/index.js +86 -0
- package/rules/security/S018_no_sensitive_browser_storage/typescript/analyzer.js +193 -0
- package/rules/security/S021_referrer_policy/dart/analyzer.js +44 -0
- package/rules/security/S021_referrer_policy/index.js +86 -0
- package/rules/security/S021_referrer_policy/typescript/analyzer.js +183 -0
- package/rules/security/S023_no_json_injection/config.json +133 -44
- package/rules/security/S023_no_json_injection/dart/analyzer.js +7 -6
- package/rules/security/S023_no_json_injection/typescript/analyzer.js +402 -126
- package/rules/security/S023_no_json_injection/typescript/ast-analyzer.js +571 -154
- package/rules/security/S026_tls_all_connections/config.json +30 -0
- package/rules/security/S026_tls_all_connections/typescript/analyzer.js +339 -0
- package/rules/security/S027_mtls_certificate_validation/config.json +30 -0
- package/rules/security/S027_mtls_certificate_validation/typescript/analyzer.js +225 -0
- package/rules/security/S035_separate_app_hostnames/config.json +28 -0
- package/rules/security/S035_separate_app_hostnames/typescript/analyzer.js +186 -0
- package/rules/security/S036_lfi_rfi_protection/config.json +2 -2
- package/rules/security/S039_tls_certificate_validation/config.json +29 -0
- package/rules/security/S039_tls_certificate_validation/typescript/analyzer.js +229 -0
- package/rules/security/S046_jwt_algorithm_allowlist/config.json +28 -0
- package/rules/security/S046_jwt_algorithm_allowlist/dart/analyzer.js +44 -0
- package/rules/security/S046_jwt_algorithm_allowlist/index.js +87 -0
- package/rules/security/S046_jwt_algorithm_allowlist/typescript/analyzer.js +235 -0
- package/rules/security/S047_oauth_pkce_protection/config.json +31 -0
- package/rules/security/S047_oauth_pkce_protection/dart/analyzer.js +44 -0
- package/rules/security/S047_oauth_pkce_protection/index.js +86 -0
- package/rules/security/S047_oauth_pkce_protection/typescript/analyzer.js +78 -0
- package/rules/security/S048_oauth_redirect_uri_validation/config.json +30 -0
- package/rules/security/S048_oauth_redirect_uri_validation/typescript/analyzer.js +278 -0
- package/rules/security/S049_short_validity_tokens/typescript/config.json +10 -3
- package/rules/security/S050_reference_tokens_entropy/config.json +28 -0
- package/rules/security/S050_reference_tokens_entropy/dart/analyzer.js +45 -0
- package/rules/security/S050_reference_tokens_entropy/index.js +86 -0
- package/rules/security/S050_reference_tokens_entropy/typescript/analyzer.js +74 -0
- package/rules/security/S053_generic_error_messages/config.json +28 -0
- package/rules/security/S053_generic_error_messages/dart/analyzer.js +45 -0
- package/rules/security/S053_generic_error_messages/index.js +86 -0
- package/rules/security/S053_generic_error_messages/typescript/analyzer.js +80 -0
- package/rules/security/S055_content_type_validation/typescript/symbol-based-analyzer.js +64 -2
- package/rules/security/S059_disable_debug_mode/config.json +28 -0
- package/rules/security/S059_disable_debug_mode/dart/analyzer.js +45 -0
- package/rules/security/S059_disable_debug_mode/index.js +86 -0
- package/rules/security/S059_disable_debug_mode/typescript/analyzer.js +85 -0
- package/rules/security/S060_password_minimum_length/config.json +28 -0
- package/rules/security/S060_password_minimum_length/dart/analyzer.js +45 -0
- package/rules/security/S060_password_minimum_length/index.js +86 -0
- package/rules/security/S060_password_minimum_length/typescript/analyzer.js +78 -0
- package/rules/security/S026_json_schema_validation/config.json +0 -27
- package/rules/security/S026_json_schema_validation/typescript/analyzer.js +0 -251
- package/rules/security/S027_no_hardcoded_secrets/config.json +0 -29
- package/rules/security/S027_no_hardcoded_secrets/typescript/analyzer.js +0 -309
- package/rules/security/S027_no_hardcoded_secrets/typescript/categories.json +0 -153
- package/rules/security/S035_path_session_cookies/config.json +0 -99
- package/rules/security/S035_path_session_cookies/typescript/analyzer.js +0 -316
- package/rules/security/S035_path_session_cookies/typescript/regex-based-analyzer.js +0 -724
- package/rules/security/S035_path_session_cookies/typescript/symbol-based-analyzer.js +0 -373
- package/rules/security/S039_no_session_tokens_in_url/config.json +0 -92
- package/rules/security/S039_no_session_tokens_in_url/typescript/analyzer.js +0 -262
- package/rules/security/S039_no_session_tokens_in_url/typescript/regex-based-analyzer.js +0 -337
- package/rules/security/S039_no_session_tokens_in_url/typescript/symbol-based-analyzer.js +0 -443
- package/rules/security/S048_no_current_password_in_reset/config.json +0 -48
- package/rules/security/S048_no_current_password_in_reset/typescript/analyzer.js +0 -366
- /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/dart/analyzer.js +0 -0
- /package/rules/security/{S026_json_schema_validation → S026_tls_all_connections}/index.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/index.js +0 -0
- /package/rules/security/{S027_no_hardcoded_secrets → S027_mtls_certificate_validation}/typescript/categorized-analyzer.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/dart/analyzer.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/index.js +0 -0
- /package/rules/security/{S035_path_session_cookies → S035_separate_app_hostnames}/typescript/README.md +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/index.js +0 -0
- /package/rules/security/{S039_no_session_tokens_in_url → S039_tls_certificate_validation}/typescript/README.md +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/dart/analyzer.js +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/index.js +0 -0
- /package/rules/security/{S048_no_current_password_in_reset → S048_oauth_redirect_uri_validation}/typescript/README.md +0 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S053 Rule Router - Generic Error Messages
|
|
3
|
+
*
|
|
4
|
+
* Routes analysis to the appropriate language-specific analyzer.
|
|
5
|
+
* Supports: TypeScript, JavaScript, Dart
|
|
6
|
+
*
|
|
7
|
+
* Rule: Return generic error messages, hide internal details from users
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
class S053Router {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.analyzers = new Map();
|
|
15
|
+
this.ruleId = 'S053';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getAnalyzer(language) {
|
|
19
|
+
const normalizedLang = this.normalizeLanguage(language);
|
|
20
|
+
|
|
21
|
+
if (!this.analyzers.has(normalizedLang)) {
|
|
22
|
+
try {
|
|
23
|
+
const analyzerPath = path.join(__dirname, normalizedLang, 'analyzer.js');
|
|
24
|
+
const AnalyzerClass = require(analyzerPath);
|
|
25
|
+
this.analyzers.set(normalizedLang, new AnalyzerClass());
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this.analyzers.get(normalizedLang);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
normalizeLanguage(language) {
|
|
35
|
+
if (typeof language !== 'string') {
|
|
36
|
+
return 'typescript';
|
|
37
|
+
}
|
|
38
|
+
const languageMap = {
|
|
39
|
+
'typescript': 'typescript',
|
|
40
|
+
'javascript': 'typescript',
|
|
41
|
+
'ts': 'typescript',
|
|
42
|
+
'js': 'typescript',
|
|
43
|
+
'dart': 'dart'
|
|
44
|
+
};
|
|
45
|
+
return languageMap[language.toLowerCase()] || language.toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
supportsLanguage(language) {
|
|
49
|
+
if (typeof language !== 'string') return false;
|
|
50
|
+
const supported = ['typescript', 'javascript', 'ts', 'js', 'dart'];
|
|
51
|
+
return supported.includes(language.toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getSupportedLanguages() {
|
|
55
|
+
return ['typescript', 'javascript', 'dart'];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async analyze(files, language, options = {}) {
|
|
59
|
+
const analyzer = this.getAnalyzer(language);
|
|
60
|
+
if (!analyzer) return [];
|
|
61
|
+
if (typeof analyzer.analyze === 'function') {
|
|
62
|
+
return analyzer.analyze(files, language, options);
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async initialize(semanticEngineOrLanguage = null, semanticEngine = null) {
|
|
68
|
+
let engine = semanticEngine;
|
|
69
|
+
let lang = null;
|
|
70
|
+
|
|
71
|
+
if (typeof semanticEngineOrLanguage === 'string') {
|
|
72
|
+
lang = semanticEngineOrLanguage;
|
|
73
|
+
} else if (semanticEngineOrLanguage && typeof semanticEngineOrLanguage === 'object') {
|
|
74
|
+
engine = semanticEngineOrLanguage;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (lang) {
|
|
78
|
+
const analyzer = this.getAnalyzer(lang);
|
|
79
|
+
if (analyzer && typeof analyzer.initialize === 'function') {
|
|
80
|
+
await analyzer.initialize(engine);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = new S053Router();
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S053 – Return generic error messages, hide internal details
|
|
3
|
+
*
|
|
4
|
+
* Detects exposure of internal details in error responses:
|
|
5
|
+
* - Stack traces in responses
|
|
6
|
+
* - SQL errors/queries exposed
|
|
7
|
+
* - File paths in errors
|
|
8
|
+
* - Internal error messages sent to client
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const fs = require('fs');
|
|
12
|
+
|
|
13
|
+
class S053Analyzer {
|
|
14
|
+
constructor() {
|
|
15
|
+
this.ruleId = 'S053';
|
|
16
|
+
this.ruleName = 'Return generic error messages, hide internal details';
|
|
17
|
+
this.description = 'Do not expose internal error details to users';
|
|
18
|
+
|
|
19
|
+
this.warningPatterns = [
|
|
20
|
+
{
|
|
21
|
+
pattern: /res\.(?:json|send)\s*\([^)]*error\.stack/gi,
|
|
22
|
+
message: 'Stack trace exposed in response - return generic error message',
|
|
23
|
+
type: 'stack_trace_exposed'
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
pattern: /res\.(?:json|send)\s*\([^)]*(?:sql|query|database)/gi,
|
|
27
|
+
message: 'SQL/database details may be exposed in response',
|
|
28
|
+
type: 'sql_exposed'
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
pattern: /res\.(?:json|send)\s*\([^)]*__dirname|__filename/gi,
|
|
32
|
+
message: 'File paths exposed in response',
|
|
33
|
+
type: 'path_exposed'
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
pattern: /catch\s*\([^)]*\)\s*\{[^}]*res\.(?:json|send)\s*\([^)]*(?:err|error)(?:\.|\.message|\))/gi,
|
|
37
|
+
message: 'Raw error passed to response - use generic message',
|
|
38
|
+
type: 'raw_error_exposed'
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
this.skipPatterns = [
|
|
43
|
+
/\.test\./i, /\.spec\./i, /test\//i, /__tests__\//i, /node_modules/i
|
|
44
|
+
];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
shouldSkipFile(filePath) {
|
|
48
|
+
return this.skipPatterns.some(p => p.test(filePath));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async analyze(files, language, options = {}) {
|
|
52
|
+
const violations = [];
|
|
53
|
+
for (const filePath of files) {
|
|
54
|
+
if (this.shouldSkipFile(filePath)) continue;
|
|
55
|
+
try {
|
|
56
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
57
|
+
violations.push(...this.analyzeFile(content, filePath));
|
|
58
|
+
} catch (e) { /* skip */ }
|
|
59
|
+
}
|
|
60
|
+
return violations;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
analyzeFile(content, filePath) {
|
|
64
|
+
const violations = [];
|
|
65
|
+
for (const { pattern, message, type } of this.warningPatterns) {
|
|
66
|
+
pattern.lastIndex = 0;
|
|
67
|
+
let match;
|
|
68
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
69
|
+
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
70
|
+
violations.push({
|
|
71
|
+
file: filePath, line: lineNum, column: 1,
|
|
72
|
+
message, severity: 'warning', ruleId: this.ruleId, type
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return violations;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = S053Analyzer;
|
|
@@ -26,6 +26,18 @@ class S055SymbolBasedAnalyzer {
|
|
|
26
26
|
/vendor\//, /mocks\//, /\.mock\./,
|
|
27
27
|
/config\//, /configs\//, /\.config\./,
|
|
28
28
|
/public\//, /static\//, /assets\//,
|
|
29
|
+
// Skip DTO files - they contain validation logic, not REST endpoints
|
|
30
|
+
/\.dto\./, /dto\//, /dtos\//,
|
|
31
|
+
// Skip entity files
|
|
32
|
+
/\.entity\./, /entities\//,
|
|
33
|
+
// Skip model/schema files
|
|
34
|
+
/\.model\./, /models\//, /\.schema\./, /schemas\//,
|
|
35
|
+
// Skip utility/helper files - contain file filters, middleware functions, not REST endpoints
|
|
36
|
+
/utils\//, /util\//, /helpers\//, /helper\./,
|
|
37
|
+
// Skip middleware files
|
|
38
|
+
/middleware\//, /middlewares\//,
|
|
39
|
+
// Skip lib files
|
|
40
|
+
/\/lib\//,
|
|
29
41
|
];
|
|
30
42
|
|
|
31
43
|
// Patterns to identify REST endpoints
|
|
@@ -69,6 +81,11 @@ class S055SymbolBasedAnalyzer {
|
|
|
69
81
|
'checkContentType'
|
|
70
82
|
]
|
|
71
83
|
};
|
|
84
|
+
|
|
85
|
+
// NestJS with @Body decorator and typed DTO has built-in Content-Type validation
|
|
86
|
+
// NestJS body-parser only accepts application/json by default and rejects other Content-Types
|
|
87
|
+
// We check: 1) has @Body decorator, 2) parameter has type annotation starting with uppercase (DTO class)
|
|
88
|
+
// This covers @Body() dto: SomeDto, @Body(pipes...) dto: SomeDto, etc.
|
|
72
89
|
}
|
|
73
90
|
|
|
74
91
|
async initialize(semanticEngine = null) {
|
|
@@ -174,13 +191,27 @@ class S055SymbolBasedAnalyzer {
|
|
|
174
191
|
return;
|
|
175
192
|
}
|
|
176
193
|
|
|
194
|
+
// GOOD CASE: NestJS @Body() with typed DTO has built-in Content-Type validation
|
|
195
|
+
// NestJS body-parser only accepts application/json by default
|
|
196
|
+
const methodText = method.getText();
|
|
197
|
+
if (this.hasNestJSTypedBodyParam(method)) {
|
|
198
|
+
if (this.verbose) {
|
|
199
|
+
console.log(`✅ [S055] Method ${method.getName()} uses @Body() with typed DTO - NestJS has built-in Content-Type validation`);
|
|
200
|
+
}
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
177
204
|
// Check if POST, PUT, or PATCH (methods that accept body)
|
|
178
|
-
const
|
|
205
|
+
const hasHttpMethodDecorator = decorators.some(dec => {
|
|
179
206
|
const name = dec.getName();
|
|
180
207
|
return ['Post', 'Put', 'Patch'].includes(name);
|
|
181
208
|
});
|
|
182
209
|
|
|
183
|
-
if (
|
|
210
|
+
// Only flag if method actually accepts body via @Body() decorator
|
|
211
|
+
// POST endpoints without @Body() don't need Content-Type validation
|
|
212
|
+
const hasBodyParam = methodText.includes('@Body');
|
|
213
|
+
|
|
214
|
+
if (hasHttpMethodDecorator && hasBodyParam) {
|
|
184
215
|
this.checkContentTypeValidation(method, sourceFile, violations);
|
|
185
216
|
}
|
|
186
217
|
}
|
|
@@ -310,6 +341,37 @@ class S055SymbolBasedAnalyzer {
|
|
|
310
341
|
});
|
|
311
342
|
}
|
|
312
343
|
|
|
344
|
+
/**
|
|
345
|
+
* Check if NestJS method has @Body parameter with typed DTO
|
|
346
|
+
* NestJS body-parser only accepts application/json by default and rejects other Content-Types
|
|
347
|
+
* This handles:
|
|
348
|
+
* - @Body() dto: SomeDto
|
|
349
|
+
* - @Body(pipe1, pipe2) dto: SomeDto
|
|
350
|
+
* - @Body(\n Pipe1,\n Pipe2\n) dto: SomeDto (multiline)
|
|
351
|
+
*/
|
|
352
|
+
hasNestJSTypedBodyParam(method) {
|
|
353
|
+
const params = method.getParameters();
|
|
354
|
+
|
|
355
|
+
for (const param of params) {
|
|
356
|
+
const decorators = param.getDecorators();
|
|
357
|
+
const hasBodyDecorator = decorators.some(dec => dec.getName() === 'Body');
|
|
358
|
+
|
|
359
|
+
if (hasBodyDecorator) {
|
|
360
|
+
// Check if parameter has type annotation (typed DTO)
|
|
361
|
+
const typeNode = param.getTypeNode();
|
|
362
|
+
if (typeNode) {
|
|
363
|
+
const typeName = typeNode.getText();
|
|
364
|
+
// DTO classes typically start with uppercase letter
|
|
365
|
+
if (/^[A-Z]/.test(typeName)) {
|
|
366
|
+
return true;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return false;
|
|
373
|
+
}
|
|
374
|
+
|
|
313
375
|
checkContentTypeValidation(node, sourceFile, violations) {
|
|
314
376
|
const bodyText = node.getText();
|
|
315
377
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S059",
|
|
3
|
+
"name": "Disable debug modes in production environments",
|
|
4
|
+
"description": "Ensure debug modes, verbose logging, and development features are disabled in production. Debug modes can expose sensitive information, enable unauthorized access, and degrade performance.",
|
|
5
|
+
"category": "security",
|
|
6
|
+
"severity": "high",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"engines": ["heuristic"],
|
|
9
|
+
"enginePreference": ["heuristic"],
|
|
10
|
+
"tags": ["security", "debug", "production", "configuration"],
|
|
11
|
+
"examples": {
|
|
12
|
+
"valid": [
|
|
13
|
+
"DEBUG = process.env.NODE_ENV !== 'production';",
|
|
14
|
+
"app.use(helmet()); // Security headers",
|
|
15
|
+
"if (process.env.NODE_ENV === 'development') { enableDebug(); }"
|
|
16
|
+
],
|
|
17
|
+
"invalid": [
|
|
18
|
+
"DEBUG = true; // Hardcoded debug mode",
|
|
19
|
+
"app.use(errorHandler({ dumpExceptions: true }));",
|
|
20
|
+
"morgan('dev'); // Verbose logging in production"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"fixable": false,
|
|
24
|
+
"docs": {
|
|
25
|
+
"description": "This rule ensures debug features are disabled in production. Debug modes to disable: verbose error messages with stack traces, development server features, debug endpoints (/debug, /test), SQL query logging, request/response body logging, source maps in client-side code, hot reload/watch modes.",
|
|
26
|
+
"url": "https://owasp.org/Top10/A05_2021-Security_Misconfiguration/"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S059 Dart Analyzer - Disable Debug Mode
|
|
3
|
+
*
|
|
4
|
+
* This is a JS wrapper that delegates to DartAnalyzer binary.
|
|
5
|
+
* Actual implementation: dart_analyzer/lib/rules/security/S059_disable_debug_mode.dart
|
|
6
|
+
*
|
|
7
|
+
* Rule: Disable debug modes in production environments
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class DartS059Analyzer {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.ruleId = 'S059';
|
|
13
|
+
this.language = 'dart';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getMetadata() {
|
|
17
|
+
return {
|
|
18
|
+
ruleId: 'S059',
|
|
19
|
+
name: 'Disable Debug Mode',
|
|
20
|
+
language: 'dart',
|
|
21
|
+
delegateTo: 'dart_analyzer',
|
|
22
|
+
description: 'Disable debug modes in production environments'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getConfig() {
|
|
27
|
+
return {
|
|
28
|
+
checkDebugPrint: true,
|
|
29
|
+
checkKDebugMode: true,
|
|
30
|
+
checkAssertStatements: true,
|
|
31
|
+
severity: 'high'
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async analyze(files, language, options) {
|
|
36
|
+
// Delegated to DartAnalyzer binary via heuristic-engine.js
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
supportsLanguage(language) {
|
|
41
|
+
return language === 'dart';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = DartS059Analyzer;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S059 Rule Router - Disable Debug Mode
|
|
3
|
+
*
|
|
4
|
+
* Routes analysis to the appropriate language-specific analyzer.
|
|
5
|
+
* Supports: TypeScript, JavaScript, Dart
|
|
6
|
+
*
|
|
7
|
+
* Rule: Disable debug modes in production environments
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
class S059Router {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.analyzers = new Map();
|
|
15
|
+
this.ruleId = 'S059';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getAnalyzer(language) {
|
|
19
|
+
const normalizedLang = this.normalizeLanguage(language);
|
|
20
|
+
|
|
21
|
+
if (!this.analyzers.has(normalizedLang)) {
|
|
22
|
+
try {
|
|
23
|
+
const analyzerPath = path.join(__dirname, normalizedLang, 'analyzer.js');
|
|
24
|
+
const AnalyzerClass = require(analyzerPath);
|
|
25
|
+
this.analyzers.set(normalizedLang, new AnalyzerClass());
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this.analyzers.get(normalizedLang);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
normalizeLanguage(language) {
|
|
35
|
+
if (typeof language !== 'string') {
|
|
36
|
+
return 'typescript';
|
|
37
|
+
}
|
|
38
|
+
const languageMap = {
|
|
39
|
+
'typescript': 'typescript',
|
|
40
|
+
'javascript': 'typescript',
|
|
41
|
+
'ts': 'typescript',
|
|
42
|
+
'js': 'typescript',
|
|
43
|
+
'dart': 'dart'
|
|
44
|
+
};
|
|
45
|
+
return languageMap[language.toLowerCase()] || language.toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
supportsLanguage(language) {
|
|
49
|
+
if (typeof language !== 'string') return false;
|
|
50
|
+
const supported = ['typescript', 'javascript', 'ts', 'js', 'dart'];
|
|
51
|
+
return supported.includes(language.toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getSupportedLanguages() {
|
|
55
|
+
return ['typescript', 'javascript', 'dart'];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async analyze(files, language, options = {}) {
|
|
59
|
+
const analyzer = this.getAnalyzer(language);
|
|
60
|
+
if (!analyzer) return [];
|
|
61
|
+
if (typeof analyzer.analyze === 'function') {
|
|
62
|
+
return analyzer.analyze(files, language, options);
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async initialize(semanticEngineOrLanguage = null, semanticEngine = null) {
|
|
68
|
+
let engine = semanticEngine;
|
|
69
|
+
let lang = null;
|
|
70
|
+
|
|
71
|
+
if (typeof semanticEngineOrLanguage === 'string') {
|
|
72
|
+
lang = semanticEngineOrLanguage;
|
|
73
|
+
} else if (semanticEngineOrLanguage && typeof semanticEngineOrLanguage === 'object') {
|
|
74
|
+
engine = semanticEngineOrLanguage;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (lang) {
|
|
78
|
+
const analyzer = this.getAnalyzer(lang);
|
|
79
|
+
if (analyzer && typeof analyzer.initialize === 'function') {
|
|
80
|
+
await analyzer.initialize(engine);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = new S059Router();
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S059 – Disable debug modes in production environments
|
|
3
|
+
*
|
|
4
|
+
* Detects hardcoded debug modes:
|
|
5
|
+
* - DEBUG = true hardcoded
|
|
6
|
+
* - Development-only features without env check
|
|
7
|
+
* - Debug endpoints exposed
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const fs = require('fs');
|
|
11
|
+
|
|
12
|
+
class S059Analyzer {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.ruleId = 'S059';
|
|
15
|
+
this.ruleName = 'Disable debug modes in production environments';
|
|
16
|
+
this.description = 'Ensure debug features are disabled in production';
|
|
17
|
+
|
|
18
|
+
this.warningPatterns = [
|
|
19
|
+
{
|
|
20
|
+
pattern: /DEBUG\s*=\s*true(?!\s*&&|\s*\|\|)/gi,
|
|
21
|
+
message: 'DEBUG hardcoded to true - use environment variable',
|
|
22
|
+
type: 'hardcoded_debug'
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
pattern: /dumpExceptions\s*:\s*true/gi,
|
|
26
|
+
message: 'Exception dumping enabled - disable in production',
|
|
27
|
+
type: 'dump_exceptions'
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
pattern: /showStackError\s*:\s*true/gi,
|
|
31
|
+
message: 'Stack errors shown - disable in production',
|
|
32
|
+
type: 'show_stack'
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
pattern: /app\.(?:get|use)\s*\(\s*['"`]\/debug/gi,
|
|
36
|
+
message: 'Debug endpoint exposed - remove or protect in production',
|
|
37
|
+
type: 'debug_endpoint'
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
pattern: /sourceMap\s*:\s*true(?![^}]*production)/gi,
|
|
41
|
+
message: 'Source maps enabled - disable in production builds',
|
|
42
|
+
type: 'source_maps'
|
|
43
|
+
}
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
this.skipPatterns = [
|
|
47
|
+
/\.test\./i, /\.spec\./i, /test\//i, /__tests__\//i, /node_modules/i,
|
|
48
|
+
/\.dev\./i, /development/i
|
|
49
|
+
];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
shouldSkipFile(filePath) {
|
|
53
|
+
return this.skipPatterns.some(p => p.test(filePath));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async analyze(files, language, options = {}) {
|
|
57
|
+
const violations = [];
|
|
58
|
+
for (const filePath of files) {
|
|
59
|
+
if (this.shouldSkipFile(filePath)) continue;
|
|
60
|
+
try {
|
|
61
|
+
const content = fs.readFileSync(filePath, 'utf8');
|
|
62
|
+
violations.push(...this.analyzeFile(content, filePath));
|
|
63
|
+
} catch (e) { /* skip */ }
|
|
64
|
+
}
|
|
65
|
+
return violations;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
analyzeFile(content, filePath) {
|
|
69
|
+
const violations = [];
|
|
70
|
+
for (const { pattern, message, type } of this.warningPatterns) {
|
|
71
|
+
pattern.lastIndex = 0;
|
|
72
|
+
let match;
|
|
73
|
+
while ((match = pattern.exec(content)) !== null) {
|
|
74
|
+
const lineNum = content.substring(0, match.index).split('\n').length;
|
|
75
|
+
violations.push({
|
|
76
|
+
file: filePath, line: lineNum, column: 1,
|
|
77
|
+
message, severity: 'warning', ruleId: this.ruleId, type
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return violations;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
module.exports = S059Analyzer;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "S060",
|
|
3
|
+
"name": "Enforce minimum password length of 8 characters, recommend 15+",
|
|
4
|
+
"description": "Enforce minimum password length of 8 characters as absolute minimum. Recommend 15+ characters for better security. Support passwords up to 64-128 characters. Combine with breach password checking.",
|
|
5
|
+
"category": "security",
|
|
6
|
+
"severity": "medium",
|
|
7
|
+
"enabled": true,
|
|
8
|
+
"engines": ["heuristic"],
|
|
9
|
+
"enginePreference": ["heuristic"],
|
|
10
|
+
"tags": ["security", "password", "authentication", "policy"],
|
|
11
|
+
"examples": {
|
|
12
|
+
"valid": [
|
|
13
|
+
"if (password.length < 8) throw new Error('Password too short');",
|
|
14
|
+
"const MIN_PASSWORD_LENGTH = 12;",
|
|
15
|
+
"validator.isLength(password, { min: 8, max: 128 });"
|
|
16
|
+
],
|
|
17
|
+
"invalid": [
|
|
18
|
+
"if (password.length < 4) // Too short minimum",
|
|
19
|
+
"if (password.length < 6) // Below NIST recommendation",
|
|
20
|
+
"// No password length validation"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"fixable": false,
|
|
24
|
+
"docs": {
|
|
25
|
+
"description": "This rule enforces NIST SP 800-63B password guidelines. Minimum 8 characters absolute minimum (12+ recommended). Support maximum length of 64+ characters. Allow all printable characters including spaces. Check against breach databases (Have I Been Pwned). Do not require arbitrary complexity rules (uppercase, numbers, symbols).",
|
|
26
|
+
"url": "https://pages.nist.gov/800-63-3/sp800-63b.html"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S060 Dart Analyzer - Password Minimum Length
|
|
3
|
+
*
|
|
4
|
+
* This is a JS wrapper that delegates to DartAnalyzer binary.
|
|
5
|
+
* Actual implementation: dart_analyzer/lib/rules/security/S060_password_minimum_length.dart
|
|
6
|
+
*
|
|
7
|
+
* Rule: Enforce minimum password length of 8 characters, recommend 15+
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
class DartS060Analyzer {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.ruleId = 'S060';
|
|
13
|
+
this.language = 'dart';
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
getMetadata() {
|
|
17
|
+
return {
|
|
18
|
+
ruleId: 'S060',
|
|
19
|
+
name: 'Password Minimum Length',
|
|
20
|
+
language: 'dart',
|
|
21
|
+
delegateTo: 'dart_analyzer',
|
|
22
|
+
description: 'Enforce minimum password length of 8 characters, recommend 15+'
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getConfig() {
|
|
27
|
+
return {
|
|
28
|
+
minLength: 8,
|
|
29
|
+
recommendedLength: 15,
|
|
30
|
+
maxLength: 128,
|
|
31
|
+
severity: 'medium'
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async analyze(files, language, options) {
|
|
36
|
+
// Delegated to DartAnalyzer binary via heuristic-engine.js
|
|
37
|
+
return [];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
supportsLanguage(language) {
|
|
41
|
+
return language === 'dart';
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
module.exports = DartS060Analyzer;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S060 Rule Router - Password Minimum Length
|
|
3
|
+
*
|
|
4
|
+
* Routes analysis to the appropriate language-specific analyzer.
|
|
5
|
+
* Supports: TypeScript, JavaScript, Dart
|
|
6
|
+
*
|
|
7
|
+
* Rule: Enforce minimum password length of 8 characters, recommend 15+
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const path = require('path');
|
|
11
|
+
|
|
12
|
+
class S060Router {
|
|
13
|
+
constructor() {
|
|
14
|
+
this.analyzers = new Map();
|
|
15
|
+
this.ruleId = 'S060';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
getAnalyzer(language) {
|
|
19
|
+
const normalizedLang = this.normalizeLanguage(language);
|
|
20
|
+
|
|
21
|
+
if (!this.analyzers.has(normalizedLang)) {
|
|
22
|
+
try {
|
|
23
|
+
const analyzerPath = path.join(__dirname, normalizedLang, 'analyzer.js');
|
|
24
|
+
const AnalyzerClass = require(analyzerPath);
|
|
25
|
+
this.analyzers.set(normalizedLang, new AnalyzerClass());
|
|
26
|
+
} catch (error) {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return this.analyzers.get(normalizedLang);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
normalizeLanguage(language) {
|
|
35
|
+
if (typeof language !== 'string') {
|
|
36
|
+
return 'typescript';
|
|
37
|
+
}
|
|
38
|
+
const languageMap = {
|
|
39
|
+
'typescript': 'typescript',
|
|
40
|
+
'javascript': 'typescript',
|
|
41
|
+
'ts': 'typescript',
|
|
42
|
+
'js': 'typescript',
|
|
43
|
+
'dart': 'dart'
|
|
44
|
+
};
|
|
45
|
+
return languageMap[language.toLowerCase()] || language.toLowerCase();
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
supportsLanguage(language) {
|
|
49
|
+
if (typeof language !== 'string') return false;
|
|
50
|
+
const supported = ['typescript', 'javascript', 'ts', 'js', 'dart'];
|
|
51
|
+
return supported.includes(language.toLowerCase());
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
getSupportedLanguages() {
|
|
55
|
+
return ['typescript', 'javascript', 'dart'];
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async analyze(files, language, options = {}) {
|
|
59
|
+
const analyzer = this.getAnalyzer(language);
|
|
60
|
+
if (!analyzer) return [];
|
|
61
|
+
if (typeof analyzer.analyze === 'function') {
|
|
62
|
+
return analyzer.analyze(files, language, options);
|
|
63
|
+
}
|
|
64
|
+
return [];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async initialize(semanticEngineOrLanguage = null, semanticEngine = null) {
|
|
68
|
+
let engine = semanticEngine;
|
|
69
|
+
let lang = null;
|
|
70
|
+
|
|
71
|
+
if (typeof semanticEngineOrLanguage === 'string') {
|
|
72
|
+
lang = semanticEngineOrLanguage;
|
|
73
|
+
} else if (semanticEngineOrLanguage && typeof semanticEngineOrLanguage === 'object') {
|
|
74
|
+
engine = semanticEngineOrLanguage;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (lang) {
|
|
78
|
+
const analyzer = this.getAnalyzer(lang);
|
|
79
|
+
if (analyzer && typeof analyzer.initialize === 'function') {
|
|
80
|
+
await analyzer.initialize(engine);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
module.exports = new S060Router();
|