eslint-plugin-node-security 4.7.3 → 4.8.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-node-security",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"description": "ESLint plugin for Node.js security — detects command injection, path traversal, SSRF, zip slip, and weak crypto (MD5/SHA-1, ECB, static IV) in fs, child_process, vm, and crypto.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"main": "./src/index.js",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
"node": ">=18.0.0"
|
|
79
79
|
},
|
|
80
80
|
"dependencies": {
|
|
81
|
-
"@interlace/eslint-devkit": "^1.
|
|
81
|
+
"@interlace/eslint-devkit": "^1.9.0"
|
|
82
82
|
},
|
|
83
83
|
"peerDependencies": {
|
|
84
84
|
"eslint": "^8.0.0 || ^9.0.0 || ^10.0.0"
|
package/src/index.js
CHANGED
|
@@ -44,14 +44,14 @@ exports.rules = {
|
|
|
44
44
|
exports.plugin = {
|
|
45
45
|
meta: {
|
|
46
46
|
name: 'eslint-plugin-node-security',
|
|
47
|
-
version: '4.
|
|
47
|
+
version: '4.8.0',
|
|
48
48
|
},
|
|
49
49
|
rules: exports.rules,
|
|
50
50
|
};
|
|
51
51
|
const recommendedRules = {
|
|
52
52
|
'node-security/detect-child-process': 'error',
|
|
53
53
|
'node-security/detect-eval-with-expression': 'error',
|
|
54
|
-
'node-security/detect-non-literal-fs-filename': '
|
|
54
|
+
'node-security/detect-non-literal-fs-filename': 'warn',
|
|
55
55
|
'node-security/no-unsafe-dynamic-require': 'error',
|
|
56
56
|
'node-security/no-buffer-overread': 'warn',
|
|
57
57
|
'node-security/no-deprecated-buffer': 'error',
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.detectNonLiteralFsFilename = exports.determineRiskLevel = exports.generateRefactoringSteps = void 0;
|
|
3
|
+
exports.detectNonLiteralFsFilename = exports.determineRiskLevel = exports.isFsModule = exports.generateRefactoringSteps = void 0;
|
|
4
|
+
exports.fsMethodName = fsMethodName;
|
|
5
|
+
exports.isFsRequire = isFsRequire;
|
|
4
6
|
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
5
7
|
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
6
8
|
const FS_OPERATIONS = [
|
|
@@ -90,6 +92,39 @@ const generateRefactoringSteps = (operation) => {
|
|
|
90
92
|
}
|
|
91
93
|
};
|
|
92
94
|
exports.generateRefactoringSteps = generateRefactoringSteps;
|
|
95
|
+
const FS_MODULES = new Set(['fs', 'node:fs', 'fs/promises', 'node:fs/promises']);
|
|
96
|
+
const isFsModule = (source) => typeof source === 'string' && FS_MODULES.has(source);
|
|
97
|
+
exports.isFsModule = isFsModule;
|
|
98
|
+
function fsMethodName(callee, namespaces, named) {
|
|
99
|
+
if (callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier)
|
|
100
|
+
return named.get(callee.name);
|
|
101
|
+
if (callee.type !== eslint_devkit_1.AST_NODE_TYPES.MemberExpression ||
|
|
102
|
+
callee.computed ||
|
|
103
|
+
callee.property.type !== eslint_devkit_1.AST_NODE_TYPES.Identifier) {
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
const object = callee.object;
|
|
107
|
+
if (object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && namespaces.has(object.name)) {
|
|
108
|
+
return callee.property.name;
|
|
109
|
+
}
|
|
110
|
+
if (object.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
111
|
+
!object.computed &&
|
|
112
|
+
object.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
113
|
+
namespaces.has(object.object.name) &&
|
|
114
|
+
object.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
115
|
+
object.property.name === 'promises') {
|
|
116
|
+
return callee.property.name;
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
function isFsRequire(node) {
|
|
121
|
+
return (node.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
|
|
122
|
+
node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
123
|
+
node.callee.name === 'require' &&
|
|
124
|
+
node.arguments.length > 0 &&
|
|
125
|
+
node.arguments[0].type === eslint_devkit_1.AST_NODE_TYPES.Literal &&
|
|
126
|
+
(0, exports.isFsModule)(node.arguments[0].value));
|
|
127
|
+
}
|
|
93
128
|
const determineRiskLevel = (operation, pathStr) => {
|
|
94
129
|
if (hasTraversalPatterns(pathStr)) {
|
|
95
130
|
return 'CRITICAL';
|
|
@@ -369,14 +404,20 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
369
404
|
}
|
|
370
405
|
return false;
|
|
371
406
|
};
|
|
407
|
+
const fsNamespaces = new Set(['fs']);
|
|
408
|
+
const fsNamedMethods = new Map();
|
|
409
|
+
const pendingCalls = [];
|
|
410
|
+
function bindFsName(local, imported) {
|
|
411
|
+
if (imported === 'promises')
|
|
412
|
+
fsNamespaces.add(local);
|
|
413
|
+
else
|
|
414
|
+
fsNamedMethods.set(local, imported);
|
|
415
|
+
}
|
|
372
416
|
const checkFsCall = (node) => {
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
node.callee.object.name !== 'fs' ||
|
|
376
|
-
node.callee.property.type !== 'Identifier') {
|
|
417
|
+
const methodName = fsMethodName(node.callee, fsNamespaces, fsNamedMethods);
|
|
418
|
+
if (methodName === undefined) {
|
|
377
419
|
return;
|
|
378
420
|
}
|
|
379
|
-
const methodName = node.callee.property.name;
|
|
380
421
|
if (!dangerousMethods.has(methodName)) {
|
|
381
422
|
return;
|
|
382
423
|
}
|
|
@@ -425,7 +466,51 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
|
|
|
425
466
|
});
|
|
426
467
|
};
|
|
427
468
|
return {
|
|
428
|
-
|
|
469
|
+
ImportDeclaration(node) {
|
|
470
|
+
if (!(0, exports.isFsModule)(node.source.value))
|
|
471
|
+
return;
|
|
472
|
+
for (const spec of node.specifiers) {
|
|
473
|
+
if (spec.type === eslint_devkit_1.AST_NODE_TYPES.ImportSpecifier) {
|
|
474
|
+
const imported = spec.imported.type === eslint_devkit_1.AST_NODE_TYPES.Identifier
|
|
475
|
+
? spec.imported.name
|
|
476
|
+
: spec.imported.value;
|
|
477
|
+
bindFsName(spec.local.name, imported);
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
fsNamespaces.add(spec.local.name);
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
VariableDeclarator(node) {
|
|
484
|
+
if (node.init === null || !isFsRequire(node.init))
|
|
485
|
+
return;
|
|
486
|
+
if (node.id.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
|
|
487
|
+
fsNamespaces.add(node.id.name);
|
|
488
|
+
return;
|
|
489
|
+
}
|
|
490
|
+
if (node.id.type !== eslint_devkit_1.AST_NODE_TYPES.ObjectPattern)
|
|
491
|
+
return;
|
|
492
|
+
for (const prop of node.id.properties) {
|
|
493
|
+
if (prop.type !== eslint_devkit_1.AST_NODE_TYPES.Property || prop.computed)
|
|
494
|
+
continue;
|
|
495
|
+
if (prop.value.type !== eslint_devkit_1.AST_NODE_TYPES.Identifier)
|
|
496
|
+
continue;
|
|
497
|
+
const key = prop.key.type === eslint_devkit_1.AST_NODE_TYPES.Identifier
|
|
498
|
+
? prop.key.name
|
|
499
|
+
: prop.key.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof prop.key.value === 'string'
|
|
500
|
+
? prop.key.value
|
|
501
|
+
: undefined;
|
|
502
|
+
if (key === undefined)
|
|
503
|
+
continue;
|
|
504
|
+
bindFsName(prop.value.name, key);
|
|
505
|
+
}
|
|
506
|
+
},
|
|
507
|
+
CallExpression(node) {
|
|
508
|
+
pendingCalls.push(node);
|
|
509
|
+
},
|
|
510
|
+
'Program:exit'() {
|
|
511
|
+
for (const call of pendingCalls)
|
|
512
|
+
checkFsCall(call);
|
|
513
|
+
},
|
|
429
514
|
};
|
|
430
515
|
},
|
|
431
516
|
});
|