@sun-asterisk/sunlint 1.3.26 → 1.3.28

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 (69) hide show
  1. package/config/rules/enhanced-rules-registry.json +101 -17
  2. package/config/rules/rules-registry-generated.json +22 -22
  3. package/origin-rules/security-en.md +351 -338
  4. package/package.json +1 -1
  5. package/rules/common/C003_no_vague_abbreviations/analyzer.js +73 -21
  6. package/rules/common/C017_constructor_logic/symbol-based-analyzer.js +206 -2
  7. package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +553 -58
  8. package/rules/common/C029_catch_block_logging/analyzer.js +47 -12
  9. package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +35 -15
  10. package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +9 -5
  11. package/rules/security/S003_open_redirect_protection/README.md +371 -0
  12. package/rules/security/S003_open_redirect_protection/analyzer.js +135 -0
  13. package/rules/security/S003_open_redirect_protection/config.json +58 -0
  14. package/rules/security/S003_open_redirect_protection/symbol-based-analyzer.js +884 -0
  15. package/rules/security/S004_sensitive_data_logging/analyzer.js +135 -0
  16. package/rules/security/S004_sensitive_data_logging/config.json +62 -0
  17. package/rules/security/S004_sensitive_data_logging/symbol-based-analyzer.js +592 -0
  18. package/rules/security/S005_no_origin_auth/analyzer.js +97 -148
  19. package/rules/security/S005_no_origin_auth/config.json +28 -67
  20. package/rules/security/S005_no_origin_auth/symbol-based-analyzer.js +708 -0
  21. package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +170 -31
  22. package/rules/security/S010_no_insecure_encryption/analyzer.js +8 -2
  23. package/rules/security/S012_hardcoded_secrets/analyzer.js +149 -0
  24. package/rules/security/S012_hardcoded_secrets/config.json +75 -0
  25. package/rules/security/S012_hardcoded_secrets/symbol-based-analyzer.js +1204 -0
  26. package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +87 -0
  27. package/rules/security/S017_use_parameterized_queries/analyzer.js +11 -78
  28. package/rules/security/S017_use_parameterized_queries/symbol-based-analyzer.js +1146 -1
  29. package/rules/security/S019_smtp_injection_protection/analyzer.js +120 -0
  30. package/rules/security/S019_smtp_injection_protection/config.json +35 -0
  31. package/rules/security/S019_smtp_injection_protection/symbol-based-analyzer.js +687 -0
  32. package/rules/security/S020_no_eval_dynamic_code/analyzer.js +55 -130
  33. package/rules/security/S020_no_eval_dynamic_code/symbol-based-analyzer.js +4 -19
  34. package/rules/security/S022_escape_output_context/README.md +254 -0
  35. package/rules/security/S022_escape_output_context/analyzer.js +510 -0
  36. package/rules/security/S022_escape_output_context/config.json +229 -0
  37. package/rules/security/S023_no_json_injection/analyzer.js +15 -0
  38. package/rules/security/S023_no_json_injection/ast-analyzer.js +18 -3
  39. package/rules/security/S023_no_json_injection/config.json +133 -0
  40. package/rules/security/S024_xpath_xxe_protection/regex-based-analyzer.js +41 -0
  41. package/rules/security/S027_no_hardcoded_secrets/analyzer.js +67 -8
  42. package/rules/security/S027_no_hardcoded_secrets/categorized-analyzer.js +29 -6
  43. package/rules/security/S029_csrf_protection/config.json +127 -0
  44. package/rules/security/S030_directory_browsing_protection/regex-based-analyzer.js +160 -28
  45. package/rules/security/S030_directory_browsing_protection/symbol-based-analyzer.js +81 -19
  46. package/rules/security/S031_secure_session_cookies/analyzer.js +20 -2
  47. package/rules/security/S031_secure_session_cookies/regex-based-analyzer.js +100 -0
  48. package/rules/security/S031_secure_session_cookies/symbol-based-analyzer.js +8 -1
  49. package/rules/security/S032_httponly_session_cookies/analyzer.js +2 -2
  50. package/rules/security/S032_httponly_session_cookies/regex-based-analyzer.js +115 -0
  51. package/rules/security/S032_httponly_session_cookies/symbol-based-analyzer.js +39 -10
  52. package/rules/security/S036_lfi_rfi_protection/analyzer.js +224 -0
  53. package/rules/security/S036_lfi_rfi_protection/config.json +20 -0
  54. package/rules/security/S040_session_fixation_protection/analyzer.js +153 -0
  55. package/rules/security/S040_session_fixation_protection/config.json +20 -0
  56. package/rules/security/S042_require_re_authentication_for_long_lived/README.md +83 -0
  57. package/rules/security/S042_require_re_authentication_for_long_lived/analyzer.js +153 -0
  58. package/rules/security/S042_require_re_authentication_for_long_lived/config.json +41 -0
  59. package/rules/security/S042_require_re_authentication_for_long_lived/symbol-based-analyzer.js +1139 -0
  60. package/rules/security/S043_password_changes_invalidate_all_sessions/README.md +107 -0
  61. package/rules/security/S043_password_changes_invalidate_all_sessions/analyzer.js +153 -0
  62. package/rules/security/S043_password_changes_invalidate_all_sessions/config.json +41 -0
  63. package/rules/security/S043_password_changes_invalidate_all_sessions/symbol-based-analyzer.js +541 -0
  64. package/docs/COMMAND-EXAMPLES.md +0 -390
  65. package/docs/FILE_LIMITS_COMPLETION_REPORT.md +0 -151
  66. package/docs/FOLDER_STRUCTURE.md +0 -59
  67. package/docs/SIMPLIFIED_USAGE_GUIDE.md +0 -208
  68. package/rules/security/S017_use_parameterized_queries/regex-based-analyzer.js +0 -541
  69. package/rules/security/S020_no_eval_dynamic_code/regex-based-analyzer.js +0 -307
