eslint-plugin-node-security 4.7.2 → 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.2",
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,8 +78,7 @@
78
78
  "node": ">=18.0.0"
79
79
  },
80
80
  "dependencies": {
81
- "tslib": "^2.3.0",
82
- "@interlace/eslint-devkit": "^1.6.1"
81
+ "@interlace/eslint-devkit": "^1.9.0"
83
82
  },
84
83
  "peerDependencies": {
85
84
  "eslint": "^8.0.0 || ^9.0.0 || ^10.0.0"
package/src/index.js CHANGED
@@ -1,93 +1,57 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.configs = exports.plugin = exports.rules = void 0;
4
- const detect_child_process_1 = require("./rules/detect-child-process");
5
- const detect_eval_with_expression_1 = require("./rules/detect-eval-with-expression");
6
- const detect_non_literal_fs_filename_1 = require("./rules/detect-non-literal-fs-filename");
7
- const no_unsafe_dynamic_require_1 = require("./rules/no-unsafe-dynamic-require");
8
- const no_buffer_overread_1 = require("./rules/no-buffer-overread");
9
- const no_deprecated_buffer_1 = require("./rules/no-deprecated-buffer");
10
- const no_unsafe_buffer_alloc_1 = require("./rules/no-unsafe-buffer-alloc");
11
- const no_toctou_vulnerability_1 = require("./rules/no-toctou-vulnerability");
12
- const no_zip_slip_1 = require("./rules/no-zip-slip");
13
- const no_arbitrary_file_access_1 = require("./rules/no-arbitrary-file-access");
14
- const no_data_in_temp_storage_1 = require("./rules/no-data-in-temp-storage");
15
- const no_ssrf_1 = require("./rules/no-ssrf");
16
- const no_shell_injection_1 = require("./rules/no-shell-injection");
17
- const no_dynamic_command_string_1 = require("./rules/no-dynamic-command-string");
18
- const no_dynamic_algorithm_selection_1 = require("./rules/no-dynamic-algorithm-selection");
19
- const detect_suspicious_dependencies_1 = require("./rules/detect-suspicious-dependencies");
20
- const lock_file_1 = require("./rules/lock-file");
21
- const no_dynamic_dependency_loading_1 = require("./rules/no-dynamic-dependency-loading");
22
- const require_dependency_integrity_1 = require("./rules/require-dependency-integrity");
23
- const require_secure_credential_storage_1 = require("./rules/require-secure-credential-storage");
24
- const require_secure_deletion_1 = require("./rules/require-secure-deletion");
25
- const require_storage_encryption_1 = require("./rules/require-storage-encryption");
26
- const no_dynamic_require_1 = require("./rules/no-dynamic-require");
27
- const no_cryptojs_1 = require("./rules/no-cryptojs");
28
- const no_cryptojs_weak_random_1 = require("./rules/no-cryptojs-weak-random");
29
- const no_deprecated_cipher_method_1 = require("./rules/no-deprecated-cipher-method");
30
- const no_ecb_mode_1 = require("./rules/no-ecb-mode");
31
- const no_insecure_key_derivation_1 = require("./rules/no-insecure-key-derivation");
32
- const no_math_random_crypto_1 = require("./rules/no-math-random-crypto");
33
- const no_insecure_rsa_padding_1 = require("./rules/no-insecure-rsa-padding");
34
- const no_self_signed_certs_1 = require("./rules/no-self-signed-certs");
35
- const no_sha1_hash_1 = require("./rules/no-sha1-hash");
36
- const no_static_iv_1 = require("./rules/no-static-iv");
37
- const no_timing_unsafe_compare_1 = require("./rules/no-timing-unsafe-compare");
38
- const no_weak_cipher_algorithm_1 = require("./rules/no-weak-cipher-algorithm");
39
- const no_weak_hash_algorithm_1 = require("./rules/no-weak-hash-algorithm");
40
- const prefer_native_crypto_1 = require("./rules/prefer-native-crypto");
41
4
  exports.rules = {
42
- 'detect-child-process': detect_child_process_1.detectChildProcess,
43
- 'detect-eval-with-expression': detect_eval_with_expression_1.detectEvalWithExpression,
44
- 'detect-non-literal-fs-filename': detect_non_literal_fs_filename_1.detectNonLiteralFsFilename,
45
- 'no-unsafe-dynamic-require': no_unsafe_dynamic_require_1.noUnsafeDynamicRequire,
46
- 'no-buffer-overread': no_buffer_overread_1.noBufferOverread,
47
- 'no-deprecated-buffer': no_deprecated_buffer_1.noDeprecatedBuffer,
48
- 'no-unsafe-buffer-alloc': no_unsafe_buffer_alloc_1.noUnsafeBufferAlloc,
49
- 'no-toctou-vulnerability': no_toctou_vulnerability_1.noToctouVulnerability,
50
- 'no-zip-slip': no_zip_slip_1.noZipSlip,
51
- 'no-arbitrary-file-access': no_arbitrary_file_access_1.noArbitraryFileAccess,
52
- 'no-data-in-temp-storage': no_data_in_temp_storage_1.noDataInTempStorage,
53
- 'no-ssrf': no_ssrf_1.noSsrf,
54
- 'no-shell-injection': no_shell_injection_1.noShellInjection,
55
- 'no-dynamic-command-string': no_dynamic_command_string_1.noDynamicCommandString,
56
- 'no-dynamic-algorithm-selection': no_dynamic_algorithm_selection_1.noDynamicAlgorithmSelection,
57
- 'detect-suspicious-dependencies': detect_suspicious_dependencies_1.detectSuspiciousDependencies,
58
- 'lock-file': lock_file_1.lockFile,
59
- 'no-dynamic-dependency-loading': no_dynamic_dependency_loading_1.noDynamicDependencyLoading,
60
- 'require-dependency-integrity': require_dependency_integrity_1.requireDependencyIntegrity,
61
- 'require-secure-credential-storage': require_secure_credential_storage_1.requireSecureCredentialStorage,
62
- 'require-secure-deletion': require_secure_deletion_1.requireSecureDeletion,
63
- 'require-storage-encryption': require_storage_encryption_1.requireStorageEncryption,
64
- 'no-dynamic-require': no_dynamic_require_1.noDynamicRequire,
65
- 'no-cryptojs': no_cryptojs_1.noCryptojs,
66
- 'no-cryptojs-weak-random': no_cryptojs_weak_random_1.noCryptojsWeakRandom,
67
- 'no-deprecated-cipher-method': no_deprecated_cipher_method_1.noDeprecatedCipherMethod,
68
- 'no-ecb-mode': no_ecb_mode_1.noEcbMode,
69
- 'no-insecure-key-derivation': no_insecure_key_derivation_1.noInsecureKeyDerivation,
70
- 'no-insecure-rsa-padding': no_insecure_rsa_padding_1.noInsecureRsaPadding,
71
- 'no-math-random-crypto': no_math_random_crypto_1.noMathRandomCrypto,
72
- 'no-self-signed-certs': no_self_signed_certs_1.noSelfSignedCerts,
73
- 'no-sha1-hash': no_sha1_hash_1.noSha1Hash,
74
- 'no-static-iv': no_static_iv_1.noStaticIv,
75
- 'no-timing-unsafe-compare': no_timing_unsafe_compare_1.noTimingUnsafeCompare,
76
- 'no-weak-cipher-algorithm': no_weak_cipher_algorithm_1.noWeakCipherAlgorithm,
77
- 'no-weak-hash-algorithm': no_weak_hash_algorithm_1.noWeakHashAlgorithm,
78
- 'prefer-native-crypto': prefer_native_crypto_1.preferNativeCrypto,
5
+ get 'detect-child-process'() { return require("./rules/detect-child-process").detectChildProcess; },
6
+ get 'detect-eval-with-expression'() { return require("./rules/detect-eval-with-expression").detectEvalWithExpression; },
7
+ get 'detect-non-literal-fs-filename'() { return require("./rules/detect-non-literal-fs-filename").detectNonLiteralFsFilename; },
8
+ get 'no-unsafe-dynamic-require'() { return require("./rules/no-unsafe-dynamic-require").noUnsafeDynamicRequire; },
9
+ get 'no-buffer-overread'() { return require("./rules/no-buffer-overread").noBufferOverread; },
10
+ get 'no-deprecated-buffer'() { return require("./rules/no-deprecated-buffer").noDeprecatedBuffer; },
11
+ get 'no-unsafe-buffer-alloc'() { return require("./rules/no-unsafe-buffer-alloc").noUnsafeBufferAlloc; },
12
+ get 'no-toctou-vulnerability'() { return require("./rules/no-toctou-vulnerability").noToctouVulnerability; },
13
+ get 'no-zip-slip'() { return require("./rules/no-zip-slip").noZipSlip; },
14
+ get 'no-arbitrary-file-access'() { return require("./rules/no-arbitrary-file-access").noArbitraryFileAccess; },
15
+ get 'no-data-in-temp-storage'() { return require("./rules/no-data-in-temp-storage").noDataInTempStorage; },
16
+ get 'no-ssrf'() { return require("./rules/no-ssrf").noSsrf; },
17
+ get 'no-shell-injection'() { return require("./rules/no-shell-injection").noShellInjection; },
18
+ get 'no-dynamic-command-string'() { return require("./rules/no-dynamic-command-string").noDynamicCommandString; },
19
+ get 'no-dynamic-algorithm-selection'() { return require("./rules/no-dynamic-algorithm-selection").noDynamicAlgorithmSelection; },
20
+ get 'detect-suspicious-dependencies'() { return require("./rules/detect-suspicious-dependencies").detectSuspiciousDependencies; },
21
+ get 'lock-file'() { return require("./rules/lock-file").lockFile; },
22
+ get 'no-dynamic-dependency-loading'() { return require("./rules/no-dynamic-dependency-loading").noDynamicDependencyLoading; },
23
+ get 'require-dependency-integrity'() { return require("./rules/require-dependency-integrity").requireDependencyIntegrity; },
24
+ get 'require-secure-credential-storage'() { return require("./rules/require-secure-credential-storage").requireSecureCredentialStorage; },
25
+ get 'require-secure-deletion'() { return require("./rules/require-secure-deletion").requireSecureDeletion; },
26
+ get 'require-storage-encryption'() { return require("./rules/require-storage-encryption").requireStorageEncryption; },
27
+ get 'no-dynamic-require'() { return require("./rules/no-dynamic-require").noDynamicRequire; },
28
+ get 'no-cryptojs'() { return require("./rules/no-cryptojs").noCryptojs; },
29
+ get 'no-cryptojs-weak-random'() { return require("./rules/no-cryptojs-weak-random").noCryptojsWeakRandom; },
30
+ get 'no-deprecated-cipher-method'() { return require("./rules/no-deprecated-cipher-method").noDeprecatedCipherMethod; },
31
+ get 'no-ecb-mode'() { return require("./rules/no-ecb-mode").noEcbMode; },
32
+ get 'no-insecure-key-derivation'() { return require("./rules/no-insecure-key-derivation").noInsecureKeyDerivation; },
33
+ get 'no-insecure-rsa-padding'() { return require("./rules/no-insecure-rsa-padding").noInsecureRsaPadding; },
34
+ get 'no-math-random-crypto'() { return require("./rules/no-math-random-crypto").noMathRandomCrypto; },
35
+ get 'no-self-signed-certs'() { return require("./rules/no-self-signed-certs").noSelfSignedCerts; },
36
+ get 'no-sha1-hash'() { return require("./rules/no-sha1-hash").noSha1Hash; },
37
+ get 'no-static-iv'() { return require("./rules/no-static-iv").noStaticIv; },
38
+ get 'no-timing-unsafe-compare'() { return require("./rules/no-timing-unsafe-compare").noTimingUnsafeCompare; },
39
+ get 'no-weak-cipher-algorithm'() { return require("./rules/no-weak-cipher-algorithm").noWeakCipherAlgorithm; },
40
+ get 'no-weak-hash-algorithm'() { return require("./rules/no-weak-hash-algorithm").noWeakHashAlgorithm; },
41
+ get 'prefer-native-crypto'() { return require("./rules/prefer-native-crypto").preferNativeCrypto; },
42
+
79
43
  };
80
44
  exports.plugin = {
81
45
  meta: {
82
46
  name: 'eslint-plugin-node-security',
83
- version: '4.7.2',
47
+ version: '4.8.0',
84
48
  },
85
49
  rules: exports.rules,
86
50
  };
87
51
  const recommendedRules = {
88
52
  'node-security/detect-child-process': 'error',
89
53
  'node-security/detect-eval-with-expression': 'error',
90
- 'node-security/detect-non-literal-fs-filename': 'error',
54
+ 'node-security/detect-non-literal-fs-filename': 'warn',
91
55
  'node-security/no-unsafe-dynamic-require': 'error',
92
56
  'node-security/no-buffer-overread': 'warn',
93
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
  });
