eslint-plugin-node-security 4.7.3 → 4.8.1

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.
Files changed (41) hide show
  1. package/package.json +2 -2
  2. package/src/index.js +1 -95
  3. package/src/oxlint.js +1 -3
  4. package/src/rules/detect-child-process/index.js +1 -625
  5. package/src/rules/detect-eval-with-expression/index.js +1 -364
  6. package/src/rules/detect-non-literal-fs-filename/index.js +1 -431
  7. package/src/rules/detect-suspicious-dependencies/index.js +1 -69
  8. package/src/rules/lock-file/index.js +1 -92
  9. package/src/rules/no-arbitrary-file-access/index.js +1 -152
  10. package/src/rules/no-buffer-overread/index.js +1 -543
  11. package/src/rules/no-cryptojs/index.js +1 -99
  12. package/src/rules/no-cryptojs-weak-random/index.js +1 -103
  13. package/src/rules/no-data-in-temp-storage/index.js +1 -85
  14. package/src/rules/no-deprecated-buffer/index.js +1 -84
  15. package/src/rules/no-deprecated-cipher-method/index.js +1 -112
  16. package/src/rules/no-dynamic-algorithm-selection/index.js +1 -72
  17. package/src/rules/no-dynamic-command-string/index.js +1 -201
  18. package/src/rules/no-dynamic-dependency-loading/index.js +1 -45
  19. package/src/rules/no-dynamic-require/index.js +1 -96
  20. package/src/rules/no-ecb-mode/index.js +1 -108
  21. package/src/rules/no-insecure-key-derivation/index.js +1 -109
  22. package/src/rules/no-insecure-rsa-padding/index.js +1 -104
  23. package/src/rules/no-math-random-crypto/index.js +1 -192
  24. package/src/rules/no-self-signed-certs/index.js +1 -110
  25. package/src/rules/no-sha1-hash/index.js +1 -121
  26. package/src/rules/no-shell-injection/index.js +1 -68
  27. package/src/rules/no-ssrf/index.js +1 -221
  28. package/src/rules/no-static-iv/index.js +1 -129
  29. package/src/rules/no-timing-unsafe-compare/index.js +1 -106
  30. package/src/rules/no-toctou-vulnerability/index.js +1 -195
  31. package/src/rules/no-unsafe-buffer-alloc/index.js +1 -87
  32. package/src/rules/no-unsafe-dynamic-require/index.js +1 -93
  33. package/src/rules/no-weak-cipher-algorithm/index.js +1 -174
  34. package/src/rules/no-weak-hash-algorithm/index.js +1 -199
  35. package/src/rules/no-zip-slip/index.js +1 -410
  36. package/src/rules/prefer-native-crypto/index.js +1 -119
  37. package/src/rules/require-dependency-integrity/index.js +1 -62
  38. package/src/rules/require-secure-credential-storage/index.js +1 -45
  39. package/src/rules/require-secure-deletion/index.js +1 -82
  40. package/src/rules/require-storage-encryption/index.js +1 -45
  41. package/src/types/index.js +1 -2