@@ -0,0 +1,229 @@
1
+ {
2
+ "rule": {
3
+ "id": "S022",
4
+ "name": "Escape data properly based on output context",
5
+ "description": "Ensure data is properly escaped or sanitized based on the output context (HTML, JavaScript, CSS, URL) to prevent Cross-Site Scripting (XSS) attacks. Different contexts require different escaping methods.",
6
+ "category": "security",
7
+ "severity": "error",
8
+ "languages": ["typescript", "javascript"],
9
+ "frameworks": ["express", "nestjs", "react", "vue", "angular", "node"],
10
+ "version": "1.0.0",
11
+ "status": "stable",
12
+ "tags": ["security", "xss", "escaping", "sanitization", "owasp", "injection"],
13
+ "references": [
14
+ "https://owasp.org/www-community/attacks/xss/",
15
+ "https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html",
16
+ "https://cheatsheetseries.owasp.org/cheatsheets/DOM_based_XSS_Prevention_Cheat_Sheet.html",
17
+ "https://portswigger.net/web-security/cross-site-scripting",
18
+ "https://cwe.mitre.org/data/definitions/79.html"
19
+ ]
20
+ },
21
+ "configuration": {
22
+ "contexts": {
23
+ "html": {
24
+ "dangerousMethods": [
25
+ "innerHTML",
26
+ "outerHTML",
27
+ "insertAdjacentHTML",
28
+ "document.write",
29
+ "document.writeln",
30
+ "v-html",
31
+ "dangerouslySetInnerHTML"
32
+ ],
33
+ "requireEscaping": true,
34
+ "severity": "error"
35
+ },
36
+ "javascript": {
37
+ "dangerousMethods": [
38
+ "eval",
39
+ "Function",
40
+ "setTimeout",
41
+ "setInterval",
42
+ "execScript"
43
+ ],
44
+ "requireEscaping": true,
45
+ "severity": "error"
46
+ },
47
+ "url": {
48
+ "dangerousMethods": [
49
+ "location.href",
50
+ "window.location",
51
+ "location.assign",
52
+ "location.replace",
53
+ "window.open"
54
+ ],
55
+ "requireValidation": true,
56
+ "severity": "warning"
57
+ },
58
+ "attribute": {
59
+ "dangerousAttributes": [
60
+ "onclick",
61
+ "onload",
62
+ "onerror",
63
+ "onmouseover",
64
+ "href",
65
+ "src"
66
+ ],
67
+ "requireEscaping": true,
68
+ "severity": "error"
69
+ }
70
+ },
71
+ "userInputSources": [
72
+ "req.body",
73
+ "req.query",
74
+ "req.params",
75
+ "request.body",
76
+ "request.query",
77
+ "request.params",
78
+ "localStorage",
79
+ "sessionStorage",
80
+ "window.location",
81
+ "location.search",
82
+ "location.hash",
83
+ "URLSearchParams",
84
+ "document.cookie",
85
+ "window.name",
86
+ "postMessage"
87
+ ],
88
+ "safeEscapingFunctions": [
89
+ "escape",
90
+ "escapeHtml",
91
+ "sanitize",
92
+ "DOMPurify.sanitize",
93
+ "textContent",
94
+ "innerText",
95
+ "setAttribute",
96
+ "encodeURIComponent",
97
+ "encodeURI",
98
+ "validator.escape",
99
+ "xss.filterXSS"
100
+ ],
101
+ "safeFrameworkMethods": {
102
+ "react": [
103
+ "textContent",
104
+ "children",
105
+ "{variable}"
106
+ ],
107
+ "vue": [
108
+ "{{ variable }}",
109
+ "v-text"
110
+ ],
111
+ "angular": [
112
+ "{{ variable }}",
113
+ "[textContent]"
114
+ ]
115
+ }
116
+ },
117
+ "examples": {
118
+ "violations": [
119
+ {
120
+ "description": "Unescaped user input in innerHTML",
121
+ "code": "element.innerHTML = userInput;",
122
+ "context": "html"
123
+ },
124
+ {
125
+ "description": "User input in eval without validation",
126
+ "code": "eval(req.query.code);",
127
+ "context": "javascript"
128
+ },
129
+ {
130
+ "description": "Unvalidated URL from user input",
131
+ "code": "window.location = req.query.redirect;",
132
+ "context": "url"
133
+ },
134
+ {
135
+ "description": "React dangerouslySetInnerHTML without sanitization",
136
+ "code": "<div dangerouslySetInnerHTML={{__html: userInput}} />",
137
+ "context": "html"
138
+ },
139
+ {
140
+ "description": "Vue v-html directive with user input",
141
+ "code": "<div v-html=\"userInput\"></div>",
142
+ "context": "html"
143
+ },
144
+ {
145
+ "description": "Dynamic event handler with user input",
146
+ "code": "element.setAttribute('onclick', userInput);",
147
+ "context": "attribute"
148
+ }
149
+ ],
150
+ "fixes": [
151
+ {
152
+ "description": "Use textContent instead of innerHTML",
153
+ "code": "element.textContent = userInput;",
154
+ "context": "html"
155
+ },
156
+ {
157
+ "description": "Sanitize HTML with DOMPurify",
158
+ "code": "element.innerHTML = DOMPurify.sanitize(userInput);",
159
+ "context": "html"
160
+ },
161
+ {
162
+ "description": "Never use eval with user input",
163
+ "code": "// Avoid eval entirely, use JSON.parse or safe alternatives",
164
+ "context": "javascript"
165
+ },
166
+ {
167
+ "description": "Validate and whitelist URLs",
168
+ "code": "const allowedHosts = ['example.com'];\nconst url = new URL(req.query.redirect);\nif (allowedHosts.includes(url.hostname)) {\n window.location = url.href;\n}",
169
+ "context": "url"
170
+ },
171
+ {
172
+ "description": "React with proper sanitization",
173
+ "code": "<div dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(userInput)}} />",
174
+ "context": "html"
175
+ },
176
+ {
177
+ "description": "Vue using v-text for safe output",
178
+ "code": "<div v-text=\"userInput\"></div>",
179
+ "context": "html"
180
+ }
181
+ ]
182
+ },
183
+ "testing": {
184
+ "testCases": [
185
+ {
186
+ "name": "innerHTML_with_user_input",
187
+ "type": "violation",
188
+ "description": "Using innerHTML with unsanitized user input"
189
+ },
190
+ {
191
+ "name": "eval_with_user_input",
192
+ "type": "violation",
193
+ "description": "Using eval with user input"
194
+ },
195
+ {
196
+ "name": "location_with_user_input",
197
+ "type": "violation",
198
+ "description": "Setting location with unvalidated user input"
199
+ },
200
+ {
201
+ "name": "textContent_safe_output",
202
+ "type": "clean",
203
+ "description": "Using textContent for safe output"
204
+ },
205
+ {
206
+ "name": "dompurify_sanitization",
207
+ "type": "clean",
208
+ "description": "Using DOMPurify to sanitize HTML"
209
+ },
210
+ {
211
+ "name": "url_validation",
212
+ "type": "clean",
213
+ "description": "Validating URLs before redirect"
214
+ }
215
+ ]
216
+ },
217
+ "performance": {
218
+ "complexity": "O(n)",
219
+ "description": "Linear complexity based on number of DOM manipulation and output operations in the source code"
220
+ },
221
+ "owaspMapping": {
222
+ "category": "A03:2021 – Injection",
223
+ "subcategories": [
224
+ "A07:2021 – Identification and Authentication Failures"
225
+ ],
226
+ "cweId": "CWE-79",
227
+ "description": "Validates that all output is properly escaped or sanitized based on context to prevent XSS attacks"
228
+ }
229
+ }
@@ -82,6 +82,21 @@ class S023Analyzer {
82
82
  }
