eslint-plugin-node-security 4.0.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/CHANGELOG.md +83 -0
- package/README.md +50 -0
- package/package.json +79 -0
- package/src/index.d.ts +10 -0
- package/src/index.js +118 -0
- package/src/index.js.map +1 -0
- package/src/rules/detect-child-process/index.d.ts +30 -0
- package/src/rules/detect-child-process/index.js +535 -0
- package/src/rules/detect-child-process/index.js.map +1 -0
- package/src/rules/detect-eval-with-expression/index.d.ts +28 -0
- package/src/rules/detect-eval-with-expression/index.js +398 -0
- package/src/rules/detect-eval-with-expression/index.js.map +1 -0
- package/src/rules/detect-non-literal-fs-filename/index.d.ts +26 -0
- package/src/rules/detect-non-literal-fs-filename/index.js +460 -0
- package/src/rules/detect-non-literal-fs-filename/index.js.map +1 -0
- package/src/rules/detect-suspicious-dependencies/index.d.ts +12 -0
- package/src/rules/detect-suspicious-dependencies/index.js +77 -0
- package/src/rules/detect-suspicious-dependencies/index.js.map +1 -0
- package/src/rules/lock-file/index.d.ts +13 -0
- package/src/rules/lock-file/index.js +94 -0
- package/src/rules/lock-file/index.js.map +1 -0
- package/src/rules/no-arbitrary-file-access/index.d.ts +12 -0
- package/src/rules/no-arbitrary-file-access/index.js +201 -0
- package/src/rules/no-arbitrary-file-access/index.js.map +1 -0
- package/src/rules/no-buffer-overread/index.d.ts +39 -0
- package/src/rules/no-buffer-overread/index.js +612 -0
- package/src/rules/no-buffer-overread/index.js.map +1 -0
- package/src/rules/no-cryptojs/index.d.ts +24 -0
- package/src/rules/no-cryptojs/index.js +104 -0
- package/src/rules/no-cryptojs/index.js.map +1 -0
- package/src/rules/no-cryptojs-weak-random/index.d.ts +24 -0
- package/src/rules/no-cryptojs-weak-random/index.js +112 -0
- package/src/rules/no-cryptojs-weak-random/index.js.map +1 -0
- package/src/rules/no-data-in-temp-storage/index.d.ts +14 -0
- package/src/rules/no-data-in-temp-storage/index.js +99 -0
- package/src/rules/no-data-in-temp-storage/index.js.map +1 -0
- package/src/rules/no-deprecated-cipher-method/index.d.ts +23 -0
- package/src/rules/no-deprecated-cipher-method/index.js +118 -0
- package/src/rules/no-deprecated-cipher-method/index.js.map +1 -0
- package/src/rules/no-dynamic-dependency-loading/index.d.ts +12 -0
- package/src/rules/no-dynamic-dependency-loading/index.js +55 -0
- package/src/rules/no-dynamic-dependency-loading/index.js.map +1 -0
- package/src/rules/no-dynamic-require/index.d.ts +21 -0
- package/src/rules/no-dynamic-require/index.js +122 -0
- package/src/rules/no-dynamic-require/index.js.map +1 -0
- package/src/rules/no-ecb-mode/index.d.ts +23 -0
- package/src/rules/no-ecb-mode/index.js +113 -0
- package/src/rules/no-ecb-mode/index.js.map +1 -0
- package/src/rules/no-insecure-key-derivation/index.d.ts +24 -0
- package/src/rules/no-insecure-key-derivation/index.js +116 -0
- package/src/rules/no-insecure-key-derivation/index.js.map +1 -0
- package/src/rules/no-insecure-rsa-padding/index.d.ts +24 -0
- package/src/rules/no-insecure-rsa-padding/index.js +110 -0
- package/src/rules/no-insecure-rsa-padding/index.js.map +1 -0
- package/src/rules/no-pii-in-logs/index.d.ts +12 -0
- package/src/rules/no-pii-in-logs/index.js +74 -0
- package/src/rules/no-pii-in-logs/index.js.map +1 -0
- package/src/rules/no-self-signed-certs/index.d.ts +23 -0
- package/src/rules/no-self-signed-certs/index.js +116 -0
- package/src/rules/no-self-signed-certs/index.js.map +1 -0
- package/src/rules/no-sha1-hash/index.d.ts +24 -0
- package/src/rules/no-sha1-hash/index.js +128 -0
- package/src/rules/no-sha1-hash/index.js.map +1 -0
- package/src/rules/no-static-iv/index.d.ts +23 -0
- package/src/rules/no-static-iv/index.js +147 -0
- package/src/rules/no-static-iv/index.js.map +1 -0
- package/src/rules/no-timing-unsafe-compare/index.d.ts +23 -0
- package/src/rules/no-timing-unsafe-compare/index.js +114 -0
- package/src/rules/no-timing-unsafe-compare/index.js.map +1 -0
- package/src/rules/no-toctou-vulnerability/index.d.ts +26 -0
- package/src/rules/no-toctou-vulnerability/index.js +214 -0
- package/src/rules/no-toctou-vulnerability/index.js.map +1 -0
- package/src/rules/no-unsafe-dynamic-require/index.d.ts +19 -0
- package/src/rules/no-unsafe-dynamic-require/index.js +112 -0
- package/src/rules/no-unsafe-dynamic-require/index.js.map +1 -0
- package/src/rules/no-weak-cipher-algorithm/index.d.ts +25 -0
- package/src/rules/no-weak-cipher-algorithm/index.js +190 -0
- package/src/rules/no-weak-cipher-algorithm/index.js.map +1 -0
- package/src/rules/no-weak-hash-algorithm/index.d.ts +25 -0
- package/src/rules/no-weak-hash-algorithm/index.js +218 -0
- package/src/rules/no-weak-hash-algorithm/index.js.map +1 -0
- package/src/rules/no-zip-slip/index.d.ts +35 -0
- package/src/rules/no-zip-slip/index.js +451 -0
- package/src/rules/no-zip-slip/index.js.map +1 -0
- package/src/rules/prefer-native-crypto/index.d.ts +23 -0
- package/src/rules/prefer-native-crypto/index.js +124 -0
- package/src/rules/prefer-native-crypto/index.js.map +1 -0
- package/src/rules/require-dependency-integrity/index.d.ts +12 -0
- package/src/rules/require-dependency-integrity/index.js +70 -0
- package/src/rules/require-dependency-integrity/index.js.map +1 -0
- package/src/rules/require-secure-credential-storage/index.d.ts +12 -0
- package/src/rules/require-secure-credential-storage/index.js +54 -0
- package/src/rules/require-secure-credential-storage/index.js.map +1 -0
- package/src/rules/require-secure-deletion/index.d.ts +12 -0
- package/src/rules/require-secure-deletion/index.js +46 -0
- package/src/rules/require-secure-deletion/index.js.map +1 -0
- package/src/rules/require-storage-encryption/index.d.ts +12 -0
- package/src/rules/require-storage-encryption/index.js +54 -0
- package/src/rules/require-storage-encryption/index.js.map +1 -0
- package/src/types/index.d.ts +24 -0
- package/src/types/index.js +8 -0
- package/src/types/index.js.map +1 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ESLint Rule: no-toctou-vulnerability
|
|
8
|
+
* Detects Time-of-Check-Time-of-Use vulnerabilities
|
|
9
|
+
* CWE-367: Time-of-check Time-of-use (TOCTOU) Race Condition
|
|
10
|
+
*
|
|
11
|
+
* @see https://cwe.mitre.org/data/definitions/367.html
|
|
12
|
+
* @see https://owasp.org/www-community/vulnerabilities/TOCTOU_Race_Condition
|
|
13
|
+
*/
|
|
14
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
15
|
+
type MessageIds = 'toctouVulnerability' | 'useAtomicOperations' | 'useFsPromises' | 'addProperLocking';
|
|
16
|
+
export interface Options {
|
|
17
|
+
/** Ignore in test files. Default: true */
|
|
18
|
+
ignoreInTests?: boolean;
|
|
19
|
+
/** File system methods to check. Default: ['fs.existsSync', 'fs.statSync', 'fs.accessSync'] */
|
|
20
|
+
fsMethods?: string[];
|
|
21
|
+
}
|
|
22
|
+
type RuleOptions = [Options?];
|
|
23
|
+
export declare const noToctouVulnerability: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
24
|
+
name: string;
|
|
25
|
+
};
|
|
26
|
+
export {};
|
|
@@ -0,0 +1,214 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.noToctouVulnerability = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
|
+
exports.noToctouVulnerability = (0, eslint_devkit_2.createRule)({
|
|
12
|
+
name: 'no-toctou-vulnerability',
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Detects Time-of-Check-Time-of-Use vulnerabilities',
|
|
17
|
+
},
|
|
18
|
+
hasSuggestions: true,
|
|
19
|
+
messages: {
|
|
20
|
+
toctouVulnerability: (0, eslint_devkit_1.formatLLMMessage)({
|
|
21
|
+
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
22
|
+
issueName: 'TOCTOU vulnerability',
|
|
23
|
+
cwe: 'CWE-367',
|
|
24
|
+
description: 'Time-of-check Time-of-use race condition detected',
|
|
25
|
+
severity: 'HIGH',
|
|
26
|
+
fix: 'Use atomic operations or fs.promises for file operations',
|
|
27
|
+
documentationLink: 'https://cwe.mitre.org/data/definitions/367.html',
|
|
28
|
+
}),
|
|
29
|
+
useAtomicOperations: (0, eslint_devkit_1.formatLLMMessage)({
|
|
30
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
31
|
+
issueName: 'Use Atomic Operations',
|
|
32
|
+
description: 'Use atomic file operations',
|
|
33
|
+
severity: 'LOW',
|
|
34
|
+
fix: 'fs.promises.access() then fs.promises.readFile()',
|
|
35
|
+
documentationLink: 'https://nodejs.org/api/fs.html#fspromisesaccesspath-mode',
|
|
36
|
+
}),
|
|
37
|
+
useFsPromises: (0, eslint_devkit_1.formatLLMMessage)({
|
|
38
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
39
|
+
issueName: 'Use fs.promises',
|
|
40
|
+
description: 'Use fs.promises API',
|
|
41
|
+
severity: 'LOW',
|
|
42
|
+
fix: 'await fs.promises.readFile() instead of sync operations',
|
|
43
|
+
documentationLink: 'https://nodejs.org/api/fs.html#promises-api',
|
|
44
|
+
}),
|
|
45
|
+
addProperLocking: (0, eslint_devkit_1.formatLLMMessage)({
|
|
46
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
47
|
+
issueName: 'Add File Locking',
|
|
48
|
+
description: 'Add proper locking mechanism',
|
|
49
|
+
severity: 'LOW',
|
|
50
|
+
fix: 'Use proper-lockfile or similar for concurrent access',
|
|
51
|
+
documentationLink: 'https://github.com/moxystudio/node-proper-lockfile',
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
schema: [
|
|
55
|
+
{
|
|
56
|
+
type: 'object',
|
|
57
|
+
properties: {
|
|
58
|
+
ignoreInTests: {
|
|
59
|
+
type: 'boolean',
|
|
60
|
+
default: true,
|
|
61
|
+
},
|
|
62
|
+
fsMethods: {
|
|
63
|
+
type: 'array',
|
|
64
|
+
items: { type: 'string' },
|
|
65
|
+
default: ['fs.existsSync', 'fs.statSync', 'fs.accessSync'],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
additionalProperties: false,
|
|
69
|
+
},
|
|
70
|
+
],
|
|
71
|
+
},
|
|
72
|
+
defaultOptions: [
|
|
73
|
+
{
|
|
74
|
+
ignoreInTests: true,
|
|
75
|
+
fsMethods: ['fs.existsSync', 'fs.statSync', 'fs.accessSync'],
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
create(context, [options = {}]) {
|
|
79
|
+
const { ignoreInTests = true } = options || {};
|
|
80
|
+
const filename = context.getFilename();
|
|
81
|
+
const isTestFile = ignoreInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
82
|
+
if (isTestFile) {
|
|
83
|
+
return {};
|
|
84
|
+
}
|
|
85
|
+
const sourceCode = context.sourceCode || context.sourceCode;
|
|
86
|
+
/**
|
|
87
|
+
* Check for TOCTOU patterns
|
|
88
|
+
*/
|
|
89
|
+
function checkCallExpression(node) {
|
|
90
|
+
// 1. Identify the file operation (Use)
|
|
91
|
+
let useMethodName = '';
|
|
92
|
+
if (node.callee.type === 'MemberExpression' && node.callee.property.type === 'Identifier') {
|
|
93
|
+
const objectName = node.callee.object.type === 'Identifier' ? node.callee.object.name : '';
|
|
94
|
+
if (objectName === 'fs' || objectName === 'fsPromises') {
|
|
95
|
+
useMethodName = node.callee.property.name;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
else if (node.callee.type === 'Identifier') {
|
|
99
|
+
useMethodName = node.callee.name;
|
|
100
|
+
}
|
|
101
|
+
const riskyUseMethods = ['readFileSync', 'writeFileSync', 'readFile', 'writeFile', 'openSync', 'open', 'unlinkSync', 'unlink'];
|
|
102
|
+
if (!riskyUseMethods.includes(useMethodName)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
const useArg = node.arguments[0];
|
|
106
|
+
if (!useArg)
|
|
107
|
+
return;
|
|
108
|
+
// 2. Walk up to find the condition (Check)
|
|
109
|
+
let current = node.parent;
|
|
110
|
+
while (current) {
|
|
111
|
+
if (current.type === 'IfStatement') {
|
|
112
|
+
// Extract the condition node
|
|
113
|
+
let condition = current.test;
|
|
114
|
+
// Handle negated condition: if (!exists(path)) { create(path) } -> also TOCTOU but different logic?
|
|
115
|
+
// Actually TOCTOU is usually Check(exists) -> Use(read).
|
|
116
|
+
// If (!exists) -> create is Check -> Use.
|
|
117
|
+
// But strict TOCTOU is checking state then acting.
|
|
118
|
+
// If checking for negation
|
|
119
|
+
if (condition.type === 'UnaryExpression' && condition.operator === '!') {
|
|
120
|
+
condition = condition.argument;
|
|
121
|
+
}
|
|
122
|
+
if (condition.type === 'CallExpression') {
|
|
123
|
+
// Check if it's a file check method
|
|
124
|
+
let checkMethodName = '';
|
|
125
|
+
if (condition.callee.type === 'MemberExpression' && condition.callee.property.type === 'Identifier') {
|
|
126
|
+
checkMethodName = condition.callee.property.name;
|
|
127
|
+
}
|
|
128
|
+
else if (condition.callee.type === 'Identifier') {
|
|
129
|
+
checkMethodName = condition.callee.name;
|
|
130
|
+
}
|
|
131
|
+
const checkMethods = ['existsSync', 'statSync', 'accessSync', 'exists', 'stat', 'access'];
|
|
132
|
+
if (checkMethods.includes(checkMethodName)) {
|
|
133
|
+
// Compare arguments
|
|
134
|
+
const checkArg = condition.arguments[0];
|
|
135
|
+
if (checkArg) {
|
|
136
|
+
// Method 1: Identifier match (same variable)
|
|
137
|
+
if (checkArg.type === 'Identifier' && useArg.type === 'Identifier' && checkArg.name === useArg.name) {
|
|
138
|
+
reportToctou(node);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Method 2: Text match (fallback)
|
|
142
|
+
const checkArgText = sourceCode.getText(checkArg).replace(/\s/g, '');
|
|
143
|
+
const useArgText = sourceCode.getText(useArg).replace(/\s/g, '');
|
|
144
|
+
if (checkArgText === useArgText) {
|
|
145
|
+
reportToctou(node);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
// Handle stats.isFile() / stats.isDirectory() pattern
|
|
151
|
+
if (condition.callee.type === 'MemberExpression' &&
|
|
152
|
+
condition.callee.property.type === 'Identifier' &&
|
|
153
|
+
['isFile', 'isDirectory'].includes(condition.callee.property.name) &&
|
|
154
|
+
condition.callee.object.type === 'Identifier') {
|
|
155
|
+
const statsVarName = condition.callee.object.name;
|
|
156
|
+
let currentScope = sourceCode.getScope(condition);
|
|
157
|
+
let variable = null;
|
|
158
|
+
while (currentScope) {
|
|
159
|
+
variable = currentScope.variables.find(v => v.name === statsVarName) || null;
|
|
160
|
+
if (variable)
|
|
161
|
+
break;
|
|
162
|
+
currentScope = currentScope.upper;
|
|
163
|
+
}
|
|
164
|
+
if (variable && variable.defs.length > 0) {
|
|
165
|
+
const def = variable.defs[0];
|
|
166
|
+
if (def.type === 'Variable' && def.node.init && def.node.init.type === 'CallExpression') {
|
|
167
|
+
const init = def.node.init;
|
|
168
|
+
if (init.callee.type === 'MemberExpression' &&
|
|
169
|
+
init.callee.property.type === 'Identifier' &&
|
|
170
|
+
['statSync', 'lstatSync', 'stat', 'lstat'].includes(init.callee.property.name)) {
|
|
171
|
+
const statArg = init.arguments[0];
|
|
172
|
+
if (statArg) {
|
|
173
|
+
const checkArgText = sourceCode.getText(statArg).replace(/\s/g, '');
|
|
174
|
+
const useArgText = sourceCode.getText(useArg).replace(/\s/g, '');
|
|
175
|
+
if (checkArgText === useArgText) {
|
|
176
|
+
reportToctou(node);
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
current = current.parent;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function reportToctou(node) {
|
|
190
|
+
context.report({
|
|
191
|
+
node,
|
|
192
|
+
messageId: 'toctouVulnerability',
|
|
193
|
+
suggest: [
|
|
194
|
+
{
|
|
195
|
+
messageId: 'useAtomicOperations',
|
|
196
|
+
fix: () => null,
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
messageId: 'useFsPromises',
|
|
200
|
+
fix: () => null,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
messageId: 'addProperLocking',
|
|
204
|
+
fix: () => null,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
return {
|
|
210
|
+
CallExpression: checkCallExpression,
|
|
211
|
+
};
|
|
212
|
+
},
|
|
213
|
+
});
|
|
214
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-toctou-vulnerability/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ESLint Rule: no-unsafe-dynamic-require
|
|
8
|
+
* Detects dynamic require() calls that could lead to code injection
|
|
9
|
+
*/
|
|
10
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
11
|
+
export interface Options {
|
|
12
|
+
/** Allow dynamic import() expressions. Default: false (stricter) */
|
|
13
|
+
allowDynamicImport?: boolean;
|
|
14
|
+
}
|
|
15
|
+
type RuleOptions = [Options?];
|
|
16
|
+
export declare const noUnsafeDynamicRequire: TSESLint.RuleModule<"unsafeDynamicRequire", RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
17
|
+
name: string;
|
|
18
|
+
};
|
|
19
|
+
export {};
|
|
@@ -0,0 +1,112 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.noUnsafeDynamicRequire = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
const eslint_devkit_2 = require("@interlace/eslint-devkit");
|
|
11
|
+
exports.noUnsafeDynamicRequire = (0, eslint_devkit_2.createRule)({
|
|
12
|
+
name: 'no-unsafe-dynamic-require',
|
|
13
|
+
meta: {
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Prevent unsafe dynamic require() calls that could enable code injection',
|
|
17
|
+
},
|
|
18
|
+
fixable: 'code',
|
|
19
|
+
hasSuggestions: false,
|
|
20
|
+
messages: {
|
|
21
|
+
unsafeDynamicRequire: (0, eslint_devkit_1.formatLLMMessage)({
|
|
22
|
+
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
23
|
+
issueName: 'Dynamic require()',
|
|
24
|
+
cwe: 'CWE-95',
|
|
25
|
+
description: 'Dynamic require() detected',
|
|
26
|
+
severity: 'CRITICAL',
|
|
27
|
+
fix: 'Use allowlist: const ALLOWED = ["mod1", "mod2"]; if (!ALLOWED.includes(name)) throw Error("Not allowed")',
|
|
28
|
+
documentationLink: 'https://owasp.org/www-community/attacks/Code_Injection',
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
schema: [
|
|
32
|
+
{
|
|
33
|
+
type: 'object',
|
|
34
|
+
properties: {
|
|
35
|
+
allowDynamicImport: {
|
|
36
|
+
type: 'boolean',
|
|
37
|
+
default: false,
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
additionalProperties: false,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
defaultOptions: [
|
|
45
|
+
{
|
|
46
|
+
allowDynamicImport: false,
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
create(context) {
|
|
50
|
+
/**
|
|
51
|
+
* Track variables that reference require
|
|
52
|
+
* Maps variable name to the node where it was assigned
|
|
53
|
+
*/
|
|
54
|
+
const requireVariables = new Set();
|
|
55
|
+
/**
|
|
56
|
+
* Check if a node is a reference to require
|
|
57
|
+
*/
|
|
58
|
+
const isRequireReference = (node) => {
|
|
59
|
+
if (node.type === 'Identifier' && node.name === 'require') {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
if (node.type === 'Identifier' && requireVariables.has(node.name)) {
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
return false;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Check if argument is dynamic (not a literal)
|
|
69
|
+
*/
|
|
70
|
+
const isDynamicArgument = (arg) => {
|
|
71
|
+
if (arg.type === 'Literal')
|
|
72
|
+
return false;
|
|
73
|
+
if (arg.type === 'TemplateLiteral' && arg.expressions.length === 0)
|
|
74
|
+
return false;
|
|
75
|
+
return true;
|
|
76
|
+
};
|
|
77
|
+
return {
|
|
78
|
+
VariableDeclarator(node) {
|
|
79
|
+
// Track when require is assigned to a variable
|
|
80
|
+
if (node.id.type === 'Identifier' && node.init) {
|
|
81
|
+
if (node.init.type === 'Identifier' && node.init.name === 'require') {
|
|
82
|
+
requireVariables.add(node.id.name);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
CallExpression(node) {
|
|
87
|
+
// Check for require() calls (direct or via variable)
|
|
88
|
+
if (node.callee.type !== 'Identifier') {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
// Check if callee is require or a variable that references require
|
|
92
|
+
if (!isRequireReference(node.callee)) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
// Must have at least one argument
|
|
96
|
+
if (node.arguments.length === 0)
|
|
97
|
+
return;
|
|
98
|
+
const firstArg = node.arguments[0];
|
|
99
|
+
if (firstArg.type === 'SpreadElement')
|
|
100
|
+
return;
|
|
101
|
+
// Check if dynamic
|
|
102
|
+
if (!isDynamicArgument(firstArg))
|
|
103
|
+
return;
|
|
104
|
+
context.report({
|
|
105
|
+
node,
|
|
106
|
+
messageId: 'unsafeDynamicRequire',
|
|
107
|
+
});
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
});
|
|
112
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-unsafe-dynamic-require/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ESLint Rule: no-weak-cipher-algorithm
|
|
8
|
+
* Detects use of weak cipher algorithms (DES, 3DES, RC4, Blowfish)
|
|
9
|
+
* CWE-327: Use of a Broken or Risky Cryptographic Algorithm
|
|
10
|
+
*
|
|
11
|
+
* @see https://cwe.mitre.org/data/definitions/327.html
|
|
12
|
+
*/
|
|
13
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
14
|
+
type MessageIds = 'weakCipherAlgorithm' | 'useAes256Gcm' | 'useChaCha20';
|
|
15
|
+
export interface Options {
|
|
16
|
+
/** Additional weak ciphers to detect. Default: [] */
|
|
17
|
+
additionalWeakCiphers?: string[];
|
|
18
|
+
/** Allow weak ciphers in test files. Default: false */
|
|
19
|
+
allowInTests?: boolean;
|
|
20
|
+
}
|
|
21
|
+
type RuleOptions = [Options?];
|
|
22
|
+
export declare const noWeakCipherAlgorithm: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
export type { Options as NoWeakCipherAlgorithmOptions };
|
|
@@ -0,0 +1,190 @@
|
|
|
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
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.noWeakCipherAlgorithm = void 0;
|
|
9
|
+
const eslint_devkit_1 = require("@interlace/eslint-devkit");
|
|
10
|
+
const WEAK_CIPHER_PATTERNS = [
|
|
11
|
+
{
|
|
12
|
+
pattern: /\bdes\b(?!-ede)/i,
|
|
13
|
+
name: 'DES',
|
|
14
|
+
alternatives: ['AES-256-GCM', 'ChaCha20-Poly1305'],
|
|
15
|
+
replacement: 'aes-256-gcm',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
pattern: /\bdes-ede3?\b|\b3des\b|\btripledes\b/i,
|
|
19
|
+
name: '3DES',
|
|
20
|
+
alternatives: ['AES-256-GCM', 'ChaCha20-Poly1305'],
|
|
21
|
+
replacement: 'aes-256-gcm',
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
pattern: /\brc4\b|\barc4\b/i,
|
|
25
|
+
name: 'RC4',
|
|
26
|
+
alternatives: ['AES-256-GCM', 'ChaCha20-Poly1305'],
|
|
27
|
+
replacement: 'aes-256-gcm',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
pattern: /\bblowfish\b|\bbf\b/i,
|
|
31
|
+
name: 'Blowfish',
|
|
32
|
+
alternatives: ['AES-256-GCM', 'ChaCha20-Poly1305'],
|
|
33
|
+
replacement: 'aes-256-gcm',
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
pattern: /\brc2\b/i,
|
|
37
|
+
name: 'RC2',
|
|
38
|
+
alternatives: ['AES-256-GCM', 'ChaCha20-Poly1305'],
|
|
39
|
+
replacement: 'aes-256-gcm',
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
pattern: /\bidea\b/i,
|
|
43
|
+
name: 'IDEA',
|
|
44
|
+
alternatives: ['AES-256-GCM', 'ChaCha20-Poly1305'],
|
|
45
|
+
replacement: 'aes-256-gcm',
|
|
46
|
+
},
|
|
47
|
+
];
|
|
48
|
+
/**
|
|
49
|
+
* Check if a string contains a weak cipher algorithm
|
|
50
|
+
*/
|
|
51
|
+
function findWeakCipher(value, additionalPatterns) {
|
|
52
|
+
// Check standard patterns
|
|
53
|
+
for (const pattern of WEAK_CIPHER_PATTERNS) {
|
|
54
|
+
if (pattern.pattern.test(value)) {
|
|
55
|
+
return pattern;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
// Check additional patterns
|
|
59
|
+
for (const additionalPattern of additionalPatterns) {
|
|
60
|
+
const regex = new RegExp(`\\b${additionalPattern}\\b`, 'i');
|
|
61
|
+
if (regex.test(value)) {
|
|
62
|
+
return {
|
|
63
|
+
pattern: regex,
|
|
64
|
+
name: additionalPattern.toUpperCase(),
|
|
65
|
+
alternatives: ['AES-256-GCM', 'ChaCha20-Poly1305'],
|
|
66
|
+
replacement: 'aes-256-gcm',
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
exports.noWeakCipherAlgorithm = (0, eslint_devkit_1.createRule)({
|
|
73
|
+
name: 'no-weak-cipher-algorithm',
|
|
74
|
+
meta: {
|
|
75
|
+
type: 'problem',
|
|
76
|
+
docs: {
|
|
77
|
+
description: 'Disallow weak cipher algorithms (DES, 3DES, RC4, Blowfish)',
|
|
78
|
+
},
|
|
79
|
+
hasSuggestions: true,
|
|
80
|
+
messages: {
|
|
81
|
+
weakCipherAlgorithm: (0, eslint_devkit_1.formatLLMMessage)({
|
|
82
|
+
icon: eslint_devkit_1.MessageIcons.SECURITY,
|
|
83
|
+
issueName: 'Weak cipher algorithm',
|
|
84
|
+
cwe: 'CWE-327',
|
|
85
|
+
description: 'Use of weak cipher algorithm: {{algorithm}}. {{algorithm}} has known vulnerabilities and should not be used.',
|
|
86
|
+
severity: 'CRITICAL',
|
|
87
|
+
fix: 'Replace with {{replacement}}: crypto.createCipheriv("{{replacement}}", key, iv)',
|
|
88
|
+
documentationLink: 'https://owasp.org/www-community/vulnerabilities/Weak_Cryptography',
|
|
89
|
+
}),
|
|
90
|
+
useAes256Gcm: (0, eslint_devkit_1.formatLLMMessage)({
|
|
91
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
92
|
+
issueName: 'Use AES-256-GCM',
|
|
93
|
+
description: 'Replace with AES-256-GCM for authenticated encryption',
|
|
94
|
+
severity: 'LOW',
|
|
95
|
+
fix: 'crypto.createCipheriv("aes-256-gcm", key, iv)',
|
|
96
|
+
documentationLink: 'https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options',
|
|
97
|
+
}),
|
|
98
|
+
useChaCha20: (0, eslint_devkit_1.formatLLMMessage)({
|
|
99
|
+
icon: eslint_devkit_1.MessageIcons.INFO,
|
|
100
|
+
issueName: 'Use ChaCha20-Poly1305',
|
|
101
|
+
description: 'Replace with ChaCha20-Poly1305 for modern encryption',
|
|
102
|
+
severity: 'LOW',
|
|
103
|
+
fix: 'crypto.createCipheriv("chacha20-poly1305", key, iv)',
|
|
104
|
+
documentationLink: 'https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options',
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
schema: [
|
|
108
|
+
{
|
|
109
|
+
type: 'object',
|
|
110
|
+
properties: {
|
|
111
|
+
additionalWeakCiphers: {
|
|
112
|
+
type: 'array',
|
|
113
|
+
items: { type: 'string' },
|
|
114
|
+
default: [],
|
|
115
|
+
description: 'Additional weak ciphers to detect',
|
|
116
|
+
},
|
|
117
|
+
allowInTests: {
|
|
118
|
+
type: 'boolean',
|
|
119
|
+
default: false,
|
|
120
|
+
description: 'Allow weak ciphers in test files',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
additionalProperties: false,
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
},
|
|
127
|
+
defaultOptions: [
|
|
128
|
+
{
|
|
129
|
+
additionalWeakCiphers: [],
|
|
130
|
+
allowInTests: false,
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
create(context, [options = {}]) {
|
|
134
|
+
const { additionalWeakCiphers = [], allowInTests = false, } = options;
|
|
135
|
+
const filename = context.filename;
|
|
136
|
+
const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
|
|
137
|
+
/**
|
|
138
|
+
* Check if a call expression uses a weak cipher
|
|
139
|
+
*/
|
|
140
|
+
function checkCallExpression(node) {
|
|
141
|
+
if (isTestFile)
|
|
142
|
+
return;
|
|
143
|
+
const cipherMethods = new Set(['createCipher', 'createCipheriv', 'createDecipher', 'createDecipheriv']);
|
|
144
|
+
// Check for crypto.createCipher*() pattern
|
|
145
|
+
if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
|
|
146
|
+
node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
147
|
+
cipherMethods.has(node.callee.property.name)) {
|
|
148
|
+
checkCipherArgument(node);
|
|
149
|
+
}
|
|
150
|
+
// Check for standalone createCipher*() pattern
|
|
151
|
+
if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
|
|
152
|
+
cipherMethods.has(node.callee.name)) {
|
|
153
|
+
checkCipherArgument(node);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Check the algorithm argument passed to createCipher*
|
|
158
|
+
*/
|
|
159
|
+
function checkCipherArgument(node) {
|
|
160
|
+
const firstArg = node.arguments[0];
|
|
161
|
+
if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof firstArg.value === 'string') {
|
|
162
|
+
const weakPattern = findWeakCipher(firstArg.value, additionalWeakCiphers);
|
|
163
|
+
if (weakPattern) {
|
|
164
|
+
context.report({
|
|
165
|
+
node: firstArg,
|
|
166
|
+
messageId: 'weakCipherAlgorithm',
|
|
167
|
+
data: {
|
|
168
|
+
algorithm: weakPattern.name,
|
|
169
|
+
replacement: weakPattern.replacement,
|
|
170
|
+
},
|
|
171
|
+
suggest: [
|
|
172
|
+
{
|
|
173
|
+
messageId: 'useAes256Gcm',
|
|
174
|
+
fix: (fixer) => fixer.replaceText(firstArg, `"aes-256-gcm"`),
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
messageId: 'useChaCha20',
|
|
178
|
+
fix: (fixer) => fixer.replaceText(firstArg, `"chacha20-poly1305"`),
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return {
|
|
186
|
+
CallExpression: checkCallExpression,
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/eslint-plugin-node-security/src/rules/no-weak-cipher-algorithm/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2025 Ofri Peretz
|
|
3
|
+
* Licensed under the MIT License. Use of this source code is governed by the
|
|
4
|
+
* MIT license that can be found in the LICENSE file.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* ESLint Rule: no-weak-hash-algorithm
|
|
8
|
+
* Detects use of weak hash algorithms (MD5, SHA1, MD4)
|
|
9
|
+
* CWE-327: Use of a Broken or Risky Cryptographic Algorithm
|
|
10
|
+
*
|
|
11
|
+
* @see https://cwe.mitre.org/data/definitions/327.html
|
|
12
|
+
*/
|
|
13
|
+
import type { TSESLint } from '@interlace/eslint-devkit';
|
|
14
|
+
type MessageIds = 'weakHashAlgorithm' | 'useSha256' | 'useSha512' | 'useSha3';
|
|
15
|
+
export interface Options {
|
|
16
|
+
/** Additional weak algorithms to detect. Default: [] */
|
|
17
|
+
additionalWeakAlgorithms?: string[];
|
|
18
|
+
/** Allow weak hashes in test files. Default: false */
|
|
19
|
+
allowInTests?: boolean;
|
|
20
|
+
}
|
|
21
|
+
type RuleOptions = [Options?];
|
|
22
|
+
export declare const noWeakHashAlgorithm: TSESLint.RuleModule<MessageIds, RuleOptions, unknown, TSESLint.RuleListener> & {
|
|
23
|
+
name: string;
|
|
24
|
+
};
|
|
25
|
+
export type { Options as NoWeakHashAlgorithmOptions };
|