@@ -1,201 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noDynamicCommandString = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const ARGV_FUNCTIONS = new Set([
6
- 'spawn',
7
- 'spawnSync',
8
- 'execFile',
9
- 'execFileSync',
10
- 'fork',
11
- ]);
12
- const POSIX_COMMAND_FLAGS = new Set(['-c']);
13
- const CMD_COMMAND_FLAGS = new Set(['/c', '/C', '/k', '/K']);
14
- const POWERSHELL_COMMAND_FLAGS = new Set([
15
- '-Command',
16
- '-command',
17
- '-c',
18
- '-EncodedCommand',
19
- '-encodedcommand',
20
- '-e',
21
- '-ec',
22
- ]);
23
- const SHELL_COMMAND_FLAGS = {
24
- sh: POSIX_COMMAND_FLAGS,
25
- bash: POSIX_COMMAND_FLAGS,
26
- zsh: POSIX_COMMAND_FLAGS,
27
- dash: POSIX_COMMAND_FLAGS,
28
- ksh: POSIX_COMMAND_FLAGS,
29
- busybox: POSIX_COMMAND_FLAGS,
30
- cmd: CMD_COMMAND_FLAGS,
31
- 'cmd.exe': CMD_COMMAND_FLAGS,
32
- powershell: POWERSHELL_COMMAND_FLAGS,
33
- 'powershell.exe': POWERSHELL_COMMAND_FLAGS,
34
- pwsh: POWERSHELL_COMMAND_FLAGS,
35
- };
36
- const COMMAND_RUNNERS = new Set([
37
- 'execaCommand',
38
- 'execaCommandSync',
39
- '$.raw',
40
- ]);
41
- function basename(command) {
42
- const segments = command.split(/[\\/]/);
43
- return segments[segments.length - 1];
44
- }
45
- function isAssembledString(node) {
46
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral) {
47
- return node.expressions.length > 0;
48
- }
49
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.BinaryExpression) {
50
- return node.operator === '+';
51
- }
52
- return (node.type === eslint_devkit_1.AST_NODE_TYPES.Identifier ||
53
- node.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression ||
54
- node.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression);
55
- }
56
- function calleeName(callee) {
57
- if (callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier)
58
- return callee.name;
59
- if (callee.type !== eslint_devkit_1.AST_NODE_TYPES.MemberExpression)
60
- return null;
61
- if (callee.property.type !== eslint_devkit_1.AST_NODE_TYPES.Identifier)
62
- return null;
63
- if (callee.computed)
64
- return null;
65
- if (callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
66
- callee.object.name === '$') {
67
- return `$.${callee.property.name}`;
68
- }
69
- return callee.property.name;
70
- }
71
- exports.noDynamicCommandString = (0, eslint_devkit_1.createRule)({
72
- name: 'no-dynamic-command-string',
73
- meta: {
74
- type: 'problem',
75
- docs: {
76
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-command-string.md',
77
- description: 'Disallow dynamically assembled command strings passed to a shell flag or to a command-runner that does not escape (CWE-77)',
78
- cwe: 'CWE-77',
79
- cvss: 9.8,
80
- confidence: 'high',
81
- },
82
- messages: {
83
- shellFlagInjection: (0, eslint_devkit_1.formatLLMMessage)({
84
- icon: eslint_devkit_1.MessageIcons.SECURITY,
85
- issueName: 'Command Injection Through Shell Flag (CWE-77)',
86
- cwe: 'CWE-77',
87
- cvss: 9.8,
88
- description: '{{fn}}("{{shell}}", ["{{flag}}", …]) hands a dynamically built string to {{shell}}, which parses it as a command line. The argument array looks parameterized but everything after {{flag}} is re-parsed — `;`, `&&`, backticks and `$()` all execute.',
89
- severity: 'CRITICAL',
90
- fix: 'Invoke the target program directly with its own argument array — spawn("kill", [String(pid)]) — instead of routing it through a shell.',
91
- documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html',
92
- }),
93
- commandStringInterpolation: (0, eslint_devkit_1.formatLLMMessage)({
94
- icon: eslint_devkit_1.MessageIcons.SECURITY,
95
- issueName: 'Interpolated Command Line (CWE-77)',
96
- cwe: 'CWE-77',
97
- cvss: 9.8,
98
- description: '{{fn}}() takes a whole command line and does NOT escape interpolated values (unlike the execa/zx tagged-template forms). Any special character in the interpolated value changes which command runs.',
99
- severity: 'CRITICAL',
100
- fix: 'Use the tagged-template or array form that escapes for you: execa("git", ["clone", url]) or await $`git clone ${url}`.',
101
- documentationLink: 'https://cwe.mitre.org/data/definitions/77.html',
102
- }),
103
- },
104
- schema: [
105
- {
106
- type: 'object',
107
- properties: {
108
- extraCommandRunners: {
109
- type: 'array',
110
- items: { type: 'string' },
111
- description: 'Extra functions that accept a full command line without escaping',
112
- },
113
- },
114
- additionalProperties: false,
115
- },
116
- ],
117
- },
118
- defaultOptions: [{}],
119
- create(context, [options]) {
120
- const { extraCommandRunners } = options;
121
- const runners = new Set([
122
- ...COMMAND_RUNNERS,
123
- ...(extraCommandRunners ?? []),
124
- ]);
125
- function checkShellFlag(node, fn) {
126
- const command = node.arguments[0];
127
- if (!command ||
128
- command.type !== eslint_devkit_1.AST_NODE_TYPES.Literal ||
129
- typeof command.value !== 'string') {
130
- return;
131
- }
132
- const shell = basename(command.value);
133
- const commandFlags = SHELL_COMMAND_FLAGS[shell.toLowerCase()];
134
- if (!commandFlags)
135
- return;
136
- const argv = node.arguments[1];
137
- if (!argv || argv.type !== eslint_devkit_1.AST_NODE_TYPES.ArrayExpression)
138
- return;
139
- for (let i = 0; i < argv.elements.length - 1; i += 1) {
140
- const flag = argv.elements[i];
141
- if (!flag || flag.type !== eslint_devkit_1.AST_NODE_TYPES.Literal)
142
- continue;
143
- if (typeof flag.value !== 'string')
144
- continue;
145
- if (!commandFlags.has(flag.value))
146
- continue;
147
- const commandString = argv.elements[i + 1];
148
- if (!commandString)
149
- continue;
150
- if (!isAssembledString(commandString))
151
- continue;
152
- context.report({
153
- node: commandString,
154
- messageId: 'shellFlagInjection',
155
- data: { fn, shell, flag: flag.value },
156
- });
157
- return;
158
- }
159
- }
160
- function checkCommandRunner(node, fn) {
161
- const commandLine = node.arguments[0];
162
- if (!commandLine)
163
- return;
164
- if (commandLine.type === eslint_devkit_1.AST_NODE_TYPES.Literal)
165
- return;
166
- if (!isAssembledString(commandLine))
167
- return;
168
- context.report({
169
- node: commandLine,
170
- messageId: 'commandStringInterpolation',
171
- data: { fn },
172
- });
173
- }
174
- return {
175
- CallExpression(node) {
176
- const fn = calleeName(node.callee);
177
- if (!fn)
178
- return;
179
- if (ARGV_FUNCTIONS.has(fn)) {
180
- checkShellFlag(node, fn);
181
- return;
182
- }
183
- if (runners.has(fn)) {
184
- checkCommandRunner(node, fn);
185
- }
186
- },
187
- TaggedTemplateExpression(node) {
188
- const fn = calleeName(node.tag);
189
- if (!fn || !runners.has(fn))
190
- return;
191
- if (node.quasi.expressions.length === 0)
192
- return;
193
- context.report({
194
- node: node.quasi,
195
- messageId: 'commandStringInterpolation',
196
- data: { fn },
197
- });
198
- },
199
- };
200
- },
201
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicCommandString=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const ARGV_FUNCTIONS=new Set(["spawn","spawnSync","execFile","execFileSync","fork"]);const POSIX_COMMAND_FLAGS=new Set(["-c"]);const CMD_COMMAND_FLAGS=new Set(["/c","/C","/k","/K"]);const POWERSHELL_COMMAND_FLAGS=new Set(["-Command","-command","-c","-EncodedCommand","-encodedcommand","-e","-ec"]);const SHELL_COMMAND_FLAGS={sh:POSIX_COMMAND_FLAGS,bash:POSIX_COMMAND_FLAGS,zsh:POSIX_COMMAND_FLAGS,dash:POSIX_COMMAND_FLAGS,ksh:POSIX_COMMAND_FLAGS,busybox:POSIX_COMMAND_FLAGS,cmd:CMD_COMMAND_FLAGS,"cmd.exe":CMD_COMMAND_FLAGS,powershell:POWERSHELL_COMMAND_FLAGS,"powershell.exe":POWERSHELL_COMMAND_FLAGS,pwsh:POWERSHELL_COMMAND_FLAGS};const COMMAND_RUNNERS=new Set(["execaCommand","execaCommandSync","$.raw"]);function basename(command){const segments=command.split(/[\\/]/);return segments[segments.length-1]}function isAssembledString(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral){return node.expressions.length>0}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression){return node.operator==="+"}return node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier||node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression||node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression}function calleeName(callee){if(callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier)return callee.name;if(callee.type!==eslint_devkit_1.AST_NODE_TYPES.MemberExpression)return null;if(callee.property.type!==eslint_devkit_1.AST_NODE_TYPES.Identifier)return null;if(callee.computed)return null;if(callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&callee.object.name==="$"){return`$.${callee.property.name}`}return callee.property.name}exports.noDynamicCommandString=(0,eslint_devkit_1.createRule)({name:"no-dynamic-command-string",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-command-string.md",description:"Disallow dynamically assembled command strings passed to a shell flag or to a command-runner that does not escape (CWE-77)",cwe:"CWE-77",cvss:9.8,confidence:"high"},messages:{shellFlagInjection:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Command Injection Through Shell Flag (CWE-77)",cwe:"CWE-77",cvss:9.8,description:'{{fn}}("{{shell}}", ["{{flag}}", \u2026]) hands a dynamically built string to {{shell}}, which parses it as a command line. The argument array looks parameterized but everything after {{flag}} is re-parsed \u2014 `;`, `&&`, backticks and `$()` all execute.',severity:"CRITICAL",fix:'Invoke the target program directly with its own argument array \u2014 spawn("kill", [String(pid)]) \u2014 instead of routing it through a shell.',documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/OS_Command_Injection_Defense_Cheat_Sheet.html"}),commandStringInterpolation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Interpolated Command Line (CWE-77)",cwe:"CWE-77",cvss:9.8,description:"{{fn}}() takes a whole command line and does NOT escape interpolated values (unlike the execa/zx tagged-template forms). Any special character in the interpolated value changes which command runs.",severity:"CRITICAL",fix:'Use the tagged-template or array form that escapes for you: execa("git", ["clone", url]) or await $`git clone ${url}`.',documentationLink:"https://cwe.mitre.org/data/definitions/77.html"})},schema:[{type:"object",properties:{extraCommandRunners:{type:"array",items:{type:"string"},description:"Extra functions that accept a full command line without escaping"}},additionalProperties:false}]},defaultOptions:[{}],create(context,[options]){const{extraCommandRunners}=options;const runners=new Set([...COMMAND_RUNNERS,...extraCommandRunners??[]]);function checkShellFlag(node,fn){const command=node.arguments[0];if(!command||command.type!==eslint_devkit_1.AST_NODE_TYPES.Literal||typeof command.value!=="string"){return}const shell=basename(command.value);const commandFlags=SHELL_COMMAND_FLAGS[shell.toLowerCase()];if(!commandFlags)return;const argv=node.arguments[1];if(!argv||argv.type!==eslint_devkit_1.AST_NODE_TYPES.ArrayExpression)return;for(let i=0;i<argv.elements.length-1;i+=1){const flag=argv.elements[i];if(!flag||flag.type!==eslint_devkit_1.AST_NODE_TYPES.Literal)continue;if(typeof flag.value!=="string")continue;if(!commandFlags.has(flag.value))continue;const commandString=argv.elements[i+1];if(!commandString)continue;if(!isAssembledString(commandString))continue;context.report({node:commandString,messageId:"shellFlagInjection",data:{fn,shell,flag:flag.value}});return}}function checkCommandRunner(node,fn){const commandLine=node.arguments[0];if(!commandLine)return;if(commandLine.type===eslint_devkit_1.AST_NODE_TYPES.Literal)return;if(!isAssembledString(commandLine))return;context.report({node:commandLine,messageId:"commandStringInterpolation",data:{fn}})}return{CallExpression(node){const fn=calleeName(node.callee);if(!fn)return;if(ARGV_FUNCTIONS.has(fn)){checkShellFlag(node,fn);return}if(runners.has(fn)){checkCommandRunner(node,fn)}},TaggedTemplateExpression(node){const fn=calleeName(node.tag);if(!fn||!runners.has(fn))return;if(node.quasi.expressions.length===0)return;context.report({node:node.quasi,messageId:"commandStringInterpolation",data:{fn}})}}}});
@@ -1,45 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noDynamicDependencyLoading = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- exports.noDynamicDependencyLoading = (0, eslint_devkit_1.createRule)({
6
- name: 'no-dynamic-dependency-loading',
7
- meta: {
8
- type: 'problem',
9
- docs: {
10
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-dependency-loading.md',
11
- description: 'Prevent runtime dependency injection with dynamic paths',
12
- cwe: 'CWE-1104',
13
- cvss: 5.3,
14
- },
15
- messages: {
16
- violationDetected: (0, eslint_devkit_1.formatLLMMessage)({
17
- icon: eslint_devkit_1.MessageIcons.SECURITY,
18
- issueName: 'violation Detected',
19
- cwe: 'CWE-1104',
20
- description: 'Dynamic import/require detected - use static imports for security',
21
- severity: 'HIGH',
22
- fix: 'Review and apply secure practices',
23
- documentationLink: 'https://cwe.mitre.org/data/definitions/1104.html',
24
- })
25
- },
26
- schema: [],
27
- },
28
- defaultOptions: [],
29
- create(context) {
30
- return {
31
- CallExpression(node) {
32
- if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
33
- node.callee.name === 'require' &&
34
- node.arguments[0]?.type !== eslint_devkit_1.AST_NODE_TYPES.Literal) {
35
- context.report({ node, messageId: 'violationDetected' });
36
- }
37
- },
38
- ImportExpression(node) {
39
- if (node.source.type !== 'Literal') {
40
- context.report({ node, messageId: 'violationDetected' });
41
- }
42
- },
43
- };
44
- },
45
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicDependencyLoading=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noDynamicDependencyLoading=(0,eslint_devkit_1.createRule)({name:"no-dynamic-dependency-loading",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-dependency-loading.md",description:"Prevent runtime dependency injection with dynamic paths",cwe:"CWE-1104",cvss:5.3},messages:{violationDetected:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"violation Detected",cwe:"CWE-1104",description:"Dynamic import/require detected - use static imports for security",severity:"HIGH",fix:"Review and apply secure practices",documentationLink:"https://cwe.mitre.org/data/definitions/1104.html"})},schema:[]},defaultOptions:[],create(context){return{CallExpression(node){if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="require"&&node.arguments[0]?.type!==eslint_devkit_1.AST_NODE_TYPES.Literal){context.report({node,messageId:"violationDetected"})}},ImportExpression(node){if(node.source.type!=="Literal"){context.report({node,messageId:"violationDetected"})}}}}});
@@ -1,96 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noDynamicRequire = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const eslint_devkit_2 = require("@interlace/eslint-devkit");
6
- exports.noDynamicRequire = (0, eslint_devkit_1.createRule)({
7
- name: 'no-dynamic-require',
8
- meta: {
9
- type: 'problem',
10
- docs: {
11
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-require.md',
12
- description: 'Forbid `require()` calls with expressions',
13
- cwe: 'CWE-94',
14
- cweJustification: 'CWE-94 (Improper Control of Generation of Code) — dynamic require with attacker-influenced path can load arbitrary modules, equivalent to remote code execution.',
15
- confidence: 'high',
16
- },
17
- hasSuggestions: false,
18
- messages: {
19
- dynamicRequire: (0, eslint_devkit_2.formatLLMMessage)({
20
- icon: eslint_devkit_2.MessageIcons.WARNING,
21
- issueName: 'Dynamic Require',
22
- description: 'Require call uses dynamic expression',
23
- severity: 'HIGH',
24
- fix: 'Use static string literals for require() calls',
25
- documentationLink: 'https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-dynamic-require.md',
26
- }),
27
- },
28
- schema: [
29
- {
30
- type: 'object',
31
- properties: {
32
- allowContexts: {
33
- type: 'array',
34
- items: {
35
- type: 'string',
36
- enum: ['test', 'config', 'build', 'runtime'],
37
- },
38
- description: 'Allow dynamic requires in specific contexts.',
39
- },
40
- allowPatterns: {
41
- type: 'array',
42
- items: { type: 'string' },
43
- description: 'Regex patterns for allowed dynamic require paths.',
44
- },
45
- },
46
- additionalProperties: false,
47
- },
48
- ],
49
- },
50
- defaultOptions: [{
51
- allowContexts: [],
52
- allowPatterns: []
53
- }],
54
- create(context) {
55
- const [options] = context.options;
56
- const { allowContexts = [], } = options || {};
57
- const filename = context.filename || '';
58
- function isInAllowedContext() {
59
- if (allowContexts.includes('test') && (filename.includes('.test.') || filename.includes('.spec.') || filename.includes('/__tests__/'))) {
60
- return true;
61
- }
62
- if (allowContexts.includes('config') && (filename.includes('config') || filename.includes('webpack') || filename.includes('rollup'))) {
63
- return true;
64
- }
65
- if (allowContexts.includes('build') && (filename.includes('build') || filename.includes('scripts') || filename.includes('tools'))) {
66
- return true;
67
- }
68
- if (allowContexts.includes('runtime') && (filename.includes('runtime') || filename.includes('dynamic'))) {
69
- return true;
70
- }
71
- return false;
72
- }
73
- function isStaticLiteral(node) {
74
- return node.type === 'Literal' && typeof node.value === 'string';
75
- }
76
- return {
77
- CallExpression(node) {
78
- if (node.callee.type === 'Identifier' &&
79
- node.callee.name === 'require' &&
80
- node.arguments.length === 1) {
81
- const requireArg = node.arguments[0];
82
- if (isInAllowedContext()) {
83
- return;
84
- }
85
- if (isStaticLiteral(requireArg)) {
86
- return;
87
- }
88
- context.report({
89
- node: requireArg,
90
- messageId: 'dynamicRequire',
91
- });
92
- }
93
- },
94
- };
95
- },
96
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noDynamicRequire=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const eslint_devkit_2=require("@interlace/eslint-devkit");exports.noDynamicRequire=(0,eslint_devkit_1.createRule)({name:"no-dynamic-require",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-dynamic-require.md",description:"Forbid `require()` calls with expressions",cwe:"CWE-94",cweJustification:"CWE-94 (Improper Control of Generation of Code) \u2014 dynamic require with attacker-influenced path can load arbitrary modules, equivalent to remote code execution.",confidence:"high"},hasSuggestions:false,messages:{dynamicRequire:(0,eslint_devkit_2.formatLLMMessage)({icon:eslint_devkit_2.MessageIcons.WARNING,issueName:"Dynamic Require",description:"Require call uses dynamic expression",severity:"HIGH",fix:"Use static string literals for require() calls",documentationLink:"https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-dynamic-require.md"})},schema:[{type:"object",properties:{allowContexts:{type:"array",items:{type:"string",enum:["test","config","build","runtime"]},description:"Allow dynamic requires in specific contexts."},allowPatterns:{type:"array",items:{type:"string"},description:"Regex patterns for allowed dynamic require paths."}},additionalProperties:false}]},defaultOptions:[{allowContexts:[],allowPatterns:[]}],create(context){const[options]=context.options;const{allowContexts=[]}=options||{};const filename=context.filename||"";function isInAllowedContext(){if(allowContexts.includes("test")&&(filename.includes(".test.")||filename.includes(".spec.")||filename.includes("/__tests__/"))){return true}if(allowContexts.includes("config")&&(filename.includes("config")||filename.includes("webpack")||filename.includes("rollup"))){return true}if(allowContexts.includes("build")&&(filename.includes("build")||filename.includes("scripts")||filename.includes("tools"))){return true}if(allowContexts.includes("runtime")&&(filename.includes("runtime")||filename.includes("dynamic"))){return true}return false}function isStaticLiteral(node){return node.type==="Literal"&&typeof node.value==="string"}return{CallExpression(node){if(node.callee.type==="Identifier"&&node.callee.name==="require"&&node.arguments.length===1){const requireArg=node.arguments[0];if(isInAllowedContext()){return}if(isStaticLiteral(requireArg)){return}context.report({node:requireArg,messageId:"dynamicRequire"})}}}}});
@@ -1,108 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noEcbMode = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- exports.noEcbMode = (0, eslint_devkit_1.createRule)({
6
- name: 'no-ecb-mode',
7
- meta: {
8
- type: 'problem',
9
- docs: {
10
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-ecb-mode.md',
11
- description: 'Disallow ECB encryption mode (use GCM or CBC instead)',
12
- cwe: 'CWE-327',
13
- cvss: 7.5,
14
- },
15
- hasSuggestions: true,
16
- messages: {
17
- ecbMode: (0, eslint_devkit_1.formatLLMMessage)({
18
- icon: eslint_devkit_1.MessageIcons.SECURITY,
19
- issueName: 'ECB mode detected',
20
- cwe: 'CWE-327',
21
- description: 'ECB mode encrypts identical plaintext blocks to identical ciphertext, leaking data patterns. Famous example: the "ECB penguin".',
22
- severity: 'HIGH',
23
- fix: 'Use GCM mode for authenticated encryption: crypto.createCipheriv("aes-256-gcm", key, iv)',
24
- documentationLink: 'https://blog.cloudflare.com/why-are-some-images-more-secure-than-others/',
25
- }),
26
- useGcm: (0, eslint_devkit_1.formatLLMMessage)({
27
- icon: eslint_devkit_1.MessageIcons.INFO,
28
- issueName: 'Use GCM mode',
29
- description: 'GCM provides authenticated encryption (confidentiality + integrity)',
30
- severity: 'LOW',
31
- fix: 'crypto.createCipheriv("aes-256-gcm", key, iv)',
32
- documentationLink: 'https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options',
33
- }),
34
- useCbc: (0, eslint_devkit_1.formatLLMMessage)({
35
- icon: eslint_devkit_1.MessageIcons.INFO,
36
- issueName: 'Use CBC mode',
37
- description: 'CBC with HMAC provides confidentiality (add separate MAC for integrity)',
38
- severity: 'LOW',
39
- fix: 'crypto.createCipheriv("aes-256-cbc", key, iv)',
40
- documentationLink: 'https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options',
41
- }),
42
- },
43
- schema: [
44
- {
45
- type: 'object',
46
- properties: {
47
- allowInTests: {
48
- type: 'boolean',
49
- default: false,
50
- description: 'Allow ECB mode in test files',
51
- },
52
- },
53
- additionalProperties: false,
54
- },
55
- ],
56
- },
57
- defaultOptions: [
58
- {
59
- allowInTests: false,
60
- },
61
- ],
62
- create(context, [options = {}]) {
63
- const { allowInTests = false } = options;
64
- const filename = context.filename;
65
- const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
66
- function checkCallExpression(node) {
67
- if (isTestFile)
68
- return;
69
- const cipherMethods = new Set(['createCipher', 'createCipheriv', 'createDecipher', 'createDecipheriv']);
70
- const isCipherCall = (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
71
- node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
72
- cipherMethods.has(node.callee.property.name)) ||
73
- (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
74
- cipherMethods.has(node.callee.name));
75
- if (isCipherCall && node.arguments.length >= 1) {
76
- const algorithmArg = node.arguments[0];
77
- if (algorithmArg.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof algorithmArg.value === 'string') {
78
- const algorithm = algorithmArg.value.toLowerCase();
79
- if (algorithm.includes('-ecb') || algorithm.endsWith('ecb')) {
80
- const gcmReplacement = algorithm.replace(/-?ecb$/, '-gcm');
81
- context.report({
82
- node: algorithmArg,
83
- messageId: 'ecbMode',
84
- suggest: [
85
- {
86
- messageId: 'useGcm',
87
- fix: (fixer) => {
88
- return fixer.replaceText(algorithmArg, `"${gcmReplacement}"`);
89
- },
90
- },
91
- {
92
- messageId: 'useCbc',
93
- fix: (fixer) => {
94
- const cbcReplacement = algorithm.replace(/-?ecb$/, '-cbc');
95
- return fixer.replaceText(algorithmArg, `"${cbcReplacement}"`);
96
- },
97
- },
98
- ],
99
- });
100
- }
101
- }
102
- }
103
- }
104
- return {
105
- CallExpression: checkCallExpression,
106
- };
107
- },
108
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noEcbMode=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noEcbMode=(0,eslint_devkit_1.createRule)({name:"no-ecb-mode",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-ecb-mode.md",description:"Disallow ECB encryption mode (use GCM or CBC instead)",cwe:"CWE-327",cvss:7.5},hasSuggestions:true,messages:{ecbMode:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"ECB mode detected",cwe:"CWE-327",description:'ECB mode encrypts identical plaintext blocks to identical ciphertext, leaking data patterns. Famous example: the "ECB penguin".',severity:"HIGH",fix:'Use GCM mode for authenticated encryption: crypto.createCipheriv("aes-256-gcm", key, iv)',documentationLink:"https://blog.cloudflare.com/why-are-some-images-more-secure-than-others/"}),useGcm:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use GCM mode",description:"GCM provides authenticated encryption (confidentiality + integrity)",severity:"LOW",fix:'crypto.createCipheriv("aes-256-gcm", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"}),useCbc:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use CBC mode",description:"CBC with HMAC provides confidentiality (add separate MAC for integrity)",severity:"LOW",fix:'crypto.createCipheriv("aes-256-cbc", key, iv)',documentationLink:"https://nodejs.org/api/crypto.html#cryptocreatecipherivalgorithm-key-iv-options"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow ECB mode in test files"}},additionalProperties:false}]},defaultOptions:[{allowInTests:false}],create(context,[options={}]){const{allowInTests=false}=options;const filename=context.filename;const isTestFile=allowInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);function checkCallExpression(node){if(isTestFile)return;const cipherMethods=new Set(["createCipher","createCipheriv","createDecipher","createDecipheriv"]);const isCipherCall=node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&cipherMethods.has(node.callee.property.name)||node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&cipherMethods.has(node.callee.name);if(isCipherCall&&node.arguments.length>=1){const algorithmArg=node.arguments[0];if(algorithmArg.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof algorithmArg.value==="string"){const algorithm=algorithmArg.value.toLowerCase();if(algorithm.includes("-ecb")||algorithm.endsWith("ecb")){const gcmReplacement=algorithm.replace(/-?ecb$/,"-gcm");context.report({node:algorithmArg,messageId:"ecbMode",suggest:[{messageId:"useGcm",fix:fixer=>{return fixer.replaceText(algorithmArg,`"${gcmReplacement}"`)}},{messageId:"useCbc",fix:fixer=>{const cbcReplacement=algorithm.replace(/-?ecb$/,"-cbc");return fixer.replaceText(algorithmArg,`"${cbcReplacement}"`)}}]})}}}}return{CallExpression:checkCallExpression}}});
@@ -1,109 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noInsecureKeyDerivation = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const DEFAULT_MIN_ITERATIONS = 100000;
6
- exports.noInsecureKeyDerivation = (0, eslint_devkit_1.createRule)({
7
- name: 'no-insecure-key-derivation',
8
- meta: {
9
- type: 'problem',
10
- docs: {
11
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-insecure-key-derivation.md',
12
- description: 'Disallow PBKDF2 with insufficient iterations (< 100,000)',
13
- cwe: 'CWE-916',
14
- cvss: 7.5,
15
- },
16
- hasSuggestions: true,
17
- messages: {
18
- insufficientIterations: (0, eslint_devkit_1.formatLLMMessage)({
19
- icon: eslint_devkit_1.MessageIcons.SECURITY,
20
- issueName: 'Insufficient PBKDF2 iterations',
21
- cwe: 'CWE-916',
22
- description: 'PBKDF2 with {{actual}} iterations is too low. Minimum recommended: {{minimum}} iterations (OWASP 2023).',
23
- severity: 'HIGH',
24
- fix: 'Increase iterations to at least {{minimum}}, or use scrypt/Argon2',
25
- documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html',
26
- }),
27
- useMinIterations: (0, eslint_devkit_1.formatLLMMessage)({
28
- icon: eslint_devkit_1.MessageIcons.INFO,
29
- issueName: 'Use minimum iterations',
30
- description: 'Use at least {{minimum}} iterations for PBKDF2',
31
- severity: 'LOW',
32
- fix: 'crypto.pbkdf2(password, salt, {{minimum}}, keylen, digest)',
33
- documentationLink: 'https://nodejs.org/api/crypto.html#cryptopbkdf2password-salt-iterations-keylen-digest-callback',
34
- }),
35
- useScrypt: (0, eslint_devkit_1.formatLLMMessage)({
36
- icon: eslint_devkit_1.MessageIcons.INFO,
37
- issueName: 'Use scrypt',
38
- description: 'scrypt is memory-hard and resistant to GPU/ASIC attacks',
39
- severity: 'LOW',
40
- fix: 'crypto.scrypt(password, salt, keylen)',
41
- documentationLink: 'https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback',
42
- }),
43
- useArgon2: (0, eslint_devkit_1.formatLLMMessage)({
44
- icon: eslint_devkit_1.MessageIcons.INFO,
45
- issueName: 'Use Argon2',
46
- description: 'Argon2id is the winner of the Password Hashing Competition',
47
- severity: 'LOW',
48
- fix: 'argon2.hash(password, { type: argon2.argon2id })',
49
- documentationLink: 'https://github.com/ranisalt/node-argon2',
50
- }),
51
- },
52
- schema: [
53
- {
54
- type: 'object',
55
- properties: {
56
- minIterations: {
57
- type: 'number',
58
- default: DEFAULT_MIN_ITERATIONS,
59
- description: 'Minimum required PBKDF2 iterations',
60
- },
61
- },
62
- additionalProperties: false,
63
- },
64
- ],
65
- },
66
- defaultOptions: [
67
- {
68
- minIterations: DEFAULT_MIN_ITERATIONS,
69
- },
70
- ],
71
- create(context, [options = {}]) {
72
- const { minIterations = DEFAULT_MIN_ITERATIONS } = options;
73
- function checkCallExpression(node) {
74
- const isPbkdf2Call = (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
75
- node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
76
- (node.callee.property.name === 'pbkdf2' || node.callee.property.name === 'pbkdf2Sync')) ||
77
- (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
78
- (node.callee.name === 'pbkdf2' || node.callee.name === 'pbkdf2Sync'));
79
- if (isPbkdf2Call) {
80
- const iterationsArg = node.arguments[2];
81
- if (iterationsArg?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof iterationsArg.value === 'number') {
82
- const iterations = iterationsArg.value;
83
- if (iterations < minIterations) {
84
- context.report({
85
- node: iterationsArg,
86
- messageId: 'insufficientIterations',
87
- data: {
88
- actual: String(iterations),
89
- minimum: String(minIterations),
90
- },
91
- suggest: [
92
- {
93
- messageId: 'useMinIterations',
94
- data: { minimum: String(minIterations) },
95
- fix: (fixer) => {
96
- return fixer.replaceText(iterationsArg, String(minIterations));
97
- },
98
- },
99
- ],
100
- });
101
- }
102
- }
103
- }
104
- }
105
- return {
106
- CallExpression: checkCallExpression,
107
- };
108
- },
109
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noInsecureKeyDerivation=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const DEFAULT_MIN_ITERATIONS=1e5;exports.noInsecureKeyDerivation=(0,eslint_devkit_1.createRule)({name:"no-insecure-key-derivation",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-insecure-key-derivation.md",description:"Disallow PBKDF2 with insufficient iterations (< 100,000)",cwe:"CWE-916",cvss:7.5},hasSuggestions:true,messages:{insufficientIterations:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Insufficient PBKDF2 iterations",cwe:"CWE-916",description:"PBKDF2 with {{actual}} iterations is too low. Minimum recommended: {{minimum}} iterations (OWASP 2023).",severity:"HIGH",fix:"Increase iterations to at least {{minimum}}, or use scrypt/Argon2",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html"}),useMinIterations:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use minimum iterations",description:"Use at least {{minimum}} iterations for PBKDF2",severity:"LOW",fix:"crypto.pbkdf2(password, salt, {{minimum}}, keylen, digest)",documentationLink:"https://nodejs.org/api/crypto.html#cryptopbkdf2password-salt-iterations-keylen-digest-callback"}),useScrypt:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use scrypt",description:"scrypt is memory-hard and resistant to GPU/ASIC attacks",severity:"LOW",fix:"crypto.scrypt(password, salt, keylen)",documentationLink:"https://nodejs.org/api/crypto.html#cryptoscryptpassword-salt-keylen-options-callback"}),useArgon2:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Argon2",description:"Argon2id is the winner of the Password Hashing Competition",severity:"LOW",fix:"argon2.hash(password, { type: argon2.argon2id })",documentationLink:"https://github.com/ranisalt/node-argon2"})},schema:[{type:"object",properties:{minIterations:{type:"number",default:DEFAULT_MIN_ITERATIONS,description:"Minimum required PBKDF2 iterations"}},additionalProperties:false}]},defaultOptions:[{minIterations:DEFAULT_MIN_ITERATIONS}],create(context,[options={}]){const{minIterations=DEFAULT_MIN_ITERATIONS}=options;function checkCallExpression(node){const isPbkdf2Call=node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.callee.property.name==="pbkdf2"||node.callee.property.name==="pbkdf2Sync")||node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.callee.name==="pbkdf2"||node.callee.name==="pbkdf2Sync");if(isPbkdf2Call){const iterationsArg=node.arguments[2];if(iterationsArg?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof iterationsArg.value==="number"){const iterations=iterationsArg.value;if(iterations<minIterations){context.report({node:iterationsArg,messageId:"insufficientIterations",data:{actual:String(iterations),minimum:String(minIterations)},suggest:[{messageId:"useMinIterations",data:{minimum:String(minIterations)},fix:fixer=>{return fixer.replaceText(iterationsArg,String(minIterations))}}]})}}}}return{CallExpression:checkCallExpression}}});