@@ -29,7 +29,7 @@ exports.lockFile = (0, eslint_devkit_1.createRule)({
29
29
  properties: {
30
30
  packageManager: {
31
31
  type: 'string',
32
- enum: ['npm', 'yarn', 'pnpm'],
32
+ enum: ['npm', 'yarn', 'pnpm'], description: 'Package manager whose lock file is required'
33
33
  },
34
34
  },
35
35
  additionalProperties: false,
@@ -132,17 +132,17 @@ exports.noBufferOverread = (0, eslint_devkit_1.createRule)({
132
132
  bufferMethods: {
133
133
  type: 'array',
134
134
  items: { type: 'string' },
135
- default: ['readUInt8', 'readUInt16LE', 'readUInt32LE', 'readInt8', 'readInt16LE', 'readInt32LE', 'writeUInt8', 'writeUInt16LE', 'writeUInt32LE', 'slice', 'copy'],
135
+ default: ['readUInt8', 'readUInt16LE', 'readUInt32LE', 'readInt8', 'readInt16LE', 'readInt32LE', 'writeUInt8', 'writeUInt16LE', 'writeUInt32LE', 'slice', 'copy'], description: 'Buffer read/write methods checked for bounds'
136
136
  },
137
137
  boundsCheckFunctions: {
138
138
  type: 'array',
139
139
  items: { type: 'string' },
140
- default: ['validateIndex', 'checkBounds', 'safeIndex', 'validateBufferIndex'],
140
+ default: ['validateIndex', 'checkBounds', 'safeIndex', 'validateBufferIndex'], description: 'Function names that count as a bounds check'
141
141
  },
142
142
  bufferTypes: {
143
143
  type: 'array',
144
144
  items: { type: 'string' },
145
- default: ['Buffer', 'Uint8Array', 'ArrayBuffer', 'DataView'],
145
+ default: ['Buffer', 'Uint8Array', 'ArrayBuffer', 'DataView'], description: 'Constructor names treated as buffer types'
146
146
  },
147
147
  trustedSanitizers: {
148
148
  type: 'array',
@@ -60,7 +60,7 @@ exports.noToctouVulnerability = (0, eslint_devkit_2.createRule)({
60
60
  fsMethods: {
61
61
  type: 'array',
62
62
  items: { type: 'string' },
63
- default: ['fs.existsSync', 'fs.statSync', 'fs.accessSync'],
63
+ default: ['fs.existsSync', 'fs.statSync', 'fs.accessSync'], description: 'Filesystem check calls that create a time-of-check window'
64
64
  },
65
65
  },
66
66
  additionalProperties: false,