@sun-asterisk/sunlint 1.3.16 → 1.3.17
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/config/rule-analysis-strategies.js +3 -3
- package/config/rules/enhanced-rules-registry.json +40 -20
- package/core/cli-action-handler.js +2 -2
- package/core/config-merger.js +28 -6
- package/core/constants/defaults.js +1 -1
- package/core/file-targeting-service.js +72 -4
- package/core/output-service.js +21 -4
- package/engines/heuristic-engine.js +5 -0
- package/package.json +1 -1
- package/rules/common/C002_no_duplicate_code/README.md +115 -0
- package/rules/common/C002_no_duplicate_code/analyzer.js +615 -219
- package/rules/common/C002_no_duplicate_code/test-cases/api-handlers.ts +64 -0
- package/rules/common/C002_no_duplicate_code/test-cases/data-processor.ts +46 -0
- package/rules/common/C002_no_duplicate_code/test-cases/good-example.tsx +40 -0
- package/rules/common/C002_no_duplicate_code/test-cases/product-service.ts +57 -0
- package/rules/common/C002_no_duplicate_code/test-cases/user-service.ts +49 -0
- package/rules/common/C008/analyzer.js +40 -0
- package/rules/common/C008/config.json +20 -0
- package/rules/common/C008/ts-morph-analyzer.js +1067 -0
- package/rules/common/C018_no_throw_generic_error/analyzer.js +1 -1
- package/rules/common/C018_no_throw_generic_error/symbol-based-analyzer.js +27 -3
- package/rules/common/C024_no_scatter_hardcoded_constants/symbol-based-analyzer.js +504 -162
- package/rules/common/C029_catch_block_logging/analyzer.js +499 -89
- package/rules/common/C033_separate_service_repository/README.md +131 -20
- package/rules/common/C033_separate_service_repository/analyzer.js +1 -1
- package/rules/common/C033_separate_service_repository/symbol-based-analyzer.js +417 -274
- package/rules/common/C041_no_sensitive_hardcode/analyzer.js +144 -254
- package/rules/common/C041_no_sensitive_hardcode/config.json +50 -0
- package/rules/common/C041_no_sensitive_hardcode/symbol-based-analyzer.js +575 -0
- package/rules/common/C067_no_hardcoded_config/analyzer.js +17 -16
- package/rules/common/C067_no_hardcoded_config/symbol-based-analyzer.js +3477 -659
- package/rules/docs/C002_no_duplicate_code.md +276 -11
- package/rules/index.js +5 -1
- package/rules/security/S006_no_plaintext_recovery_codes/analyzer.js +266 -88
- package/rules/security/S006_no_plaintext_recovery_codes/symbol-based-analyzer.js +805 -0
- package/rules/security/S010_no_insecure_encryption/README.md +78 -0
- package/rules/security/S010_no_insecure_encryption/analyzer.js +463 -398
- package/rules/security/S013_tls_enforcement/README.md +51 -0
- package/rules/security/S013_tls_enforcement/analyzer.js +99 -0
- package/rules/security/S013_tls_enforcement/config.json +41 -0
- package/rules/security/S013_tls_enforcement/symbol-based-analyzer.js +339 -0
- package/rules/security/S014_tls_version_enforcement/README.md +354 -0
- package/rules/security/S014_tls_version_enforcement/analyzer.js +118 -0
- package/rules/security/S014_tls_version_enforcement/config.json +56 -0
- package/rules/security/S014_tls_version_enforcement/symbol-based-analyzer.js +194 -0
- package/rules/security/S055_content_type_validation/analyzer.js +121 -279
- package/rules/security/S055_content_type_validation/symbol-based-analyzer.js +346 -0
- package/rules/tests/C002_no_duplicate_code.test.js +111 -22
- package/rules/common/C029_catch_block_logging/analyzer-smart-pipeline.js +0 -755
- package/rules/common/C041_no_sensitive_hardcode/ast-analyzer.js +0 -296
|
@@ -1,87 +1,241 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* S006 - No Plaintext Recovery/Activation Codes
|
|
3
|
+
*
|
|
4
|
+
* Main analyzer using symbol-based analysis to detect plaintext recovery/activation codes
|
|
4
5
|
* Based on OWASP A02:2021 - Cryptographic Failures
|
|
5
6
|
*/
|
|
6
7
|
|
|
8
|
+
// Command: node cli.js --rule=S006 --input=examples/rule-test-fixtures/rules/S006_no_plaintext_recovery_codes --engine=heuristic
|
|
9
|
+
|
|
10
|
+
const S006SymbolBasedAnalyzer = require("./symbol-based-analyzer");
|
|
11
|
+
|
|
7
12
|
class S006Analyzer {
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.ruleId = "S006";
|
|
15
|
+
this.semanticEngine = options.semanticEngine || null;
|
|
16
|
+
this.verbose = options.verbose || false;
|
|
17
|
+
|
|
18
|
+
try {
|
|
19
|
+
this.symbolAnalyzer = new S006SymbolBasedAnalyzer(this.semanticEngine);
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.warn(`⚠ [S006] Failed to create symbol analyzer: ${e.message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async initialize(semanticEngine) {
|
|
26
|
+
this.semanticEngine = semanticEngine;
|
|
27
|
+
if (this.symbolAnalyzer && this.symbolAnalyzer.initialize) {
|
|
28
|
+
await this.symbolAnalyzer.initialize(semanticEngine);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
analyzeSingle(filePath, options = {}) {
|
|
33
|
+
return this.analyze([filePath], "typescript", options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async analyze(files, language, options = {}) {
|
|
37
|
+
const violations = [];
|
|
38
|
+
for (const filePath of files) {
|
|
39
|
+
try {
|
|
40
|
+
const vs = await this.analyzeFile(filePath, options);
|
|
41
|
+
violations.push(...vs);
|
|
42
|
+
} catch (e) {
|
|
43
|
+
console.warn(`⚠ [S006] Analysis error for ${filePath}: ${e.message}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return violations;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async analyzeFile(filePath, options = {}) {
|
|
50
|
+
const violationMap = new Map();
|
|
51
|
+
|
|
52
|
+
if (!this.symbolAnalyzer) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Skip test files, build directories, and node_modules
|
|
57
|
+
if (this.shouldSkipFile(filePath)) {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
let sourceFile = null;
|
|
63
|
+
if (this.semanticEngine?.project) {
|
|
64
|
+
sourceFile = this.semanticEngine.project.getSourceFile(filePath);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!sourceFile) {
|
|
68
|
+
// Create temporary ts-morph source file
|
|
69
|
+
const fs = require("fs");
|
|
70
|
+
const path = require("path");
|
|
71
|
+
const { Project } = require("ts-morph");
|
|
72
|
+
if (!fs.existsSync(filePath)) {
|
|
73
|
+
throw new Error(`File not found: ${filePath}`);
|
|
74
|
+
}
|
|
75
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
76
|
+
const tmp = new Project({
|
|
77
|
+
useInMemoryFileSystem: true,
|
|
78
|
+
compilerOptions: { allowJs: true },
|
|
79
|
+
});
|
|
80
|
+
sourceFile = tmp.createSourceFile(path.basename(filePath), content);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (sourceFile) {
|
|
84
|
+
const symbolViolations = await this.symbolAnalyzer.analyze(
|
|
85
|
+
sourceFile,
|
|
86
|
+
filePath
|
|
87
|
+
);
|
|
88
|
+
symbolViolations.forEach((v) => {
|
|
89
|
+
const key = `${v.line}:${v.column}:${v.message}`;
|
|
90
|
+
if (!violationMap.has(key)) violationMap.set(key, v);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.warn(`⚠ [S006] Symbol analysis failed: ${e.message}`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return Array.from(violationMap.values()).map((v) => ({
|
|
98
|
+
...v,
|
|
99
|
+
filePath,
|
|
100
|
+
file: filePath,
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
shouldSkipFile(filePath) {
|
|
105
|
+
const skipPatterns = [
|
|
106
|
+
"test/",
|
|
107
|
+
"tests/",
|
|
108
|
+
"__tests__/",
|
|
109
|
+
".test.",
|
|
110
|
+
".spec.",
|
|
111
|
+
"node_modules/",
|
|
112
|
+
"build/",
|
|
113
|
+
"dist/",
|
|
114
|
+
".next/",
|
|
115
|
+
"coverage/",
|
|
116
|
+
"vendor/",
|
|
117
|
+
"mocks/",
|
|
118
|
+
".mock.",
|
|
119
|
+
];
|
|
120
|
+
|
|
121
|
+
return skipPatterns.some((pattern) => filePath.includes(pattern));
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
cleanup() {
|
|
125
|
+
if (this.symbolAnalyzer?.cleanup) {
|
|
126
|
+
this.symbolAnalyzer.cleanup();
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Keep old implementation for reference but not used
|
|
132
|
+
class S006AnalyzerOld {
|
|
8
133
|
constructor() {
|
|
9
|
-
this.ruleId =
|
|
10
|
-
this.ruleName =
|
|
11
|
-
this.description =
|
|
12
|
-
|
|
134
|
+
this.ruleId = "S006";
|
|
135
|
+
this.ruleName = "No Plaintext Recovery/Activation Codes";
|
|
136
|
+
this.description = "Do not send recovery or activation codes in plaintext";
|
|
137
|
+
|
|
13
138
|
// Keywords that indicate sensitive codes
|
|
14
139
|
this.sensitiveCodeKeywords = [
|
|
15
|
-
|
|
16
|
-
|
|
140
|
+
"recovery",
|
|
141
|
+
"activation",
|
|
142
|
+
"reset",
|
|
143
|
+
"verification",
|
|
144
|
+
"confirm",
|
|
145
|
+
"verify",
|
|
146
|
+
"otp",
|
|
147
|
+
"totp",
|
|
148
|
+
"code",
|
|
149
|
+
"pin",
|
|
150
|
+
"token",
|
|
151
|
+
"secret",
|
|
152
|
+
"key",
|
|
153
|
+
"password",
|
|
17
154
|
];
|
|
18
|
-
|
|
155
|
+
|
|
19
156
|
// Keywords that indicate code sending/transmission
|
|
20
157
|
this.sendingKeywords = [
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
158
|
+
"send",
|
|
159
|
+
"email",
|
|
160
|
+
"sms",
|
|
161
|
+
"text",
|
|
162
|
+
"message",
|
|
163
|
+
"mail",
|
|
164
|
+
"push",
|
|
165
|
+
"notify",
|
|
166
|
+
"transmit",
|
|
167
|
+
"deliver",
|
|
168
|
+
"dispatch",
|
|
169
|
+
"forward",
|
|
170
|
+
"post",
|
|
171
|
+
"put",
|
|
172
|
+
"create",
|
|
173
|
+
"response",
|
|
174
|
+
"body",
|
|
175
|
+
"content",
|
|
176
|
+
"payload",
|
|
177
|
+
"data",
|
|
24
178
|
];
|
|
25
|
-
|
|
179
|
+
|
|
26
180
|
// Patterns that indicate plaintext transmission
|
|
27
181
|
this.plaintextPatterns = [
|
|
28
182
|
// Email/SMS sending with codes
|
|
29
183
|
/(?:send|email|sms|text|message).*(?:recovery|activation|reset|verification|otp|code|pin)/i,
|
|
30
184
|
/(?:recovery|activation|reset|verification|otp|code|pin).*(?:send|email|sms|text|message)/i,
|
|
31
|
-
|
|
185
|
+
|
|
32
186
|
// HTTP responses with codes in body
|
|
33
187
|
/(?:response|body|json|data|payload).*(?:recovery|activation|reset|verification|otp|code|pin)/i,
|
|
34
188
|
/(?:recovery|activation|reset|verification|otp|code|pin).*(?:response|body|json|data|payload)/i,
|
|
35
|
-
|
|
189
|
+
|
|
36
190
|
// Direct code exposure in strings
|
|
37
191
|
/".*(?:recovery|activation|reset|verification|otp|code|pin).*"/i,
|
|
38
192
|
/'.*(?:recovery|activation|reset|verification|otp|code|pin).*'/i,
|
|
39
193
|
/`.*(?:recovery|activation|reset|verification|otp|code|pin).*`/i,
|
|
40
|
-
|
|
194
|
+
|
|
41
195
|
// Template strings with codes
|
|
42
196
|
/\$\{.*(?:recovery|activation|reset|verification|otp|code|pin).*\}/i,
|
|
43
|
-
|
|
197
|
+
|
|
44
198
|
// API endpoint responses
|
|
45
199
|
/return.*(?:recovery|activation|reset|verification|otp|code|pin)/i,
|
|
46
200
|
/res\.(?:send|json|end).*(?:recovery|activation|reset|verification|otp|code|pin)/i,
|
|
47
201
|
];
|
|
48
|
-
|
|
202
|
+
|
|
49
203
|
// Patterns that should be excluded (safe practices)
|
|
50
204
|
this.safePatterns = [
|
|
51
205
|
// Hashed or encrypted codes
|
|
52
206
|
/hash|encrypt|cipher|bcrypt|crypto|secure/i,
|
|
53
|
-
|
|
207
|
+
|
|
54
208
|
// Environment variables or config
|
|
55
209
|
/process\.env|config\.|getenv/i,
|
|
56
|
-
|
|
210
|
+
|
|
57
211
|
// Database storage (not transmission)
|
|
58
212
|
/save|store|insert|update|database|db\./i,
|
|
59
|
-
|
|
213
|
+
|
|
60
214
|
// Logging patterns (depends on context but often acceptable for debugging)
|
|
61
215
|
/log|debug|trace|console/i,
|
|
62
|
-
|
|
216
|
+
|
|
63
217
|
// Comments and documentation
|
|
64
218
|
/\/\/|\/\*|\*\/|@param|@return|@example/,
|
|
65
|
-
|
|
219
|
+
|
|
66
220
|
// Type definitions and interfaces
|
|
67
221
|
/interface|type|enum|class.*\{/i,
|
|
68
|
-
|
|
222
|
+
|
|
69
223
|
// Import/export statements
|
|
70
224
|
/import|export|require|module\.exports/i,
|
|
71
|
-
|
|
225
|
+
|
|
72
226
|
// Safe message patterns (no actual codes exposed)
|
|
73
227
|
/instructions sent|sent to|check your|please enter|has been sent|successfully sent|we've sent|click the link|enter the code|will expire/i,
|
|
74
|
-
|
|
228
|
+
|
|
75
229
|
// Configuration and constants
|
|
76
230
|
/const\s+\w+\s*=|enum\s+\w+|type\s+\w+/i,
|
|
77
|
-
|
|
231
|
+
|
|
78
232
|
// Function definitions
|
|
79
233
|
/function\s+\w+|async\s+\w+|\w+\s*\(/i,
|
|
80
|
-
|
|
234
|
+
|
|
81
235
|
// Return statements with safe messages
|
|
82
236
|
/return\s*\{[^}]*success[^}]*\}/i,
|
|
83
237
|
];
|
|
84
|
-
|
|
238
|
+
|
|
85
239
|
// Common safe variable names that might contain keywords
|
|
86
240
|
this.safeVariableNames = [
|
|
87
241
|
/^(is|has|can|should|will|enable|disable|show|hide|display).*code/i,
|
|
@@ -95,15 +249,15 @@ class S006Analyzer {
|
|
|
95
249
|
|
|
96
250
|
async analyze(files, language, options = {}) {
|
|
97
251
|
const violations = [];
|
|
98
|
-
|
|
252
|
+
|
|
99
253
|
for (const filePath of files) {
|
|
100
254
|
// Skip test files, build directories, and node_modules
|
|
101
255
|
if (this.shouldSkipFile(filePath)) {
|
|
102
256
|
continue;
|
|
103
257
|
}
|
|
104
|
-
|
|
258
|
+
|
|
105
259
|
try {
|
|
106
|
-
const content = require(
|
|
260
|
+
const content = require("fs").readFileSync(filePath, "utf8");
|
|
107
261
|
const fileViolations = this.analyzeFile(content, filePath, options);
|
|
108
262
|
violations.push(...fileViolations);
|
|
109
263
|
} catch (error) {
|
|
@@ -112,41 +266,55 @@ class S006Analyzer {
|
|
|
112
266
|
}
|
|
113
267
|
}
|
|
114
268
|
}
|
|
115
|
-
|
|
269
|
+
|
|
116
270
|
return violations;
|
|
117
271
|
}
|
|
118
272
|
|
|
119
273
|
shouldSkipFile(filePath) {
|
|
120
274
|
const skipPatterns = [
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
275
|
+
"test/",
|
|
276
|
+
"tests/",
|
|
277
|
+
"__tests__/",
|
|
278
|
+
".test.",
|
|
279
|
+
".spec.",
|
|
280
|
+
"node_modules/",
|
|
281
|
+
"build/",
|
|
282
|
+
"dist/",
|
|
283
|
+
".next/",
|
|
284
|
+
"coverage/",
|
|
285
|
+
"vendor/",
|
|
286
|
+
"mocks/",
|
|
287
|
+
".mock.",
|
|
124
288
|
// Removed 'fixtures/' to allow testing
|
|
125
289
|
];
|
|
126
|
-
|
|
127
|
-
return skipPatterns.some(pattern => filePath.includes(pattern));
|
|
290
|
+
|
|
291
|
+
return skipPatterns.some((pattern) => filePath.includes(pattern));
|
|
128
292
|
}
|
|
129
293
|
|
|
130
294
|
analyzeFile(content, filePath, options = {}) {
|
|
131
295
|
const violations = [];
|
|
132
|
-
const lines = content.split(
|
|
133
|
-
|
|
296
|
+
const lines = content.split("\n");
|
|
297
|
+
|
|
134
298
|
lines.forEach((line, index) => {
|
|
135
299
|
const lineNumber = index + 1;
|
|
136
300
|
const trimmedLine = line.trim();
|
|
137
|
-
|
|
301
|
+
|
|
138
302
|
// Skip comments, imports, and empty lines
|
|
139
303
|
if (this.shouldSkipLine(trimmedLine)) {
|
|
140
304
|
return;
|
|
141
305
|
}
|
|
142
|
-
|
|
306
|
+
|
|
143
307
|
// Check for potential plaintext code transmission
|
|
144
|
-
const violation = this.checkForPlaintextCodeTransmission(
|
|
308
|
+
const violation = this.checkForPlaintextCodeTransmission(
|
|
309
|
+
line,
|
|
310
|
+
lineNumber,
|
|
311
|
+
filePath
|
|
312
|
+
);
|
|
145
313
|
if (violation) {
|
|
146
314
|
violations.push(violation);
|
|
147
315
|
}
|
|
148
316
|
});
|
|
149
|
-
|
|
317
|
+
|
|
150
318
|
return violations;
|
|
151
319
|
}
|
|
152
320
|
|
|
@@ -154,30 +322,34 @@ class S006Analyzer {
|
|
|
154
322
|
// Skip comments, imports, and other non-code lines
|
|
155
323
|
return (
|
|
156
324
|
line.length === 0 ||
|
|
157
|
-
line.startsWith(
|
|
158
|
-
line.startsWith(
|
|
159
|
-
line.startsWith(
|
|
160
|
-
line.startsWith(
|
|
161
|
-
line.startsWith(
|
|
162
|
-
line.startsWith(
|
|
163
|
-
line.includes(
|
|
325
|
+
line.startsWith("//") ||
|
|
326
|
+
line.startsWith("/*") ||
|
|
327
|
+
line.startsWith("*") ||
|
|
328
|
+
line.startsWith("import ") ||
|
|
329
|
+
line.startsWith("export ") ||
|
|
330
|
+
line.startsWith("require(") ||
|
|
331
|
+
line.includes("module.exports")
|
|
164
332
|
);
|
|
165
333
|
}
|
|
166
334
|
|
|
167
335
|
checkForPlaintextCodeTransmission(line, lineNumber, filePath) {
|
|
168
336
|
const lowerLine = line.toLowerCase();
|
|
169
|
-
|
|
337
|
+
|
|
170
338
|
// First check if line contains safe patterns (early exit)
|
|
171
339
|
if (this.containsSafePattern(line)) {
|
|
172
340
|
return null;
|
|
173
341
|
}
|
|
174
|
-
|
|
342
|
+
|
|
175
343
|
// Check for variable assignments with sensitive names
|
|
176
|
-
const sensitiveAssignment = this.checkSensitiveAssignment(
|
|
344
|
+
const sensitiveAssignment = this.checkSensitiveAssignment(
|
|
345
|
+
line,
|
|
346
|
+
lineNumber,
|
|
347
|
+
filePath
|
|
348
|
+
);
|
|
177
349
|
if (sensitiveAssignment) {
|
|
178
350
|
return sensitiveAssignment;
|
|
179
351
|
}
|
|
180
|
-
|
|
352
|
+
|
|
181
353
|
// Check for direct plaintext patterns
|
|
182
354
|
for (const pattern of this.plaintextPatterns) {
|
|
183
355
|
if (pattern.test(line)) {
|
|
@@ -185,67 +357,73 @@ class S006Analyzer {
|
|
|
185
357
|
if (this.hasTransmissionContext(line)) {
|
|
186
358
|
return {
|
|
187
359
|
ruleId: this.ruleId,
|
|
188
|
-
severity:
|
|
189
|
-
message:
|
|
360
|
+
severity: "error",
|
|
361
|
+
message:
|
|
362
|
+
"Recovery/activation codes should not be transmitted in plaintext. Use encrypted channels or hash the codes.",
|
|
190
363
|
line: lineNumber,
|
|
191
364
|
column: this.findPatternColumn(line, pattern),
|
|
192
365
|
filePath: filePath,
|
|
193
|
-
type:
|
|
194
|
-
details:
|
|
366
|
+
type: "plaintext_code_transmission",
|
|
367
|
+
details:
|
|
368
|
+
"Consider using encrypted communication or sending only hashed/masked versions of sensitive codes.",
|
|
195
369
|
};
|
|
196
370
|
}
|
|
197
371
|
}
|
|
198
372
|
}
|
|
199
|
-
|
|
373
|
+
|
|
200
374
|
return null;
|
|
201
375
|
}
|
|
202
376
|
|
|
203
377
|
containsSafePattern(line) {
|
|
204
|
-
return this.safePatterns.some(pattern => pattern.test(line));
|
|
378
|
+
return this.safePatterns.some((pattern) => pattern.test(line));
|
|
205
379
|
}
|
|
206
380
|
|
|
207
381
|
checkSensitiveAssignment(line, lineNumber, filePath) {
|
|
208
382
|
// Look for variable assignments that combine sensitive codes with transmission
|
|
209
|
-
const assignmentMatch = line.match(
|
|
383
|
+
const assignmentMatch = line.match(
|
|
384
|
+
/(?:const|let|var)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*(.+)/
|
|
385
|
+
);
|
|
210
386
|
if (!assignmentMatch) {
|
|
211
387
|
return null;
|
|
212
388
|
}
|
|
213
|
-
|
|
389
|
+
|
|
214
390
|
const [, variableName, valueExpr] = assignmentMatch;
|
|
215
391
|
const lowerVarName = variableName.toLowerCase();
|
|
216
392
|
const lowerValueExpr = valueExpr.toLowerCase();
|
|
217
|
-
|
|
393
|
+
|
|
218
394
|
// Skip safe variable names
|
|
219
|
-
if (this.safeVariableNames.some(pattern => pattern.test(lowerVarName))) {
|
|
395
|
+
if (this.safeVariableNames.some((pattern) => pattern.test(lowerVarName))) {
|
|
220
396
|
return null;
|
|
221
397
|
}
|
|
222
|
-
|
|
398
|
+
|
|
223
399
|
// Check if variable name suggests code transmission
|
|
224
|
-
const hasSensitiveCodeKeyword = this.sensitiveCodeKeywords.some(keyword =>
|
|
400
|
+
const hasSensitiveCodeKeyword = this.sensitiveCodeKeywords.some((keyword) =>
|
|
225
401
|
lowerVarName.includes(keyword)
|
|
226
402
|
);
|
|
227
|
-
|
|
228
|
-
const hasSendingKeyword = this.sendingKeywords.some(
|
|
229
|
-
|
|
403
|
+
|
|
404
|
+
const hasSendingKeyword = this.sendingKeywords.some(
|
|
405
|
+
(keyword) =>
|
|
406
|
+
lowerVarName.includes(keyword) || lowerValueExpr.includes(keyword)
|
|
230
407
|
);
|
|
231
|
-
|
|
408
|
+
|
|
232
409
|
if (hasSensitiveCodeKeyword && hasSendingKeyword) {
|
|
233
410
|
// Check if the value looks like it contains actual codes or sensitive data
|
|
234
411
|
if (this.valueContainsCodes(valueExpr)) {
|
|
235
412
|
return {
|
|
236
413
|
ruleId: this.ruleId,
|
|
237
|
-
severity:
|
|
414
|
+
severity: "warning",
|
|
238
415
|
message: `Variable '${variableName}' appears to handle sensitive codes for transmission. Ensure codes are encrypted or hashed.`,
|
|
239
416
|
line: lineNumber,
|
|
240
417
|
column: line.indexOf(variableName) + 1,
|
|
241
418
|
filePath: filePath,
|
|
242
|
-
type:
|
|
419
|
+
type: "sensitive_code_variable",
|
|
243
420
|
variableName: variableName,
|
|
244
|
-
details:
|
|
421
|
+
details:
|
|
422
|
+
"Consider encrypting sensitive codes before transmission or use secure communication channels.",
|
|
245
423
|
};
|
|
246
424
|
}
|
|
247
425
|
}
|
|
248
|
-
|
|
426
|
+
|
|
249
427
|
return null;
|
|
250
428
|
}
|
|
251
429
|
|
|
@@ -255,21 +433,21 @@ class S006Analyzer {
|
|
|
255
433
|
/res\.(?:send|json|status|end)/i,
|
|
256
434
|
/response\.(?:send|json|status|end)/i,
|
|
257
435
|
/return.*(?:json|response|status)/i,
|
|
258
|
-
|
|
436
|
+
|
|
259
437
|
// Email/SMS functions
|
|
260
438
|
/(?:sendEmail|sendSMS|sendMessage|notify|mail)/i,
|
|
261
|
-
|
|
439
|
+
|
|
262
440
|
// Template rendering
|
|
263
441
|
/render|template|view|html|email/i,
|
|
264
|
-
|
|
442
|
+
|
|
265
443
|
// API responses
|
|
266
444
|
/\.json\(|\.send\(|\.end\(/,
|
|
267
|
-
|
|
445
|
+
|
|
268
446
|
// String concatenation or template literals with codes
|
|
269
447
|
/\+.*['"`]|['"`].*\+|\$\{.*\}/,
|
|
270
448
|
];
|
|
271
|
-
|
|
272
|
-
return transmissionIndicators.some(indicator => indicator.test(line));
|
|
449
|
+
|
|
450
|
+
return transmissionIndicators.some((indicator) => indicator.test(line));
|
|
273
451
|
}
|
|
274
452
|
|
|
275
453
|
valueContainsCodes(valueExpr) {
|
|
@@ -277,24 +455,24 @@ class S006Analyzer {
|
|
|
277
455
|
const codePatterns = [
|
|
278
456
|
// Template strings with variables
|
|
279
457
|
/\$\{[^}]+\}/,
|
|
280
|
-
|
|
458
|
+
|
|
281
459
|
// String concatenation
|
|
282
460
|
/\+\s*[a-zA-Z_$]/,
|
|
283
|
-
|
|
461
|
+
|
|
284
462
|
// Function calls that might return codes
|
|
285
463
|
/\w+\([^)]*\)/,
|
|
286
|
-
|
|
464
|
+
|
|
287
465
|
// Property access that might be codes
|
|
288
466
|
/\w+\.\w+/,
|
|
289
|
-
|
|
467
|
+
|
|
290
468
|
// Array/object access
|
|
291
469
|
/\[.*\]/,
|
|
292
|
-
|
|
470
|
+
|
|
293
471
|
// Direct string literals that look like codes (6+ chars with mixed case/numbers)
|
|
294
472
|
/['"`][a-zA-Z0-9]{6,}['"`]/,
|
|
295
473
|
];
|
|
296
|
-
|
|
297
|
-
return codePatterns.some(pattern => pattern.test(valueExpr));
|
|
474
|
+
|
|
475
|
+
return codePatterns.some((pattern) => pattern.test(valueExpr));
|
|
298
476
|
}
|
|
299
477
|
|
|
300
478
|
findPatternColumn(line, pattern) {
|