eslint-plugin-node-security 4.8.0 → 4.9.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.
Files changed (42) hide show
  1. package/README.md +1 -1
  2. package/package.json +3 -3
  3. package/src/index.js +1 -95
  4. package/src/oxlint.js +1 -3
  5. package/src/rules/detect-child-process/index.js +1 -625
  6. package/src/rules/detect-eval-with-expression/index.js +1 -364
  7. package/src/rules/detect-non-literal-fs-filename/index.js +1 -516
  8. package/src/rules/detect-suspicious-dependencies/index.js +1 -69
  9. package/src/rules/lock-file/index.js +1 -92
  10. package/src/rules/no-arbitrary-file-access/index.js +1 -152
  11. package/src/rules/no-buffer-overread/index.js +1 -543
  12. package/src/rules/no-cryptojs/index.js +1 -99
  13. package/src/rules/no-cryptojs-weak-random/index.js +1 -103
  14. package/src/rules/no-data-in-temp-storage/index.js +1 -85
  15. package/src/rules/no-deprecated-buffer/index.js +1 -84
  16. package/src/rules/no-deprecated-cipher-method/index.js +1 -112
  17. package/src/rules/no-dynamic-algorithm-selection/index.js +1 -72
  18. package/src/rules/no-dynamic-command-string/index.js +1 -201
  19. package/src/rules/no-dynamic-dependency-loading/index.js +1 -45
  20. package/src/rules/no-dynamic-require/index.js +1 -96
  21. package/src/rules/no-ecb-mode/index.js +1 -108
  22. package/src/rules/no-insecure-key-derivation/index.js +1 -109
  23. package/src/rules/no-insecure-rsa-padding/index.js +1 -104
  24. package/src/rules/no-math-random-crypto/index.js +1 -192
  25. package/src/rules/no-self-signed-certs/index.js +1 -110
  26. package/src/rules/no-sha1-hash/index.js +1 -121
  27. package/src/rules/no-shell-injection/index.js +1 -68
  28. package/src/rules/no-ssrf/index.js +1 -221
  29. package/src/rules/no-static-iv/index.js +1 -129
  30. package/src/rules/no-timing-unsafe-compare/index.js +1 -106
  31. package/src/rules/no-toctou-vulnerability/index.js +1 -195
  32. package/src/rules/no-unsafe-buffer-alloc/index.js +1 -87
  33. package/src/rules/no-unsafe-dynamic-require/index.js +1 -93
  34. package/src/rules/no-weak-cipher-algorithm/index.js +1 -174
  35. package/src/rules/no-weak-hash-algorithm/index.js +1 -199
  36. package/src/rules/no-zip-slip/index.js +1 -410
  37. package/src/rules/prefer-native-crypto/index.js +1 -119
  38. package/src/rules/require-dependency-integrity/index.js +1 -62
  39. package/src/rules/require-secure-credential-storage/index.js +1 -45
  40. package/src/rules/require-secure-deletion/index.js +1 -82
  41. package/src/rules/require-storage-encryption/index.js +1 -45
  42. package/src/types/index.js +1 -2
