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.
|
|
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
|
-
"
|
|
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'
|
|
43
|
-
'detect-eval-with-expression'
|
|
44
|
-
'detect-non-literal-fs-filename'
|
|
45
|
-
'no-unsafe-dynamic-require'
|
|
46
|
-
'no-buffer-overread'
|
|
47
|
-
'no-deprecated-buffer'
|
|
48
|
-
'no-unsafe-buffer-alloc'
|
|
49
|
-
'no-toctou-vulnerability'
|
|
50
|
-
'no-zip-slip'
|
|
51
|
-
'no-arbitrary-file-access'
|
|
52
|
-
'no-data-in-temp-storage'
|
|
53
|
-
'no-ssrf'
|
|
54
|
-
'no-shell-injection'
|
|
55
|
-
'no-dynamic-command-string'
|
|
56
|
-
'no-dynamic-algorithm-selection'
|
|
57
|
-
'detect-suspicious-dependencies'
|
|
58
|
-
'lock-file'
|
|
59
|
-
'no-dynamic-dependency-loading'
|
|
60
|
-
'require-dependency-integrity'
|
|
61
|
-
'require-secure-credential-storage'
|
|
62
|
-
'require-secure-deletion'
|
|
63
|
-
'require-storage-encryption'
|
|
64
|
-
'no-dynamic-require'
|
|
65
|
-
'no-cryptojs'
|
|
66
|
-
'no-cryptojs-weak-random'
|
|
67
|
-
'no-deprecated-cipher-method'
|
|
68
|
-
'no-ecb-mode'
|
|
69
|
-
'no-insecure-key-derivation'
|
|
70
|
-
'no-insecure-rsa-padding'
|
|
71
|
-
'no-math-random-crypto'
|
|
72
|
-
'no-self-signed-certs'
|
|
73
|
-
'no-sha1-hash'
|
|
74
|
-
'no-static-iv'
|
|
75
|
-
'no-timing-unsafe-compare'
|
|
76
|
-
'no-weak-cipher-algorithm'
|
|
77
|
-
'no-weak-hash-algorithm'
|
|
78
|
-
'prefer-native-crypto'
|
|
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.
|
|
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': '
|
|
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
|
-
|
|
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
|
});
|
|
@@ -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,
|