eslint-plugin-node-security 4.5.0 → 4.7.0
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/README.md +2 -0
- package/package.json +5 -8
- package/src/index.d.ts +17 -10
- package/src/index.js +8 -31
- package/src/oxlint.d.ts +17 -20
- package/src/oxlint.js +0 -19
- package/src/rules/detect-child-process/index.js +0 -107
- package/src/rules/detect-eval-with-expression/index.js +2 -48
- package/src/rules/detect-non-literal-fs-filename/index.js +0 -82
- package/src/rules/detect-suspicious-dependencies/index.js +0 -12
- package/src/rules/lock-file/index.js +0 -18
- package/src/rules/no-arbitrary-file-access/index.js +0 -52
- package/src/rules/no-buffer-overread/index.js +3 -96
- package/src/rules/no-cryptojs/index.js +2 -10
- package/src/rules/no-cryptojs-weak-random/index.js +1 -13
- package/src/rules/no-data-in-temp-storage/index.js +0 -12
- package/src/rules/no-deprecated-buffer/index.js +0 -15
- package/src/rules/no-deprecated-cipher-method/index.js +0 -9
- package/src/rules/no-dynamic-algorithm-selection/index.js +0 -13
- package/src/rules/no-dynamic-command-string/index.js +201 -0
- package/src/rules/no-dynamic-dependency-loading/index.js +0 -13
- package/src/rules/no-dynamic-require/index.js +0 -10
- package/src/rules/no-ecb-mode/index.js +0 -7
- package/src/rules/no-insecure-key-derivation/index.js +0 -10
- package/src/rules/no-insecure-rsa-padding/index.js +0 -9
- package/src/rules/no-math-random-crypto/index.js +1 -17
- package/src/rules/no-self-signed-certs/index.js +1 -9
- package/src/rules/no-sha1-hash/index.js +0 -9
- package/src/rules/no-shell-injection/index.js +0 -20
- package/src/rules/no-ssrf/index.js +5 -79
- package/src/rules/no-static-iv/index.js +2 -21
- package/src/rules/no-timing-unsafe-compare/index.js +1 -11
- package/src/rules/no-toctou-vulnerability/index.js +0 -22
- package/src/rules/no-unsafe-buffer-alloc/index.js +87 -0
- package/src/rules/no-unsafe-dynamic-require/index.js +0 -22
- package/src/rules/no-weak-cipher-algorithm/index.js +0 -19
- package/src/rules/no-weak-hash-algorithm/index.js +0 -22
- package/src/rules/no-zip-slip/index.js +3 -81
- package/src/rules/prefer-native-crypto/index.js +4 -11
- package/src/rules/require-dependency-integrity/index.js +0 -11
- package/src/rules/require-secure-credential-storage/index.js +0 -12
- package/src/rules/require-secure-deletion/index.js +0 -29
- package/src/rules/require-storage-encryption/index.js +0 -12
- package/src/types/index.js +0 -6
- package/CHANGELOG.md +0 -384
- package/src/index.js.map +0 -1
- package/src/oxlint.js.map +0 -1
- package/src/rules/detect-child-process/index.d.ts +0 -52
- package/src/rules/detect-child-process/index.js.map +0 -1
- package/src/rules/detect-eval-with-expression/index.d.ts +0 -46
- package/src/rules/detect-eval-with-expression/index.js.map +0 -1
- package/src/rules/detect-non-literal-fs-filename/index.d.ts +0 -52
- package/src/rules/detect-non-literal-fs-filename/index.js.map +0 -1
- package/src/rules/detect-suspicious-dependencies/index.d.ts +0 -12
- package/src/rules/detect-suspicious-dependencies/index.js.map +0 -1
- package/src/rules/lock-file/index.d.ts +0 -13
- package/src/rules/lock-file/index.js.map +0 -1
- package/src/rules/no-arbitrary-file-access/index.d.ts +0 -12
- package/src/rules/no-arbitrary-file-access/index.js.map +0 -1
- package/src/rules/no-buffer-overread/index.d.ts +0 -39
- package/src/rules/no-buffer-overread/index.js.map +0 -1
- package/src/rules/no-cryptojs/index.d.ts +0 -24
- package/src/rules/no-cryptojs/index.js.map +0 -1
- package/src/rules/no-cryptojs-weak-random/index.d.ts +0 -24
- package/src/rules/no-cryptojs-weak-random/index.js.map +0 -1
- package/src/rules/no-data-in-temp-storage/index.d.ts +0 -14
- package/src/rules/no-data-in-temp-storage/index.js.map +0 -1
- package/src/rules/no-deprecated-buffer/index.d.ts +0 -23
- package/src/rules/no-deprecated-buffer/index.js.map +0 -1
- package/src/rules/no-deprecated-cipher-method/index.d.ts +0 -23
- package/src/rules/no-deprecated-cipher-method/index.js.map +0 -1
- package/src/rules/no-dynamic-algorithm-selection/index.d.ts +0 -26
- package/src/rules/no-dynamic-algorithm-selection/index.js.map +0 -1
- package/src/rules/no-dynamic-dependency-loading/index.d.ts +0 -12
- package/src/rules/no-dynamic-dependency-loading/index.js.map +0 -1
- package/src/rules/no-dynamic-require/index.d.ts +0 -21
- package/src/rules/no-dynamic-require/index.js.map +0 -1
- package/src/rules/no-ecb-mode/index.d.ts +0 -23
- package/src/rules/no-ecb-mode/index.js.map +0 -1
- package/src/rules/no-insecure-key-derivation/index.d.ts +0 -24
- package/src/rules/no-insecure-key-derivation/index.js.map +0 -1
- package/src/rules/no-insecure-rsa-padding/index.d.ts +0 -24
- package/src/rules/no-insecure-rsa-padding/index.js.map +0 -1
- package/src/rules/no-math-random-crypto/index.d.ts +0 -26
- package/src/rules/no-math-random-crypto/index.js.map +0 -1
- package/src/rules/no-self-signed-certs/index.d.ts +0 -23
- package/src/rules/no-self-signed-certs/index.js.map +0 -1
- package/src/rules/no-sha1-hash/index.d.ts +0 -24
- package/src/rules/no-sha1-hash/index.js.map +0 -1
- package/src/rules/no-shell-injection/index.d.ts +0 -20
- package/src/rules/no-shell-injection/index.js.map +0 -1
- package/src/rules/no-ssrf/index.d.ts +0 -25
- package/src/rules/no-ssrf/index.js.map +0 -1
- package/src/rules/no-static-iv/index.d.ts +0 -23
- package/src/rules/no-static-iv/index.js.map +0 -1
- package/src/rules/no-timing-unsafe-compare/index.d.ts +0 -23
- package/src/rules/no-timing-unsafe-compare/index.js.map +0 -1
- package/src/rules/no-toctou-vulnerability/index.d.ts +0 -26
- package/src/rules/no-toctou-vulnerability/index.js.map +0 -1
- package/src/rules/no-unsafe-dynamic-require/index.d.ts +0 -19
- package/src/rules/no-unsafe-dynamic-require/index.js.map +0 -1
- package/src/rules/no-weak-cipher-algorithm/index.d.ts +0 -25
- package/src/rules/no-weak-cipher-algorithm/index.js.map +0 -1
- package/src/rules/no-weak-hash-algorithm/index.d.ts +0 -25
- package/src/rules/no-weak-hash-algorithm/index.js.map +0 -1
- package/src/rules/no-zip-slip/index.d.ts +0 -35
- package/src/rules/no-zip-slip/index.js.map +0 -1
- package/src/rules/prefer-native-crypto/index.d.ts +0 -23
- package/src/rules/prefer-native-crypto/index.js.map +0 -1
- package/src/rules/require-dependency-integrity/index.d.ts +0 -12
- package/src/rules/require-dependency-integrity/index.js.map +0 -1
- package/src/rules/require-secure-credential-storage/index.d.ts +0 -12
- package/src/rules/require-secure-credential-storage/index.js.map +0 -1
- package/src/rules/require-secure-deletion/index.d.ts +0 -17
- package/src/rules/require-secure-deletion/index.js.map +0 -1
- package/src/rules/require-storage-encryption/index.d.ts +0 -12
- package/src/rules/require-storage-encryption/index.js.map +0 -1
- package/src/types/index.js.map +0 -1
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.detectEvalWithExpression = exports.generateRefactoringSteps = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
@@ -35,7 +30,6 @@ const EVAL_PATTERNS = [
|
|
|
35
30
|
safeAlternative: 'Template literals or template engine',
|
|
36
31
|
example: {
|
|
37
32
|
bad: 'eval(\'Hello \' + userName + \'!\')',
|
|
38
|
-
// oxlint-disable-next-line no-template-curly-in-string
|
|
39
33
|
good: 'const template = `Hello ${userName}!`;'
|
|
40
34
|
},
|
|
41
35
|
effort: '3 minutes'
|
|
@@ -51,10 +45,6 @@ const EVAL_PATTERNS = [
|
|
|
51
45
|
effort: '8 minutes'
|
|
52
46
|
}
|
|
53
47
|
];
|
|
54
|
-
/**
|
|
55
|
-
* Generate refactoring steps based on pattern.
|
|
56
|
-
* Module-scope so it is directly unit-testable (Layer-2).
|
|
57
|
-
*/
|
|
58
48
|
const generateRefactoringSteps = (pattern) => {
|
|
59
49
|
if (!pattern) {
|
|
60
50
|
return [
|
|
@@ -80,10 +70,8 @@ const generateRefactoringSteps = (pattern) => {
|
|
|
80
70
|
' 3. Validate inputs are numbers',
|
|
81
71
|
' 4. Consider using a math expression parser library'
|
|
82
72
|
].join('\n');
|
|
83
|
-
// oxlint-disable-next-line no-template-curly-in-string
|
|
84
73
|
case 'template':
|
|
85
74
|
return [
|
|
86
|
-
// oxlint-disable-next-line no-template-curly-in-string
|
|
87
75
|
' 1. Use template literals: `Hello ${name}`',
|
|
88
76
|
' 2. Sanitize variables before interpolation',
|
|
89
77
|
' 3. Use a template engine like Handlebars if complex',
|
|
@@ -118,7 +106,6 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
|
|
|
118
106
|
confidence: 'high',
|
|
119
107
|
},
|
|
120
108
|
messages: {
|
|
121
|
-
// 🎯 Token optimization: 38% reduction (47→29 tokens) - compact format saves LLM processing
|
|
122
109
|
evalWithExpression: (0, eslint_devkit_1.formatLLMMessage)({
|
|
123
110
|
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
124
111
|
issueName: 'eval() with dynamic code',
|
|
@@ -151,9 +138,7 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
|
|
|
151
138
|
issueName: 'Unsafe eval() for string interpolation',
|
|
152
139
|
cwe: 'CWE-95',
|
|
153
140
|
description: 'Use template literals instead of eval() for string interpolation',
|
|
154
|
-
// oxlint-disable-next-line no-template-curly-in-string
|
|
155
141
|
severity: 'HIGH',
|
|
156
|
-
// oxlint-disable-next-line no-template-curly-in-string
|
|
157
142
|
fix: 'Replace eval() with template literals: `Hello ${name}`',
|
|
158
143
|
documentationLink: 'https://owasp.org/www-community/attacks/Code_Injection',
|
|
159
144
|
}),
|
|
@@ -239,25 +224,14 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
|
|
|
239
224
|
create(context) {
|
|
240
225
|
const options = context.options[0] || {};
|
|
241
226
|
const { allowLiteralStrings = false, additionalEvalFunctions = [], strategy = 'auto' } = options;
|
|
242
|
-
/**
|
|
243
|
-
* All functions that can execute arbitrary code
|
|
244
|
-
* NOTE: setTimeout/setInterval are NOT eval-like - they don't execute code strings
|
|
245
|
-
*/
|
|
246
227
|
const evalFunctions = new Set([
|
|
247
228
|
'eval',
|
|
248
229
|
'Function',
|
|
249
230
|
...additionalEvalFunctions
|
|
250
231
|
]);
|
|
251
|
-
/**
|
|
252
|
-
* Check if a node is a literal string (safe)
|
|
253
|
-
*/
|
|
254
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
255
232
|
const isLiteralString = (node) => {
|
|
256
233
|
return node.type === 'Literal' && typeof node.value === 'string';
|
|
257
234
|
};
|
|
258
|
-
/**
|
|
259
|
-
* Select message ID based on strategy
|
|
260
|
-
*/
|
|
261
235
|
const selectStrategyMessage = (pattern) => {
|
|
262
236
|
switch (strategy) {
|
|
263
237
|
case 'remove':
|
|
@@ -268,7 +242,6 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
|
|
|
268
242
|
return 'strategyValidate';
|
|
269
243
|
case 'auto':
|
|
270
244
|
default:
|
|
271
|
-
// Auto mode: choose based on pattern confidence
|
|
272
245
|
if (pattern && pattern.category === 'json') {
|
|
273
246
|
return 'useJsonParse';
|
|
274
247
|
}
|
|
@@ -278,12 +251,9 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
|
|
|
278
251
|
if (pattern && pattern.category === 'template') {
|
|
279
252
|
return 'useTemplateLiteral';
|
|
280
253
|
}
|
|
281
|
-
return 'strategyRefactor';
|
|
254
|
+
return 'strategyRefactor';
|
|
282
255
|
}
|
|
283
256
|
};
|
|
284
|
-
/**
|
|
285
|
-
* Detect the pattern category from the expression
|
|
286
|
-
*/
|
|
287
257
|
const detectPattern = (expression) => {
|
|
288
258
|
for (const pattern of EVAL_PATTERNS) {
|
|
289
259
|
if (new RegExp(pattern.pattern, 'i').test(expression)) {
|
|
@@ -292,31 +262,21 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
|
|
|
292
262
|
}
|
|
293
263
|
return null;
|
|
294
264
|
};
|
|
295
|
-
/**
|
|
296
|
-
* Extract expression text for pattern analysis
|
|
297
|
-
*/
|
|
298
265
|
const extractExpression = (node) => {
|
|
299
266
|
const sourceCode = context.sourceCode;
|
|
300
|
-
// Try to get the argument text
|
|
301
267
|
if (node.arguments.length > 0) {
|
|
302
268
|
return sourceCode.getText(node.arguments[0]);
|
|
303
269
|
}
|
|
304
270
|
return 'dynamic expression';
|
|
305
271
|
};
|
|
306
|
-
/**
|
|
307
|
-
* Check call expressions for dangerous eval usage
|
|
308
|
-
*/
|
|
309
272
|
const checkCallExpression = (node) => {
|
|
310
|
-
// Check if it's a call to an eval-like function
|
|
311
273
|
if (node.callee.type === 'Identifier' &&
|
|
312
274
|
evalFunctions.has(node.callee.name)) {
|
|
313
|
-
// Skip if it's a literal string and literals are allowed
|
|
314
275
|
if (allowLiteralStrings &&
|
|
315
276
|
node.arguments.length > 0 &&
|
|
316
277
|
isLiteralString(node.arguments[0])) {
|
|
317
278
|
return;
|
|
318
279
|
}
|
|
319
|
-
// Skip if it's a direct string literal (safe)
|
|
320
280
|
if (node.arguments.length > 0 &&
|
|
321
281
|
node.callee.name === 'eval' &&
|
|
322
282
|
isLiteralString(node.arguments[0])) {
|
|
@@ -343,12 +303,11 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
|
|
|
343
303
|
safeAlternative: pattern.safeAlternative,
|
|
344
304
|
alternative: pattern.safeAlternative
|
|
345
305
|
},
|
|
346
|
-
fix: () => null
|
|
306
|
+
fix: () => null
|
|
347
307
|
}
|
|
348
308
|
] : undefined
|
|
349
309
|
});
|
|
350
310
|
}
|
|
351
|
-
// Also check for Function constructor usage
|
|
352
311
|
if (node.callee.type === 'NewExpression' &&
|
|
353
312
|
node.callee.callee.type === 'Identifier' &&
|
|
354
313
|
node.callee.callee.name === 'Function') {
|
|
@@ -373,11 +332,7 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
|
|
|
373
332
|
});
|
|
374
333
|
}
|
|
375
334
|
};
|
|
376
|
-
/**
|
|
377
|
-
* Check NewExpression for Function constructor usage
|
|
378
|
-
*/
|
|
379
335
|
const checkNewExpression = (node) => {
|
|
380
|
-
// Check for new Function() usage
|
|
381
336
|
if (node.callee.type === 'Identifier' && node.callee.name === 'Function') {
|
|
382
337
|
const sourceCode = context.sourceCode;
|
|
383
338
|
const expression = node.arguments.map((arg) => sourceCode.getText(arg)).join(', ');
|
|
@@ -407,4 +362,3 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
|
|
|
407
362
|
};
|
|
408
363
|
},
|
|
409
364
|
});
|
|
410
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,9 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.detectNonLiteralFsFilename = exports.determineRiskLevel = exports.generateRefactoringSteps = void 0;
|
|
9
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
@@ -54,18 +49,9 @@ const FS_OPERATIONS = [
|
|
|
54
49
|
effort: '15-20 minutes'
|
|
55
50
|
}
|
|
56
51
|
];
|
|
57
|
-
/**
|
|
58
|
-
* Check if path has dangerous patterns like ../ or ..\
|
|
59
|
-
* Module-scope so it is directly unit-testable (Layer-2).
|
|
60
|
-
*/
|
|
61
52
|
const hasTraversalPatterns = (pathStr) => {
|
|
62
53
|
return /\.\.[/\\]/.test(pathStr) || /^\.\.[/\\]/.test(pathStr);
|
|
63
54
|
};
|
|
64
|
-
/**
|
|
65
|
-
* Generate refactoring steps based on the operation.
|
|
66
|
-
* Module-scope so the default arm (methods without a dedicated entry) is
|
|
67
|
-
* directly unit-testable (Layer-2).
|
|
68
|
-
*/
|
|
69
55
|
const generateRefactoringSteps = (operation) => {
|
|
70
56
|
switch (operation.method) {
|
|
71
57
|
case 'readFile':
|
|
@@ -104,11 +90,6 @@ const generateRefactoringSteps = (operation) => {
|
|
|
104
90
|
}
|
|
105
91
|
};
|
|
106
92
|
exports.generateRefactoringSteps = generateRefactoringSteps;
|
|
107
|
-
/**
|
|
108
|
-
* Determine risk level based on the operation and path.
|
|
109
|
-
* Module-scope so the non-dangerous fallback (no FS_OPERATIONS entry sets
|
|
110
|
-
* dangerous: false today) is directly unit-testable (Layer-2).
|
|
111
|
-
*/
|
|
112
93
|
const determineRiskLevel = (operation, pathStr) => {
|
|
113
94
|
if (hasTraversalPatterns(pathStr)) {
|
|
114
95
|
return 'CRITICAL';
|
|
@@ -130,7 +111,6 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
130
111
|
confidence: 'medium',
|
|
131
112
|
},
|
|
132
113
|
messages: {
|
|
133
|
-
// 🎯 Token optimization: 39% reduction (49→30 tokens) - template variables still work
|
|
134
114
|
fsPathTraversal: (0, eslint_devkit_1.formatLLMMessage)({
|
|
135
115
|
icon: '🔑',
|
|
136
116
|
issueName: 'Path traversal',
|
|
@@ -216,9 +196,6 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
216
196
|
create(context) {
|
|
217
197
|
const options = context.options[0] || {};
|
|
218
198
|
const { allowLiterals = false, additionalMethods = [] } = options;
|
|
219
|
-
/**
|
|
220
|
-
* File system methods that can be dangerous with user input
|
|
221
|
-
*/
|
|
222
199
|
const dangerousMethods = new Set([
|
|
223
200
|
'readFile', 'readFileSync',
|
|
224
201
|
'writeFile', 'writeFileSync',
|
|
@@ -233,48 +210,29 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
233
210
|
'createReadStream', 'createWriteStream',
|
|
234
211
|
...additionalMethods
|
|
235
212
|
]);
|
|
236
|
-
/**
|
|
237
|
-
* Check if a node is a literal string (safe)
|
|
238
|
-
*/
|
|
239
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
240
213
|
const isLiteralString = (node) => {
|
|
241
214
|
return node.type === 'Literal' && typeof node.value === 'string';
|
|
242
215
|
};
|
|
243
|
-
/**
|
|
244
|
-
* Extract path argument from fs call.
|
|
245
|
-
* `method` comes from checkFsCall, which already resolved the callee
|
|
246
|
-
* shape — re-deriving it here would hide an unreachable defensive branch.
|
|
247
|
-
*/
|
|
248
216
|
const extractPathArgument = (node, method) => {
|
|
249
217
|
const operation = FS_OPERATIONS.find(op => op.method === method) || null;
|
|
250
|
-
// First argument is usually the path
|
|
251
218
|
const pathNode = node.arguments.length > 0 ? node.arguments[0] : null;
|
|
252
219
|
const sourceCode = context.sourceCode;
|
|
253
220
|
const path = pathNode ? sourceCode.getText(pathNode) : '';
|
|
254
221
|
return { path, pathNode, operation };
|
|
255
222
|
};
|
|
256
|
-
/**
|
|
257
|
-
* Determine if the path argument is potentially dangerous
|
|
258
|
-
*/
|
|
259
223
|
const isDangerousPath = (pathNode, pathStr) => {
|
|
260
|
-
// Allow literals if configured
|
|
261
224
|
if (allowLiterals && pathNode && isLiteralString(pathNode)) {
|
|
262
225
|
return false;
|
|
263
226
|
}
|
|
264
|
-
// Check for obvious traversal patterns in literals
|
|
265
227
|
if (pathNode && isLiteralString(pathNode) && hasTraversalPatterns(pathStr)) {
|
|
266
228
|
return true;
|
|
267
229
|
}
|
|
268
|
-
// SAFE: path.join(__dirname, 'literal', 'path') with all literal args
|
|
269
230
|
if (pathNode && isSafePathConstruction(pathNode)) {
|
|
270
231
|
return false;
|
|
271
232
|
}
|
|
272
|
-
// SAFE: Path variable inside validated if-block with startsWith check
|
|
273
233
|
if (pathNode && hasPathValidation(pathNode)) {
|
|
274
234
|
return false;
|
|
275
235
|
}
|
|
276
|
-
// SAFE: path.join("./base", dynamicVar) where dynamicVar has been validated
|
|
277
|
-
// Extract dynamic identifiers from path.join/resolve calls and check their validation
|
|
278
236
|
if (pathNode && pathNode.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
|
|
279
237
|
const callee = pathNode.callee;
|
|
280
238
|
if (callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
@@ -282,23 +240,14 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
282
240
|
callee.object.name === 'path' &&
|
|
283
241
|
callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
284
242
|
['join', 'resolve'].includes(callee.property.name)) {
|
|
285
|
-
// Find any dynamic (non-literal) identifier args and check if they're validated
|
|
286
243
|
const dynamicArgs = pathNode.arguments.filter((arg) => arg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && arg.name !== '__dirname');
|
|
287
244
|
if (dynamicArgs.length > 0 && dynamicArgs.every((arg) => hasPathValidation(arg))) {
|
|
288
245
|
return false;
|
|
289
246
|
}
|
|
290
247
|
}
|
|
291
248
|
}
|
|
292
|
-
// Any non-literal is dangerous
|
|
293
249
|
return !pathNode || !isLiteralString(pathNode);
|
|
294
250
|
};
|
|
295
|
-
/**
|
|
296
|
-
* Check if path is constructed safely using path.join/__dirname with literal args
|
|
297
|
-
*
|
|
298
|
-
* Safe patterns:
|
|
299
|
-
* - path.join(__dirname, 'data', 'file.json')
|
|
300
|
-
* - path.resolve(__dirname, 'uploads')
|
|
301
|
-
*/
|
|
302
251
|
const isSafePathConstruction = (pathNode) => {
|
|
303
252
|
if (pathNode.type !== eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
|
|
304
253
|
return false;
|
|
@@ -318,44 +267,29 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
318
267
|
if (args.length === 0) {
|
|
319
268
|
return false;
|
|
320
269
|
}
|
|
321
|
-
// First arg should be __dirname or a literal
|
|
322
270
|
const firstArg = args[0];
|
|
323
271
|
const isFirstArgSafe = (firstArg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && firstArg.name === '__dirname') ||
|
|
324
272
|
(firstArg.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof firstArg.value === 'string');
|
|
325
273
|
if (!isFirstArgSafe) {
|
|
326
274
|
return false;
|
|
327
275
|
}
|
|
328
|
-
// All remaining args should be literals
|
|
329
276
|
for (let i = 1; i < args.length; i++) {
|
|
330
277
|
const arg = args[i];
|
|
331
278
|
if (arg.type !== eslint_devkit_1.AST_NODE_TYPES.Literal || typeof arg.value !== 'string') {
|
|
332
279
|
return false;
|
|
333
280
|
}
|
|
334
|
-
// Also check for traversal patterns in literals
|
|
335
281
|
if (hasTraversalPatterns(String(arg.value))) {
|
|
336
282
|
return false;
|
|
337
283
|
}
|
|
338
284
|
}
|
|
339
285
|
return true;
|
|
340
286
|
};
|
|
341
|
-
/**
|
|
342
|
-
* Check if the path variable has been validated with startsWith()
|
|
343
|
-
*
|
|
344
|
-
* Safe patterns:
|
|
345
|
-
* 1. Inside if-block: if (safePath.startsWith(SAFE_DIR)) { fs.readFileSync(safePath); }
|
|
346
|
-
* 2. After guard clause: if (!safePath.startsWith(SAFE_DIR)) { throw }; fs.readFileSync(safePath);
|
|
347
|
-
*/
|
|
348
287
|
const hasPathValidation = (pathNode) => {
|
|
349
288
|
if (pathNode.type !== eslint_devkit_1.AST_NODE_TYPES.Identifier) {
|
|
350
289
|
return false;
|
|
351
290
|
}
|
|
352
291
|
const varName = pathNode.name;
|
|
353
|
-
// AST-based validation detection (faster than getText + regex)
|
|
354
292
|
const isValidationCall = (testNode) => {
|
|
355
|
-
// Handle negation: !path.startsWith(...). The negation flag was
|
|
356
|
-
// tracked here but never read afterwards (CodeQL:
|
|
357
|
-
// `js/useless-assignment-to-local`); current callers only need to
|
|
358
|
-
// know whether the call matches a validation idiom.
|
|
359
293
|
if (testNode.type === eslint_devkit_1.AST_NODE_TYPES.UnaryExpression &&
|
|
360
294
|
testNode.operator === '!' &&
|
|
361
295
|
testNode.argument.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
|
|
@@ -364,7 +298,6 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
364
298
|
if (testNode.type !== eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
|
|
365
299
|
return false;
|
|
366
300
|
}
|
|
367
|
-
// Pattern 1: varName.startsWith(...) or varName.includes(...)
|
|
368
301
|
if (testNode.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
369
302
|
testNode.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
370
303
|
testNode.callee.object.name === varName &&
|
|
@@ -373,22 +306,18 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
373
306
|
testNode.callee.property.name === 'includes')) {
|
|
374
307
|
return true;
|
|
375
308
|
}
|
|
376
|
-
// Pattern 2: ALLOWED_FILES.includes(varName) - allowlist validation
|
|
377
309
|
if (testNode.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
378
310
|
testNode.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
379
311
|
testNode.callee.property.name === 'includes') {
|
|
380
|
-
// Check if varName is in the arguments
|
|
381
312
|
for (const arg of testNode.arguments) {
|
|
382
313
|
if (arg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && arg.name === varName) {
|
|
383
314
|
return true;
|
|
384
315
|
}
|
|
385
316
|
}
|
|
386
317
|
}
|
|
387
|
-
// Pattern 3: /regex/.test(varName) - regex validation
|
|
388
318
|
if (testNode.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
389
319
|
testNode.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
390
320
|
testNode.callee.property.name === 'test') {
|
|
391
|
-
// Check if varName is in the arguments
|
|
392
321
|
for (const arg of testNode.arguments) {
|
|
393
322
|
if (arg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && arg.name === varName) {
|
|
394
323
|
return true;
|
|
@@ -405,17 +334,14 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
405
334
|
return consequent.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement ||
|
|
406
335
|
consequent.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement;
|
|
407
336
|
};
|
|
408
|
-
// Walk up to find enclosing IfStatement or BlockStatement
|
|
409
337
|
let current = pathNode.parent;
|
|
410
338
|
let foundFunctionBody = false;
|
|
411
339
|
while (current && !foundFunctionBody) {
|
|
412
|
-
// Check 1: Inside an if-block with validation
|
|
413
340
|
if (current.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
|
|
414
341
|
if (isValidationCall(current.test)) {
|
|
415
342
|
return true;
|
|
416
343
|
}
|
|
417
344
|
}
|
|
418
|
-
// Check 2: In a function body, look for preceding sibling if-statements with guard clause
|
|
419
345
|
if (current.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement && current.parent && (current.parent.type === eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration ||
|
|
420
346
|
current.parent.type === eslint_devkit_1.AST_NODE_TYPES.FunctionExpression ||
|
|
421
347
|
current.parent.type === eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
|
|
@@ -430,7 +356,6 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
430
356
|
}
|
|
431
357
|
return false;
|
|
432
358
|
});
|
|
433
|
-
// Look at preceding statements for validation patterns with early exit
|
|
434
359
|
for (let i = 0; i < nodeIndex; i++) {
|
|
435
360
|
const stmt = blockBody[i];
|
|
436
361
|
if (stmt.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement &&
|
|
@@ -444,11 +369,7 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
444
369
|
}
|
|
445
370
|
return false;
|
|
446
371
|
};
|
|
447
|
-
/**
|
|
448
|
-
* Check fs method calls for path traversal vulnerabilities
|
|
449
|
-
*/
|
|
450
372
|
const checkFsCall = (node) => {
|
|
451
|
-
// Check if it's an fs method call
|
|
452
373
|
if (node.callee.type !== 'MemberExpression' ||
|
|
453
374
|
node.callee.object.type !== 'Identifier' ||
|
|
454
375
|
node.callee.object.name !== 'fs' ||
|
|
@@ -456,13 +377,11 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
456
377
|
return;
|
|
457
378
|
}
|
|
458
379
|
const methodName = node.callee.property.name;
|
|
459
|
-
// Skip if not a dangerous method
|
|
460
380
|
if (!dangerousMethods.has(methodName)) {
|
|
461
381
|
return;
|
|
462
382
|
}
|
|
463
383
|
const { path, pathNode, operation } = extractPathArgument(node, methodName);
|
|
464
384
|
const method = methodName;
|
|
465
|
-
// Check if the path argument is dangerous
|
|
466
385
|
if (!isDangerousPath(pathNode, path)) {
|
|
467
386
|
return;
|
|
468
387
|
}
|
|
@@ -510,4 +429,3 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
510
429
|
};
|
|
511
430
|
},
|
|
512
431
|
});
|
|
513
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.detectSuspiciousDependencies = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Detect potential typosquatting in dependencies
|
|
11
|
-
* @see https://owasp.org/www-project-mobile-top-10/
|
|
12
|
-
* @see https://cwe.mitre.org/data/definitions/506.html
|
|
13
|
-
*/
|
|
14
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
5
|
exports.detectSuspiciousDependencies = (0, eslint_devkit_1.createRule)({
|
|
16
6
|
name: 'detect-suspicious-dependencies',
|
|
@@ -38,7 +28,6 @@ exports.detectSuspiciousDependencies = (0, eslint_devkit_1.createRule)({
|
|
|
38
28
|
defaultOptions: [],
|
|
39
29
|
create(context) {
|
|
40
30
|
const popularPackages = ['react', 'lodash', 'express', 'axios', 'webpack'];
|
|
41
|
-
// oxlint-disable-next-line consistent-function-scoping
|
|
42
31
|
function levenshtein(a, b) {
|
|
43
32
|
const matrix = [];
|
|
44
33
|
for (let i = 0; i <= b.length; i++) {
|
|
@@ -78,4 +67,3 @@ exports.detectSuspiciousDependencies = (0, eslint_devkit_1.createRule)({
|
|
|
78
67
|
};
|
|
79
68
|
},
|
|
80
69
|
});
|
|
81
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,16 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.lockFile = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Ensure package lock file exists
|
|
11
|
-
* @see https://owasp.org/www-project-mobile-top-10/
|
|
12
|
-
* @see https://cwe.mitre.org/data/definitions/829.html
|
|
13
|
-
*/
|
|
14
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
15
5
|
exports.lockFile = (0, eslint_devkit_1.createRule)({
|
|
16
6
|
name: 'lock-file',
|
|
@@ -50,7 +40,6 @@ exports.lockFile = (0, eslint_devkit_1.createRule)({
|
|
|
50
40
|
create(context) {
|
|
51
41
|
const fs = require('node:fs');
|
|
52
42
|
const path = require('node:path');
|
|
53
|
-
// Check once per file
|
|
54
43
|
let checked = false;
|
|
55
44
|
const options = context.options[0] || {};
|
|
56
45
|
const userPackageManager = options.packageManager;
|
|
@@ -59,10 +48,6 @@ exports.lockFile = (0, eslint_devkit_1.createRule)({
|
|
|
59
48
|
yarn: 'yarn.lock',
|
|
60
49
|
pnpm: 'pnpm-lock.yaml',
|
|
61
50
|
};
|
|
62
|
-
// When no packageManager is configured, accept ANY of the three common
|
|
63
|
-
// lock files. Otherwise look only for the configured one. This avoids
|
|
64
|
-
// firing on every file in a pnpm/yarn monorepo just because the rule
|
|
65
|
-
// defaulted to looking for package-lock.json.
|
|
66
51
|
const targetLockFiles = userPackageManager
|
|
67
52
|
? [lockFiles[userPackageManager]]
|
|
68
53
|
: Object.values(lockFiles);
|
|
@@ -75,10 +60,8 @@ exports.lockFile = (0, eslint_devkit_1.createRule)({
|
|
|
75
60
|
if (checked)
|
|
76
61
|
return;
|
|
77
62
|
checked = true;
|
|
78
|
-
// Find project root (simplified)
|
|
79
63
|
let dir = path.dirname(context.filename);
|
|
80
64
|
let found = false;
|
|
81
|
-
// Search up to 10 levels for any acceptable lock file
|
|
82
65
|
for (let i = 0; i < 10; i++) {
|
|
83
66
|
for (const lf of targetLockFiles) {
|
|
84
67
|
if (fs.existsSync(path.join(dir, lf))) {
|
|
@@ -107,4 +90,3 @@ exports.lockFile = (0, eslint_devkit_1.createRule)({
|
|
|
107
90
|
};
|
|
108
91
|
},
|
|
109
92
|
});
|
|
110
|
-
//# sourceMappingURL=index.js.map
|
|
@@ -1,21 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Copyright (c) 2025 Ofri Peretz
|
|
4
|
-
* Licensed under the MIT License. Use of this source code is governed by the
|
|
5
|
-
* MIT license that can be found in the LICENSE file.
|
|
6
|
-
*/
|
|
7
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
3
|
exports.noArbitraryFileAccess = void 0;
|
|
9
|
-
/**
|
|
10
|
-
* @fileoverview Prevent file access from user input
|
|
11
|
-
*
|
|
12
|
-
* False Positive Reduction:
|
|
13
|
-
* This rule detects safe patterns including:
|
|
14
|
-
* - path.basename() sanitization
|
|
15
|
-
* - path.join() with validated base directories
|
|
16
|
-
* - startsWith() validation guards
|
|
17
|
-
* - Early-return throw patterns
|
|
18
|
-
*/
|
|
19
4
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
20
5
|
exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
21
6
|
name: 'no-arbitrary-file-access',
|
|
@@ -49,20 +34,14 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
|
49
34
|
const fsReadMethods = ['readFile', 'readFileSync', 'readdir', 'readdirSync', 'stat', 'statSync'];
|
|
50
35
|
const fsWriteMethods = ['writeFile', 'writeFileSync', 'appendFile', 'appendFileSync'];
|
|
51
36
|
const userInputSources = new Set(['req', 'request', 'params', 'query', 'body']);
|
|
52
|
-
// Track variables that have been sanitized with path.basename()
|
|
53
37
|
const sanitizedVariables = new Set();
|
|
54
|
-
// Track variables that have been validated with startsWith() guards
|
|
55
38
|
const validatedVariables = new Set();
|
|
56
|
-
/**
|
|
57
|
-
* Check if a variable is assigned from path.basename() or path.join() with basename
|
|
58
|
-
*/
|
|
59
39
|
function checkVariableDeclaration(node) {
|
|
60
40
|
if (node.id.type !== 'Identifier' || !node.init) {
|
|
61
41
|
return;
|
|
62
42
|
}
|
|
63
43
|
const varName = node.id.name;
|
|
64
44
|
const init = node.init;
|
|
65
|
-
// Check for path.basename() assignment
|
|
66
45
|
if (init.type === 'CallExpression' &&
|
|
67
46
|
init.callee.type === 'MemberExpression' &&
|
|
68
47
|
init.callee.object.type === 'Identifier' &&
|
|
@@ -71,16 +50,13 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
|
71
50
|
init.callee.property.name === 'basename') {
|
|
72
51
|
sanitizedVariables.add(varName);
|
|
73
52
|
}
|
|
74
|
-
// Check for path.join() with a sanitized variable or literal base
|
|
75
53
|
if (init.type === 'CallExpression' &&
|
|
76
54
|
init.callee.type === 'MemberExpression' &&
|
|
77
55
|
init.callee.object.type === 'Identifier' &&
|
|
78
56
|
init.callee.object.name === 'path' &&
|
|
79
57
|
init.callee.property.type === 'Identifier' &&
|
|
80
58
|
init.callee.property.name === 'join') {
|
|
81
|
-
// Check if any argument is a sanitized variable
|
|
82
59
|
const hasSanitizedArg = init.arguments.some((arg) => arg.type === 'Identifier' && sanitizedVariables.has(arg.name));
|
|
83
|
-
// Check if first arg is a safe base (literal or known safe variable)
|
|
84
60
|
const firstArg = init.arguments[0];
|
|
85
61
|
const hasSafeBase = firstArg && (firstArg.type === 'Literal' ||
|
|
86
62
|
(firstArg.type === 'Identifier' && /^(SAFE|BASE|ROOT|UPLOAD|PUBLIC)/i.test(firstArg.name)));
|
|
@@ -89,32 +65,19 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
|
89
65
|
}
|
|
90
66
|
}
|
|
91
67
|
}
|
|
92
|
-
/**
|
|
93
|
-
* Check if there's a startsWith() guard validation for this variable
|
|
94
|
-
* Looks for patterns like:
|
|
95
|
-
* if (!path.startsWith(baseDir)) { throw ... }
|
|
96
|
-
* if (!path.startsWith(baseDir)) { return ... }
|
|
97
|
-
*/
|
|
98
68
|
function hasStartsWithGuard(node, varName) {
|
|
99
|
-
// Already validated
|
|
100
69
|
if (validatedVariables.has(varName)) {
|
|
101
70
|
return true;
|
|
102
71
|
}
|
|
103
|
-
// Walk up to find the containing block or function
|
|
104
72
|
let current = node.parent;
|
|
105
73
|
while (current) {
|
|
106
|
-
// If we've reached a function body or block, search its statements
|
|
107
74
|
if (current.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement) {
|
|
108
75
|
const statements = current.body;
|
|
109
|
-
// Look for IF statements in this block that validate our variable
|
|
110
76
|
for (const stmt of statements) {
|
|
111
77
|
if (stmt.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
|
|
112
78
|
const testText = sourceCode.getText(stmt.test).toLowerCase();
|
|
113
|
-
// Check for startsWith() validation pattern with our variable
|
|
114
79
|
if (testText.includes('startswith') && testText.includes(varName.toLowerCase())) {
|
|
115
|
-
// Check if this is a guard clause (negated condition with throw/return)
|
|
116
80
|
const consequent = stmt.consequent;
|
|
117
|
-
// Handle block statement: if (...) { throw/return; }
|
|
118
81
|
if (consequent.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement && consequent.body.length > 0) {
|
|
119
82
|
const firstStmt = consequent.body[0];
|
|
120
83
|
if (firstStmt.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement || firstStmt.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement) {
|
|
@@ -122,7 +85,6 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
|
122
85
|
return true;
|
|
123
86
|
}
|
|
124
87
|
}
|
|
125
|
-
// Handle direct statement: if (...) throw/return;
|
|
126
88
|
if (consequent.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement || consequent.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement) {
|
|
127
89
|
validatedVariables.add(varName);
|
|
128
90
|
return true;
|
|
@@ -131,7 +93,6 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
|
131
93
|
}
|
|
132
94
|
}
|
|
133
95
|
}
|
|
134
|
-
// Also check if current IS an if statement (when node is inside the consequent)
|
|
135
96
|
if (current.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
|
|
136
97
|
const testText = sourceCode.getText(current.test).toLowerCase();
|
|
137
98
|
if (testText.includes('startswith') && testText.includes(varName.toLowerCase())) {
|
|
@@ -143,52 +104,40 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
|
143
104
|
}
|
|
144
105
|
return false;
|
|
145
106
|
}
|
|
146
|
-
/**
|
|
147
|
-
* Check if a variable comes from a sanitized/validated source
|
|
148
|
-
*/
|
|
149
107
|
function isVariableSafe(varName, node) {
|
|
150
|
-
// Already tracked as sanitized
|
|
151
108
|
if (sanitizedVariables.has(varName)) {
|
|
152
109
|
return true;
|
|
153
110
|
}
|
|
154
|
-
// Has startsWith guard validation
|
|
155
111
|
if (hasStartsWithGuard(node, varName)) {
|
|
156
112
|
return true;
|
|
157
113
|
}
|
|
158
|
-
// Check naming conventions that suggest safety
|
|
159
114
|
if (/^(safe|sanitized|validated|clean)/i.test(varName)) {
|
|
160
115
|
return true;
|
|
161
116
|
}
|
|
162
117
|
return false;
|
|
163
118
|
}
|
|
164
119
|
return {
|
|
165
|
-
// Track variable declarations for sanitization patterns
|
|
166
120
|
VariableDeclarator(node) {
|
|
167
121
|
checkVariableDeclaration(node);
|
|
168
122
|
},
|
|
169
123
|
CallExpression(node) {
|
|
170
|
-
// Detect fs.* with user input
|
|
171
124
|
if (node.callee.type === 'MemberExpression' &&
|
|
172
125
|
node.callee.object.type === 'Identifier' &&
|
|
173
126
|
node.callee.object.name === 'fs' &&
|
|
174
127
|
node.callee.property.type === 'Identifier' &&
|
|
175
128
|
[...fsReadMethods, ...fsWriteMethods].includes(node.callee.property.name)) {
|
|
176
129
|
const pathArg = node.arguments[0];
|
|
177
|
-
// Skip if path is a literal (safe)
|
|
178
130
|
if (pathArg && pathArg.type === 'Literal') {
|
|
179
131
|
return;
|
|
180
132
|
}
|
|
181
|
-
// Check if path is a variable
|
|
182
133
|
if (pathArg && pathArg.type === 'Identifier') {
|
|
183
134
|
const varName = pathArg.name;
|
|
184
|
-
// Skip if variable is sanitized or validated
|
|
185
135
|
if (isVariableSafe(varName, node)) {
|
|
186
136
|
return;
|
|
187
137
|
}
|
|
188
138
|
report(node);
|
|
189
139
|
return;
|
|
190
140
|
}
|
|
191
|
-
// Flag if path is from a member expression (user input sources)
|
|
192
141
|
if (pathArg?.type === 'MemberExpression' &&
|
|
193
142
|
pathArg.object.type === 'Identifier') {
|
|
194
143
|
const objName = pathArg.object.name.toLowerCase();
|
|
@@ -201,4 +150,3 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
|
|
|
201
150
|
};
|
|
202
151
|
},
|
|
203
152
|
});
|
|
204
|
-
//# sourceMappingURL=index.js.map
|