@@ -1,221 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noSsrf = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const HTTP_CLIENT_FUNCTIONS = new Set([
6
- 'fetch',
7
- 'got',
8
- 'nodeFetch',
9
- 'undici',
10
- ]);
11
- const HTTP_CLIENT_METHODS = new Set([
12
- 'get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'request',
13
- ]);
14
- const HTTP_CLIENT_OBJECTS = new Set([
15
- 'axios', 'got', 'superagent', 'request', 'http', 'https', 'undici', 'needle',
16
- ]);
17
- const VALIDATION_FUNCTION_NAMES = new Set([
18
- 'validateUrl', 'validateURL', 'isValidUrl', 'isSafeUrl', 'isAllowed',
19
- 'isValidURL', 'checkUrl', 'checkURL', 'sanitizeUrl', 'sanitizeURL',
20
- ]);
21
- const USER_INPUT_SUBSTRINGS = [
22
- 'url', 'endpoint', 'uri', 'href', 'link',
23
- 'target', 'dest', 'source', 'host',
24
- 'user', 'input', 'param',
25
- ];
26
- function isUserInputParamName(name) {
27
- const lower = name.toLowerCase();
28
- return USER_INPUT_SUBSTRINGS.some(sub => lower.includes(sub));
29
- }
30
- const REQUEST_ROOT_NAMES = new Set(['req', 'request', 'ctx', 'event']);
31
- const URL_OPTION_KEYS = new Set(['url', 'href', 'uri']);
32
- function isRequestSourced(node) {
33
- let current = node;
34
- while (current.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression) {
35
- current = current.object;
36
- }
37
- return (current !== node &&
38
- current.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
39
- REQUEST_ROOT_NAMES.has(current.name.toLowerCase()));
40
- }
41
- function carriesUntrustedUrl(node) {
42
- switch (node.type) {
43
- case eslint_devkit_1.AST_NODE_TYPES.Identifier:
44
- return isUserInputParamName(node.name);
45
- case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:
46
- return isRequestSourced(node);
47
- case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:
48
- return node.expressions.some(carriesUntrustedUrl);
49
- case eslint_devkit_1.AST_NODE_TYPES.CallExpression:
50
- case eslint_devkit_1.AST_NODE_TYPES.NewExpression:
51
- return node.arguments.some(carriesUntrustedUrl);
52
- case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:
53
- return carriesUntrustedUrl(node.left) || carriesUntrustedUrl(node.right);
54
- case eslint_devkit_1.AST_NODE_TYPES.ObjectExpression:
55
- return node.properties.some(property => {
56
- if (property.type !== eslint_devkit_1.AST_NODE_TYPES.Property)
57
- return false;
58
- const value = property.value;
59
- if (isRequestSourced(value))
60
- return true;
61
- const key = property.key.type === eslint_devkit_1.AST_NODE_TYPES.Identifier
62
- ? property.key.name
63
- : property.key.type === eslint_devkit_1.AST_NODE_TYPES.Literal
64
- ? String(property.key.value)
65
- : '';
66
- return URL_OPTION_KEYS.has(key.toLowerCase()) && carriesUntrustedUrl(value);
67
- });
68
- default:
69
- return false;
70
- }
71
- }
72
- function nodeContainsValidation(node) {
73
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.NewExpression &&
74
- node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
75
- node.callee.name === 'URL') {
76
- return true;
77
- }
78
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
79
- node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
80
- VALIDATION_FUNCTION_NAMES.has(node.callee.name)) {
81
- return true;
82
- }
83
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
84
- node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
85
- node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
86
- const method = node.callee.property.name;
87
- if (method === 'includes' || method === 'has' || method === 'startsWith' || method === 'test' || method === 'some') {
88
- return true;
89
- }
90
- }
91
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.BinaryExpression &&
92
- (node.operator === '===' || node.operator === '==') &&
93
- ((node.left.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
94
- node.left.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
95
- (node.left.property.name === 'hostname' || node.left.property.name === 'host')) ||
96
- (node.right.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
97
- node.right.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
98
- (node.right.property.name === 'hostname' || node.right.property.name === 'host')))) {
99
- return true;
100
- }
101
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement) {
102
- return true;
103
- }
104
- const SKIP_KEYS = new Set(['parent', 'range', 'loc', 'tokens', 'comments', 'start', 'end']);
105
- for (const key of Object.keys(node)) {
106
- if (SKIP_KEYS.has(key))
107
- continue;
108
- const value = node[key];
109
- if (value && typeof value === 'object' && 'type' in value) {
110
- if (nodeContainsValidation(value))
111
- return true;
112
- }
113
- if (Array.isArray(value)) {
114
- for (const item of value) {
115
- if (item && typeof item === 'object' && 'type' in item) {
116
- if (nodeContainsValidation(item))
117
- return true;
118
- }
119
- }
120
- }
121
- }
122
- return false;
123
- }
124
- function hasValidationBefore(node) {
125
- let current = node.parent;
126
- while (current) {
127
- const parent = current.parent;
128
- if (!parent)
129
- break;
130
- if (parent.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement || parent.type === eslint_devkit_1.AST_NODE_TYPES.Program) {
131
- const body = parent.body;
132
- const idx = body.indexOf(current);
133
- for (let i = idx - 1; i >= 0 && i >= idx - 10; i--) {
134
- if (nodeContainsValidation(body[i])) {
135
- return true;
136
- }
137
- }
138
- }
139
- if (parent.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement && parent.test) {
140
- if (nodeContainsValidation(parent.test)) {
141
- return true;
142
- }
143
- }
144
- current = parent;
145
- }
146
- return false;
147
- }
148
- exports.noSsrf = (0, eslint_devkit_1.createRule)({
149
- name: 'no-ssrf',
150
- meta: {
151
- type: 'suggestion',
152
- docs: {
153
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-ssrf.md',
154
- description: 'Flags HTTP calls whose URL argument is a user-input-named identifier or reads off a request object — a heuristic prompt for code review, not a proof of SSRF',
155
- cwe: 'CWE-918',
156
- cvss: 9.1,
157
- },
158
- messages: {
159
- ssrfVulnerability: (0, eslint_devkit_1.formatLLMMessage)({
160
- icon: eslint_devkit_1.MessageIcons.SECURITY,
161
- issueName: 'Possible SSRF — heuristic (CWE-918)',
162
- cwe: 'CWE-918',
163
- description: 'HTTP call whose URL argument name suggests user input. This is a naming heuristic, not data-flow analysis — review whether the URL could originate from an untrusted source at runtime.',
164
- severity: 'LOW',
165
- fix: 'If the URL comes from user input, validate it against an allowlist of permitted hosts before making the request.',
166
- documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html',
167
- }),
168
- },
169
- schema: [
170
- {
171
- type: 'object',
172
- properties: {
173
- allowInTests: {
174
- type: 'boolean',
175
- default: true,
176
- },
177
- },
178
- additionalProperties: false,
179
- },
180
- ],
181
- },
182
- defaultOptions: [{ allowInTests: true }],
183
- create(context, [options = {}]) {
184
- const { allowInTests = true } = options || {};
185
- const filename = context.filename;
186
- const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
187
- if (isTestFile)
188
- return {};
189
- return {
190
- CallExpression(node) {
191
- let isHttpCall = false;
192
- if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
193
- HTTP_CLIENT_FUNCTIONS.has(node.callee.name)) {
194
- isHttpCall = true;
195
- }
196
- if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
197
- node.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
198
- node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
199
- HTTP_CLIENT_OBJECTS.has(node.callee.object.name) &&
200
- HTTP_CLIENT_METHODS.has(node.callee.property.name)) {
201
- isHttpCall = true;
202
- }
203
- if (!isHttpCall)
204
- return;
205
- const urlArg = node.arguments[0];
206
- if (!urlArg)
207
- return;
208
- if (hasValidationBefore(node)) {
209
- return;
210
- }
211
- if (!carriesUntrustedUrl(urlArg)) {
212
- return;
213
- }
214
- context.report({
215
- node,
216
- messageId: 'ssrfVulnerability',
217
- });
218
- },
219
- };
220
- },
221
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noSsrf=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const HTTP_CLIENT_FUNCTIONS=new Set(["fetch","got","nodeFetch","undici"]);const HTTP_CLIENT_METHODS=new Set(["get","post","put","patch","delete","head","options","request"]);const HTTP_CLIENT_OBJECTS=new Set(["axios","got","superagent","request","http","https","undici","needle"]);const VALIDATION_FUNCTION_NAMES=new Set(["validateUrl","validateURL","isValidUrl","isSafeUrl","isAllowed","isValidURL","checkUrl","checkURL","sanitizeUrl","sanitizeURL"]);const USER_INPUT_SUBSTRINGS=["url","endpoint","uri","href","link","target","dest","source","host","user","input","param"];function isUserInputParamName(name){const lower=name.toLowerCase();return USER_INPUT_SUBSTRINGS.some(sub=>lower.includes(sub))}const REQUEST_ROOT_NAMES=new Set(["req","request","ctx","event"]);const URL_OPTION_KEYS=new Set(["url","href","uri"]);function isRequestSourced(node){let current=node;while(current.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){current=current.object}return current!==node&&current.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&REQUEST_ROOT_NAMES.has(current.name.toLowerCase())}function carriesUntrustedUrl(node){switch(node.type){case eslint_devkit_1.AST_NODE_TYPES.Identifier:return isUserInputParamName(node.name);case eslint_devkit_1.AST_NODE_TYPES.MemberExpression:return isRequestSourced(node);case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:return node.expressions.some(carriesUntrustedUrl);case eslint_devkit_1.AST_NODE_TYPES.CallExpression:case eslint_devkit_1.AST_NODE_TYPES.NewExpression:return node.arguments.some(carriesUntrustedUrl);case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:return carriesUntrustedUrl(node.left)||carriesUntrustedUrl(node.right);case eslint_devkit_1.AST_NODE_TYPES.ObjectExpression:return node.properties.some(property=>{if(property.type!==eslint_devkit_1.AST_NODE_TYPES.Property)return false;const value=property.value;if(isRequestSourced(value))return true;const key=property.key.type===eslint_devkit_1.AST_NODE_TYPES.Identifier?property.key.name:property.key.type===eslint_devkit_1.AST_NODE_TYPES.Literal?String(property.key.value):"";return URL_OPTION_KEYS.has(key.toLowerCase())&&carriesUntrustedUrl(value)});default:return false}}function nodeContainsValidation(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.name==="URL"){return true}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&VALIDATION_FUNCTION_NAMES.has(node.callee.name)){return true}if(node.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const method=node.callee.property.name;if(method==="includes"||method==="has"||method==="startsWith"||method==="test"||method==="some"){return true}}if(node.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&(node.operator==="==="||node.operator==="==")&&(node.left.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.left.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.left.property.name==="hostname"||node.left.property.name==="host")||node.right.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.right.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.right.property.name==="hostname"||node.right.property.name==="host"))){return true}if(node.type===eslint_devkit_1.AST_NODE_TYPES.ThrowStatement){return true}const SKIP_KEYS=new Set(["parent","range","loc","tokens","comments","start","end"]);for(const key of Object.keys(node)){if(SKIP_KEYS.has(key))continue;const value=node[key];if(value&&typeof value==="object"&&"type"in value){if(nodeContainsValidation(value))return true}if(Array.isArray(value)){for(const item of value){if(item&&typeof item==="object"&&"type"in item){if(nodeContainsValidation(item))return true}}}}return false}function hasValidationBefore(node){let current=node.parent;while(current){const parent=current.parent;if(!parent)break;if(parent.type===eslint_devkit_1.AST_NODE_TYPES.BlockStatement||parent.type===eslint_devkit_1.AST_NODE_TYPES.Program){const body=parent.body;const idx=body.indexOf(current);for(let i=idx-1;i>=0&&i>=idx-10;i--){if(nodeContainsValidation(body[i])){return true}}}if(parent.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement&&parent.test){if(nodeContainsValidation(parent.test)){return true}}current=parent}return false}exports.noSsrf=(0,eslint_devkit_1.createRule)({name:"no-ssrf",meta:{type:"suggestion",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-ssrf.md",description:"Flags HTTP calls whose URL argument is a user-input-named identifier or reads off a request object \u2014 a heuristic prompt for code review, not a proof of SSRF",cwe:"CWE-918",cvss:9.1},messages:{ssrfVulnerability:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Possible SSRF \u2014 heuristic (CWE-918)",cwe:"CWE-918",description:"HTTP call whose URL argument name suggests user input. This is a naming heuristic, not data-flow analysis \u2014 review whether the URL could originate from an untrusted source at runtime.",severity:"LOW",fix:"If the URL comes from user input, validate it against an allowlist of permitted hosts before making the request.",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Server_Side_Request_Forgery_Prevention_Cheat_Sheet.html"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:true}},additionalProperties:false}]},defaultOptions:[{allowInTests:true}],create(context,[options={}]){const{allowInTests=true}=options||{};const filename=context.filename;const isTestFile=allowInTests&&/\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);if(isTestFile)return{};return{CallExpression(node){let isHttpCall=false;if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&HTTP_CLIENT_FUNCTIONS.has(node.callee.name)){isHttpCall=true}if(node.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&HTTP_CLIENT_OBJECTS.has(node.callee.object.name)&&HTTP_CLIENT_METHODS.has(node.callee.property.name)){isHttpCall=true}if(!isHttpCall)return;const urlArg=node.arguments[0];if(!urlArg)return;if(hasValidationBefore(node)){return}if(!carriesUntrustedUrl(urlArg)){return}context.report({node,messageId:"ssrfVulnerability"})}}}});
@@ -1,129 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noStaticIv = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const STATIC_IV_PATTERNS = [
6
- /^[0-9a-f]+$/i,
7
- /^[A-Za-z0-9+/]+=*$/,
8
- ];
9
- exports.noStaticIv = (0, eslint_devkit_1.createRule)({
10
- name: 'no-static-iv',
11
- meta: {
12
- type: 'problem',
13
- docs: {
14
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-static-iv.md',
15
- description: 'Disallow static or hardcoded initialization vectors (IVs)',
16
- cwe: 'CWE-329',
17
- cvss: 7.5,
18
- },
19
- hasSuggestions: false,
20
- messages: {
21
- staticIv: (0, eslint_devkit_1.formatLLMMessage)({
22
- icon: eslint_devkit_1.MessageIcons.SECURITY,
23
- issueName: 'Static IV detected',
24
- cwe: 'CWE-329',
25
- description: 'Hardcoded IV detected. Using static IVs makes encryption deterministic, allowing attackers to detect repeated plaintexts.',
26
- severity: 'HIGH',
27
- fix: 'Generate IV dynamically using crypto.randomBytes(16)',
28
- documentationLink: 'https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#initialization-vectors',
29
- }),
30
- useRandomBytes: (0, eslint_devkit_1.formatLLMMessage)({
31
- icon: eslint_devkit_1.MessageIcons.INFO,
32
- issueName: 'Use randomBytes',
33
- description: 'Generate IV dynamically for each encryption operation',
34
- severity: 'LOW',
35
- fix: 'const iv = crypto.randomBytes(16);',
36
- documentationLink: 'https://nodejs.org/api/crypto.html#cryptorandombytessize-callback',
37
- }),
38
- },
39
- schema: [
40
- {
41
- type: 'object',
42
- properties: {
43
- allowInTests: {
44
- type: 'boolean',
45
- default: false,
46
- description: 'Allow static IVs in test files',
47
- },
48
- },
49
- additionalProperties: false,
50
- },
51
- ],
52
- },
53
- defaultOptions: [
54
- {
55
- allowInTests: false,
56
- },
57
- ],
58
- create(context, [options = {}]) {
59
- const { allowInTests = false } = options;
60
- const filename = context.filename;
61
- const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
62
- const randomIvVariables = new Set();
63
- function checkVariableDeclarator(node) {
64
- if (node.id.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
65
- node.init?.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
66
- const init = node.init;
67
- if (init.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
68
- init.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
69
- init.callee.property.name === 'randomBytes') {
70
- randomIvVariables.add(node.id.name);
71
- }
72
- if (init.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
73
- init.callee.name === 'randomBytes') {
74
- randomIvVariables.add(node.id.name);
75
- }
76
- }
77
- }
78
- function checkCallExpression(node) {
79
- if (isTestFile)
80
- return;
81
- const isCipherivCall = (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
82
- node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
83
- (node.callee.property.name === 'createCipheriv' || node.callee.property.name === 'createDecipheriv')) ||
84
- (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
85
- (node.callee.name === 'createCipheriv' || node.callee.name === 'createDecipheriv'));
86
- if (isCipherivCall && node.arguments.length >= 3) {
87
- const ivArg = node.arguments[2];
88
- checkIvArgument(ivArg);
89
- }
90
- }
91
- function checkIvArgument(ivArg) {
92
- if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof ivArg.value === 'string') {
93
- const value = ivArg.value;
94
- if (STATIC_IV_PATTERNS.some(p => p.test(value)) || value.length >= 8) {
95
- reportStaticIv(ivArg);
96
- }
97
- }
98
- if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
99
- ivArg.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
100
- ivArg.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
101
- ivArg.callee.object.name === 'Buffer' &&
102
- ivArg.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
103
- (ivArg.callee.property.name === 'from' || ivArg.callee.property.name === 'alloc')) {
104
- const firstArg = ivArg.arguments[0];
105
- if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof firstArg.value === 'string') {
106
- reportStaticIv(ivArg);
107
- }
108
- if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.ArrayExpression) {
109
- const allLiterals = firstArg.elements.every((el) => el?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof el.value === 'number');
110
- if (allLiterals) {
111
- reportStaticIv(ivArg);
112
- }
113
- }
114
- }
115
- if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
116
- }
117
- }
118
- function reportStaticIv(node) {
119
- context.report({
120
- node,
121
- messageId: 'staticIv',
122
- });
123
- }
124
- return {
125
- VariableDeclarator: checkVariableDeclarator,
126
- CallExpression: checkCallExpression,
127
- };
128
- },
129
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noStaticIv=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const STATIC_IV_PATTERNS=[/^[0-9a-f]+$/i,/^[A-Za-z0-9+/]+=*$/];exports.noStaticIv=(0,eslint_devkit_1.createRule)({name:"no-static-iv",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-static-iv.md",description:"Disallow static or hardcoded initialization vectors (IVs)",cwe:"CWE-329",cvss:7.5},hasSuggestions:false,messages:{staticIv:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Static IV detected",cwe:"CWE-329",description:"Hardcoded IV detected. Using static IVs makes encryption deterministic, allowing attackers to detect repeated plaintexts.",severity:"HIGH",fix:"Generate IV dynamically using crypto.randomBytes(16)",documentationLink:"https://cheatsheetseries.owasp.org/cheatsheets/Cryptographic_Storage_Cheat_Sheet.html#initialization-vectors"}),useRandomBytes:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use randomBytes",description:"Generate IV dynamically for each encryption operation",severity:"LOW",fix:"const iv = crypto.randomBytes(16);",documentationLink:"https://nodejs.org/api/crypto.html#cryptorandombytessize-callback"})},schema:[{type:"object",properties:{allowInTests:{type:"boolean",default:false,description:"Allow static IVs 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);const randomIvVariables=new Set;function checkVariableDeclarator(node){if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init?.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const init=node.init;if(init.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&init.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&init.callee.property.name==="randomBytes"){randomIvVariables.add(node.id.name)}if(init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&init.callee.name==="randomBytes"){randomIvVariables.add(node.id.name)}}}function checkCallExpression(node){if(isTestFile)return;const isCipherivCall=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==="createCipheriv"||node.callee.property.name==="createDecipheriv")||node.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(node.callee.name==="createCipheriv"||node.callee.name==="createDecipheriv");if(isCipherivCall&&node.arguments.length>=3){const ivArg=node.arguments[2];checkIvArgument(ivArg)}}function checkIvArgument(ivArg){if(ivArg.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof ivArg.value==="string"){const value=ivArg.value;if(STATIC_IV_PATTERNS.some(p=>p.test(value))||value.length>=8){reportStaticIv(ivArg)}}if(ivArg.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&ivArg.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&ivArg.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&ivArg.callee.object.name==="Buffer"&&ivArg.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(ivArg.callee.property.name==="from"||ivArg.callee.property.name==="alloc")){const firstArg=ivArg.arguments[0];if(firstArg?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof firstArg.value==="string"){reportStaticIv(ivArg)}if(firstArg?.type===eslint_devkit_1.AST_NODE_TYPES.ArrayExpression){const allLiterals=firstArg.elements.every(el=>el?.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof el.value==="number");if(allLiterals){reportStaticIv(ivArg)}}}if(ivArg.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){}}function reportStaticIv(node){context.report({node,messageId:"staticIv"})}return{VariableDeclarator:checkVariableDeclarator,CallExpression:checkCallExpression}}});
@@ -1,106 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noTimingUnsafeCompare = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- const DEFAULT_SECRET_PATTERNS = [
6
- 'token', 'secret', 'key', 'password', 'hash', 'signature',
7
- 'mac', 'hmac', 'digest', 'apiKey', 'api_key', 'api-key',
8
- 'auth', 'credential', 'bearer', 'jwt', 'csrf', 'nonce',
9
- 'ssn', 'social_security', 'social-security',
10
- 'pii', 'private_key', 'private-key', 'privateKey',
11
- 'access_token', 'access-token', 'accessToken',
12
- 'refresh_token', 'refresh-token', 'refreshToken',
13
- 'session_id', 'session-id', 'sessionId',
14
- 'auth_token', 'auth-token', 'authToken',
15
- 'encryption_key', 'encryption-key', 'encryptionKey',
16
- ];
17
- exports.noTimingUnsafeCompare = (0, eslint_devkit_1.createRule)({
18
- name: 'no-timing-unsafe-compare',
19
- meta: {
20
- type: 'problem',
21
- docs: {
22
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-timing-unsafe-compare.md',
23
- description: 'Disallow timing-unsafe comparison of secrets',
24
- cwe: 'CWE-208',
25
- cvss: 7.5,
26
- },
27
- hasSuggestions: true,
28
- messages: {
29
- timingUnsafeCompare: (0, eslint_devkit_1.formatLLMMessage)({
30
- icon: eslint_devkit_1.MessageIcons.SECURITY,
31
- issueName: 'Timing-unsafe comparison',
32
- cwe: 'CWE-208',
33
- description: 'Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.',
34
- severity: 'HIGH',
35
- fix: 'Use crypto.timingSafeEqual() for constant-time comparison',
36
- documentationLink: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b',
37
- }),
38
- useTimingSafeEqual: (0, eslint_devkit_1.formatLLMMessage)({
39
- icon: eslint_devkit_1.MessageIcons.INFO,
40
- issueName: 'Use timingSafeEqual',
41
- description: 'Use constant-time comparison to prevent timing attacks',
42
- severity: 'LOW',
43
- fix: 'crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))',
44
- documentationLink: 'https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b',
45
- }),
46
- },
47
- schema: [
48
- {
49
- type: 'object',
50
- properties: {
51
- secretPatterns: {
52
- type: 'array',
53
- items: { type: 'string' },
54
- default: DEFAULT_SECRET_PATTERNS,
55
- description: 'Variable name patterns that indicate secrets',
56
- },
57
- },
58
- additionalProperties: false,
59
- },
60
- ],
61
- },
62
- defaultOptions: [
63
- {
64
- secretPatterns: DEFAULT_SECRET_PATTERNS,
65
- },
66
- ],
67
- create(context, [options = {}]) {
68
- const { secretPatterns = DEFAULT_SECRET_PATTERNS } = options;
69
- const patterns = secretPatterns.map(p => new RegExp(p, 'i'));
70
- function isSecretIdentifier(node) {
71
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
72
- return patterns.some(p => p.test(node.name));
73
- }
74
- if (node.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression) {
75
- const prop = node.property;
76
- if (prop.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
77
- return patterns.some(p => p.test(prop.name));
78
- }
79
- }
80
- return false;
81
- }
82
- function checkBinaryExpression(node) {
83
- if (node.operator !== '===' && node.operator !== '==' &&
84
- node.operator !== '!==' && node.operator !== '!=') {
85
- return;
86
- }
87
- const leftIsSecret = isSecretIdentifier(node.left);
88
- const rightIsSecret = isSecretIdentifier(node.right);
89
- if (leftIsSecret || rightIsSecret) {
90
- context.report({
91
- node,
92
- messageId: 'timingUnsafeCompare',
93
- suggest: [
94
- {
95
- messageId: 'useTimingSafeEqual',
96
- fix: () => null,
97
- },
98
- ],
99
- });
100
- }
101
- }
102
- return {
103
- BinaryExpression: checkBinaryExpression,
104
- };
105
- },
106
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noTimingUnsafeCompare=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");const DEFAULT_SECRET_PATTERNS=["token","secret","password","hash","signature","mac","hmac","digest","apiKey","api_key","api-key","auth","credential","bearer","jwt","csrf","nonce","ssn","social_security","social-security","pii","private_key","private-key","privateKey","access_token","access-token","accessToken","refresh_token","refresh-token","refreshToken","session_id","session-id","sessionId","auth_token","auth-token","authToken","encryption_key","encryption-key","encryptionKey"];function isExistenceCheck(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.name==="undefined")return true;if(node.type===eslint_devkit_1.AST_NODE_TYPES.Literal){return node.value===null||typeof node.value==="number"||typeof node.value==="boolean"}return false}exports.noTimingUnsafeCompare=(0,eslint_devkit_1.createRule)({name:"no-timing-unsafe-compare",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-timing-unsafe-compare.md",description:"Disallow timing-unsafe comparison of secrets",cwe:"CWE-208",cvss:7.5},hasSuggestions:true,messages:{timingUnsafeCompare:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Timing-unsafe comparison",cwe:"CWE-208",description:"Using === to compare secrets enables timing attacks. The comparison short-circuits on first mismatch, leaking information about the secret.",severity:"HIGH",fix:"Use crypto.timingSafeEqual() for constant-time comparison",documentationLink:"https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b"}),useTimingSafeEqual:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use timingSafeEqual",description:"Use constant-time comparison to prevent timing attacks",severity:"LOW",fix:"crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b))",documentationLink:"https://nodejs.org/api/crypto.html#cryptotimingsafeequala-b"})},schema:[{type:"object",properties:{secretPatterns:{type:"array",items:{type:"string"},default:DEFAULT_SECRET_PATTERNS,description:"Variable name patterns that indicate secrets"}},additionalProperties:false}]},defaultOptions:[{secretPatterns:DEFAULT_SECRET_PATTERNS}],create(context,[options={}]){const{secretPatterns=DEFAULT_SECRET_PATTERNS}=options;const patterns=secretPatterns.map(p=>new RegExp(p,"i"));function isSecretIdentifier(node){if(node.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return patterns.some(p=>p.test(node.name))}if(node.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const prop=node.property;if(prop.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){return patterns.some(p=>p.test(prop.name))}}return false}function checkBinaryExpression(node){if(node.operator!=="==="&&node.operator!=="=="&&node.operator!=="!=="&&node.operator!=="!="){return}if(isExistenceCheck(node.left)||isExistenceCheck(node.right)){return}const leftIsSecret=isSecretIdentifier(node.left);const rightIsSecret=isSecretIdentifier(node.right);if(leftIsSecret||rightIsSecret){context.report({node,messageId:"timingUnsafeCompare",suggest:[{messageId:"useTimingSafeEqual",fix:()=>null}]})}}return{BinaryExpression:checkBinaryExpression}}});