83
83
 
84
84
  async analyzeFile(filePath, language, options = {}) {
85
+ // Skip test files - they often use JSON.parse for testing purposes
86
+ const skipPatterns = [
87
+ /\.spec\.(ts|tsx|js|jsx)$/,
88
+ /\.test\.(ts|tsx|js|jsx)$/,
89
+ /__tests__\//, // Test directories
90
+ /__mocks__\//, // Mock directories
91
+ /\/tests?\//, // Test directories
92
+ /\/fixtures?\//, // Fixture directories
93
+ ];
94
+
95
+ const shouldSkip = skipPatterns.some((pattern) => pattern.test(filePath));
96
+ if (shouldSkip) {
97
+ return [];
98
+ }
99
+
85
100
  switch (language) {
86
101
  case 'typescript':
87
102
  case 'javascript':
@@ -10,12 +10,27 @@ class S023ASTAnalyzer {
10
10
 
11
11
  async analyze(files, language, options = {}) {
12
12
  const violations = [];
13
-
13
+
14
+ // Skip patterns for test files
15
+ const skipPatterns = [
16
+ /\.spec\.(ts|tsx|js|jsx)$/,
17
+ /\.test\.(ts|tsx|js|jsx)$/,
18
+ /__tests__\//,
19
+ /__mocks__\//,
20
+ /\/tests?\//,
21
+ /\/fixtures?\//,
22
+ ];
23
+
14
24
  for (const filePath of files) {
25
+ // Skip test files
26
+ if (skipPatterns.some(pattern => pattern.test(filePath))) {
27
+ continue;
28
+ }
29
+
15
30
  if (options.verbose) {
16
31
  console.log(`🎯 Running S023 AST analysis on ${path.basename(filePath)}`);
17
32
  }
18
-
33
+
19
34
  try {
20
35
  const content = fs.readFileSync(filePath, 'utf8');
21
36
  const fileViolations = await this.analyzeFile(filePath, content, language, options);
@@ -24,7 +39,7 @@ class S023ASTAnalyzer {
24
39
  console.warn(`⚠️ Failed to analyze ${filePath}: ${error.message}`);
25
40
  }
26
41
  }
27
-
42
+
28
43
  return violations;
29
44
  }
30
45
 
@@ -0,0 +1,133 @@
1
+ {
2
+ "rule": {
3
+ "id": "S023",
4
+ "name": "Prevent JSON Injection and JSON eval attacks",
5
+ "description": "Prevent JSON injection attacks and unsafe JSON handling. Detects unsafe JSON.parse(), eval() with JSON, JSON.stringify in HTML context, and JSON handling without proper validation.",
6
+ "category": "security",
7
+ "severity": "error",
8
+ "languages": ["typescript", "javascript"],
9
+ "frameworks": ["express", "nestjs", "node", "react", "vue", "angular"],
10
+ "version": "1.0.0",
11
+ "status": "stable",
12
+ "tags": ["security", "json", "injection", "xss", "owasp", "eval"],
13
+ "references": [
14
+ "https://owasp.org/www-community/vulnerabilities/JSON_Injection",
15
+ "https://cheatsheetseries.owasp.org/cheatsheets/Injection_Prevention_Cheat_Sheet.html",
16
+ "https://portswigger.net/web-security/dom-based/json-injection",
17
+ "https://cwe.mitre.org/data/definitions/94.html"
18
+ ]
19
+ },
20
+ "configuration": {
21
+ "checkJsonParse": true,
22
+ "checkEvalWithJson": true,
23
+ "checkJsonStringifyInHtml": true,
24
+ "userInputSources": [
25
+ "localStorage",
26
+ "sessionStorage",
27
+ "window.location",
28
+ "location.search",
29
+ "location.hash",
30
+ "URLSearchParams",
31
+ "req.body",
32
+ "req.query",
33
+ "req.params",
34
+ "request.body",
35
+ "request.query",
36
+ "document.cookie",
37
+ "window.name",
38
+ "postMessage",
39
+ "fetch",
40
+ "axios"
41
+ ],
42
+ "validationPatterns": [
43
+ "try",
44
+ "catch",
45
+ "typeof",
46
+ "instanceof",
47
+ "validate",
48
+ "check",
49
+ "isValid",
50
+ "sanitize",
51
+ "escape",
52
+ "filter"
53
+ ],
54
+ "htmlContextPatterns": [
55
+ "innerHTML",
56
+ "outerHTML",
57
+ "insertAdjacentHTML",
58
+ "document.write",
59
+ ".html(",
60
+ "<script",
61
+ "</script>"
62
+ ]
63
+ },
64
+ "examples": {
65
+ "violations": [
66
+ {
67
+ "description": "Unsafe JSON.parse with user input",
68
+ "code": "const data = JSON.parse(localStorage.getItem('userData'));"
69
+ },
70
+ {
71
+ "description": "Using eval() to parse JSON",
72
+ "code": "const obj = eval('(' + jsonString + ')');"
73
+ },
74
+ {
75
+ "description": "JSON.stringify in HTML context without escaping",
76
+ "code": "element.innerHTML = JSON.stringify(userInput);"
77
+ },
78
+ {
79
+ "description": "Parsing URL parameters without validation",
80
+ "code": "const params = JSON.parse(new URLSearchParams(window.location.search).get('data'));"
81
+ }
82
+ ],
83
+ "fixes": [
84
+ {
85
+ "description": "Validate input before parsing JSON",
86
+ "code": "try {\n const data = JSON.parse(localStorage.getItem('userData'));\n if (typeof data === 'object' && data !== null) {\n // Use data\n }\n} catch (e) {\n // Handle error\n}"
87
+ },
88
+ {
89
+ "description": "Always use JSON.parse instead of eval",
90
+ "code": "const obj = JSON.parse(jsonString);"
91
+ },
92
+ {
93
+ "description": "Escape JSON output when used in HTML",
94
+ "code": "element.textContent = JSON.stringify(userInput);\n// or\nelement.innerHTML = escapeHtml(JSON.stringify(userInput));"
95
+ }
96
+ ]
97
+ },
98
+ "testing": {
99
+ "testCases": [
100
+ {
101
+ "name": "unsafe_json_parse_localstorage",
102
+ "type": "violation",
103
+ "description": "JSON.parse with localStorage without validation"
104
+ },
105
+ {
106
+ "name": "eval_with_json",
107
+ "type": "violation",
108
+ "description": "Using eval() to parse JSON data"
109
+ },
110
+ {
111
+ "name": "json_stringify_html_context",
112
+ "type": "violation",
113
+ "description": "JSON.stringify output in innerHTML"
114
+ },
115
+ {
116
+ "name": "safe_json_parse_with_trycatch",
117
+ "type": "clean",
118
+ "description": "JSON.parse with proper try-catch validation"
119
+ }
120
+ ]
121
+ },
122
+ "performance": {
123
+ "complexity": "O(n)",
124
+ "description": "Linear complexity based on number of JSON operations in the source code"
125
+ },
126
+ "owaspMapping": {
127
+ "category": "A03:2021 – Injection",
128
+ "subcategories": [
129
+ "A05:2021 – Security Misconfiguration"
130
+ ],
131
+ "description": "Validates that JSON parsing and handling is done safely to prevent injection attacks and XSS vulnerabilities"
132
+ }
133
+ }
@@ -113,6 +113,12 @@ class S024RegexBasedAnalyzer {
113
113
  // Remove comments to avoid false positives
114
114
  const cleanContent = this.removeComments(content);
115
115
 
116
+ // PRE-FILTER: Skip files that don't use XPath or XML at all
117
+ // This prevents false positives on regular JavaScript code
118
+ if (!this.hasXPathOrXMLUsage(cleanContent)) {
119
+ return []; // Early return - no XPath/XML usage detected
120
+ }
121
+
116
122
  try {
117
123
  // Pattern 1: XPath Injection vulnerabilities
118
124
  violations.push(
@@ -146,6 +152,41 @@ class S024RegexBasedAnalyzer {
146
152
  return violations;
147
153
  }
148
154
 
155
+ hasXPathOrXMLUsage(content) {
156
+ // Check if file imports or uses XPath/XML libraries
157
+ const xpathXmlIndicators = [
158
+ // Library imports
159
+ /require\s*\(\s*['"]xpath['"]\s*\)/i,
160
+ /require\s*\(\s*['"]xmldom['"]\s*\)/i,
161
+ /require\s*\(\s*['"]libxmljs['"]\s*\)/i,
162
+ /require\s*\(\s*['"]xml2js['"]\s*\)/i,
163
+ /require\s*\(\s*['"]fast-xml-parser['"]\s*\)/i,
164
+ /from\s+['"]xpath['"]/i,
165
+ /from\s+['"]xmldom['"]/i,
166
+ /from\s+['"]libxmljs['"]/i,
167
+ /from\s+['"]xml2js['"]/i,
168
+
169
+ // XPath method calls
170
+ /xpath\s*\.\s*(?:evaluate|select|selectText|selectValue|selectNodes)/i,
171
+ /\.evaluate\s*\([^)]*\s*,\s*(?:XPathResult|doc|document)/i,
172
+
173
+ // XML parsing methods
174
+ /DOMParser\s*\(\s*\)/,
175
+ /parseFromString/i,
176
+ /parseXml/i,
177
+ /parseString\s*\(/,
178
+ /XSLTProcessor/,
179
+ /SAXParser/,
180
+
181
+ // XML document indicators
182
+ /<\?xml/i,
183
+ /xmlDoc/i,
184
+ /xmlDocument/i,
185
+ ];
186
+
187
+ return xpathXmlIndicators.some(pattern => pattern.test(content));
188
+ }
189
+
149
190
  analyzeXPathInjection(content, lines, filePath) {
150
191
  const violations = [];
151
192
 
@@ -74,7 +74,24 @@ class S027CategorizedAnalyzer {
74
74
  'coverage/', '.next/', '.cache/', 'tmp/',
75
75
  '.lock', '.log', '.min.js', '.bundle.js'
76
76
  ];
77
-
77
+
78
+ // Skip test files completely - they often contain mock/fake credentials
79
+ const testPatterns = [
80
+ /\.spec\.(ts|tsx|js|jsx)$/,
81
+ /\.test\.(ts|tsx|js|jsx)$/,
82
+ /__tests__\//,
83
+ /__mocks__\//,
84
+ /\/tests?\//,
85
+ /\/fixtures?\//,
86
+ /\/factories\//, // Test factories
87
+ /setupTests\./,
88
+ /testSetup\./,
89
+ ];
90
+
91
+ if (testPatterns.some(pattern => pattern.test(filePath))) {
92
+ return true;
93
+ }
94
+
78
95
  return skipPatterns.some(pattern => filePath.includes(pattern));
79
96
  }
80
97
 
@@ -95,11 +112,16 @@ class S027CategorizedAnalyzer {
95
112
  return;
96
113
  }
97
114
 
115
+ // Skip NEXT_PUBLIC_ environment variables - these are public by design
116
+ if (this.isPublicEnvironmentVariable(line)) {
117
+ return;
118
+ }
119
+
98
120
  // Check global exclude patterns first
99
121
  if (this.matchesGlobalExcludes(line)) {
100
122
  return;
101
123
  }
102
-
124
+
103
125
  // Check each category
104
126
  this.categories.forEach(category => {
105
127
  const categoryViolations = this.checkCategory(
@@ -136,7 +158,39 @@ class S027CategorizedAnalyzer {
136
158
  matchesGlobalExcludes(line) {
137
159
  return this.globalExcludePatterns.some(pattern => pattern.test(line));
138
160
  }
139
-
161
+
162
+ isPublicEnvironmentVariable(line) {
163
+ // NEXT_PUBLIC_, REACT_APP_, VITE_, PUBLIC_ prefixed env vars are public by design
164
+ // These are intentionally exposed to the client-side and are not secrets
165
+ const publicEnvPatterns = [
166
+ /NEXT_PUBLIC_[A-Z0-9_]+\s*[:=]/i, // Next.js public env vars
167
+ /REACT_APP_[A-Z0-9_]+\s*[:=]/i, // Create React App public env vars
168
+ /VITE_[A-Z0-9_]+\s*[:=]/i, // Vite public env vars
169
+ /PUBLIC_[A-Z0-9_]+\s*[:=]/i, // Generic public prefix
170
+ /EXPO_PUBLIC_[A-Z0-9_]+\s*[:=]/i, // Expo public env vars
171
+ ];
172
+
173
+ return publicEnvPatterns.some(pattern => pattern.test(line));
174
+ }
175
+
176
+ isEnvironmentVariableUsage(line) {
177
+ // Patterns that indicate environment variable or config service usage - these are SAFE
178
+ const safePatterns = [
179
+ /process\.env\./i, // process.env.DB_PASSWORD
180
+ /configService\.get/i, // configService.get('DB_PASSWORD')
181
+ /config\.get/i, // config.get('API_KEY')
182
+ /getConfig\(/i, // getConfig('secret')
183
+ /env\(['"]([^'"]+)['"]\)/i, // env('SECRET_KEY')
184
+ /process\.env\[['"]([^'"]+)['"]\]/i, // process.env['API_KEY']
185
+ /import\.meta\.env\./i, // import.meta.env.VITE_API_KEY
186
+ /Deno\.env\.get/i, // Deno.env.get()
187
+ /os\.getenv/i, // Python os.getenv()
188
+ /System\.getenv/i, // Java System.getenv()
189
+ ];
190
+
191
+ return safePatterns.some(pattern => pattern.test(line));
192
+ }
193
+
140
194
  checkCategory(category, line, lineNumber, filePath, isTestFile) {
141
195
  const violations = [];
142
196
 
@@ -156,14 +210,19 @@ class S027CategorizedAnalyzer {
156
210
  }
157
211
 
158
212
  // Check category-specific excludes
159
- if (category.compiledExcludePatterns &&
213
+ if (category.compiledExcludePatterns &&
160
214
  category.compiledExcludePatterns.some(pattern => pattern.test(matchedText))) {
161
215
  continue;
162
216
  }
163
-
164
- // Be more lenient in test files for lower severity categories
165
- // But still report critical/high severity issues even in test files
166
- if (isTestFile && category.severity === 'low') {
217
+
218
+ // Check if this uses environment variables or config services - SAFE patterns
219
+ if (this.isEnvironmentVariableUsage(line)) {
220
+ continue;
221
+ }
222
+
223
+ // Be more lenient in test files - skip all but critical severity
224
+ // Test files commonly use hardcoded values for testing purposes
225
+ if (isTestFile && (category.severity === 'low' || category.severity === 'medium' || category.severity === 'high')) {
167
226
  continue;
168
227
  }
169
228
 
@@ -136,7 +136,25 @@ class S027CategorizedAnalyzer {
136
136
  matchesGlobalExcludes(line) {
137
137
  return this.globalExcludePatterns.some(pattern => pattern.test(line));
138
138
  }
139
-
139
+
140
+ isEnvironmentVariableUsage(line) {
141
+ // Patterns that indicate environment variable or config service usage - these are SAFE
142
+ const safePatterns = [
143
+ /process\.env\./i, // process.env.DB_PASSWORD
144
+ /configService\.get/i, // configService.get('DB_PASSWORD')
145
+ /config\.get/i, // config.get('API_KEY')
146
+ /getConfig\(/i, // getConfig('secret')
147
+ /env\(['"]([^'"]+)['"]\)/i, // env('SECRET_KEY')
148
+ /process\.env\[['"]([^'"]+)['"]\]/i, // process.env['API_KEY']
149
+ /import\.meta\.env\./i, // import.meta.env.VITE_API_KEY
150
+ /Deno\.env\.get/i, // Deno.env.get()
151
+ /os\.getenv/i, // Python os.getenv()
152
+ /System\.getenv/i, // Java System.getenv()
153
+ ];
154
+
155
+ return safePatterns.some(pattern => pattern.test(line));
156
+ }
157
+
140
158
  checkCategory(category, line, lineNumber, filePath, isTestFile) {
141
159
  const violations = [];
142
160
 
@@ -156,14 +174,19 @@ class S027CategorizedAnalyzer {
156
174
  }
157
175
 
158
176
  // Check category-specific excludes
159
- if (category.compiledExcludePatterns &&
177
+ if (category.compiledExcludePatterns &&
160
178
  category.compiledExcludePatterns.some(pattern => pattern.test(matchedText))) {
161
179
  continue;
162
180
  }
163
-
164
- // Be more lenient in test files for lower severity categories
165
- // But still report critical/high severity issues even in test files
166
- if (isTestFile && category.severity === 'low') {
181
+
182
+ // Check if this uses environment variables or config services - SAFE patterns
183
+ if (this.isEnvironmentVariableUsage(line)) {
184
+ continue;
185
+ }
186
+
187
+ // Be more lenient in test files - skip all but critical severity
188
+ // Test files commonly use hardcoded values for testing purposes
189
+ if (isTestFile && (category.severity === 'low' || category.severity === 'medium' || category.severity === 'high')) {
167
190
  continue;
168
191
  }
169
192