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.7.3",
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.8.0"
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.7.3',
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': 'error',
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
- if (node.callee.type !== 'MemberExpression' ||
374
- node.callee.object.type !== 'Identifier' ||
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
- CallExpression: checkFsCall
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
  });