eslint-plugin-node-security 4.5.0 → 4.7.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 (118) hide show
  1. package/README.md +2 -0
  2. package/package.json +5 -8
  3. package/src/index.d.ts +17 -10
  4. package/src/index.js +8 -31
  5. package/src/oxlint.d.ts +17 -20
  6. package/src/oxlint.js +0 -19
  7. package/src/rules/detect-child-process/index.js +0 -107
  8. package/src/rules/detect-eval-with-expression/index.js +2 -48
  9. package/src/rules/detect-non-literal-fs-filename/index.js +0 -82
  10. package/src/rules/detect-suspicious-dependencies/index.js +0 -12
  11. package/src/rules/lock-file/index.js +0 -18
  12. package/src/rules/no-arbitrary-file-access/index.js +0 -52
  13. package/src/rules/no-buffer-overread/index.js +3 -96
  14. package/src/rules/no-cryptojs/index.js +2 -10
  15. package/src/rules/no-cryptojs-weak-random/index.js +1 -13
  16. package/src/rules/no-data-in-temp-storage/index.js +0 -12
  17. package/src/rules/no-deprecated-buffer/index.js +0 -15
  18. package/src/rules/no-deprecated-cipher-method/index.js +0 -9
  19. package/src/rules/no-dynamic-algorithm-selection/index.js +0 -13
  20. package/src/rules/no-dynamic-command-string/index.js +201 -0
  21. package/src/rules/no-dynamic-dependency-loading/index.js +0 -13
  22. package/src/rules/no-dynamic-require/index.js +0 -10
  23. package/src/rules/no-ecb-mode/index.js +0 -7
  24. package/src/rules/no-insecure-key-derivation/index.js +0 -10
  25. package/src/rules/no-insecure-rsa-padding/index.js +0 -9
  26. package/src/rules/no-math-random-crypto/index.js +1 -17
  27. package/src/rules/no-self-signed-certs/index.js +1 -9
  28. package/src/rules/no-sha1-hash/index.js +0 -9
  29. package/src/rules/no-shell-injection/index.js +0 -20
  30. package/src/rules/no-ssrf/index.js +5 -79
  31. package/src/rules/no-static-iv/index.js +2 -21
  32. package/src/rules/no-timing-unsafe-compare/index.js +1 -11
  33. package/src/rules/no-toctou-vulnerability/index.js +0 -22
  34. package/src/rules/no-unsafe-buffer-alloc/index.js +87 -0
  35. package/src/rules/no-unsafe-dynamic-require/index.js +0 -22
  36. package/src/rules/no-weak-cipher-algorithm/index.js +0 -19
  37. package/src/rules/no-weak-hash-algorithm/index.js +0 -22
  38. package/src/rules/no-zip-slip/index.js +3 -81
  39. package/src/rules/prefer-native-crypto/index.js +4 -11
  40. package/src/rules/require-dependency-integrity/index.js +0 -11
  41. package/src/rules/require-secure-credential-storage/index.js +0 -12
  42. package/src/rules/require-secure-deletion/index.js +0 -29
  43. package/src/rules/require-storage-encryption/index.js +0 -12
  44. package/src/types/index.js +0 -6
  45. package/CHANGELOG.md +0 -384
  46. package/src/index.js.map +0 -1
  47. package/src/oxlint.js.map +0 -1
  48. package/src/rules/detect-child-process/index.d.ts +0 -52
  49. package/src/rules/detect-child-process/index.js.map +0 -1
  50. package/src/rules/detect-eval-with-expression/index.d.ts +0 -46
  51. package/src/rules/detect-eval-with-expression/index.js.map +0 -1
  52. package/src/rules/detect-non-literal-fs-filename/index.d.ts +0 -52
  53. package/src/rules/detect-non-literal-fs-filename/index.js.map +0 -1
  54. package/src/rules/detect-suspicious-dependencies/index.d.ts +0 -12
  55. package/src/rules/detect-suspicious-dependencies/index.js.map +0 -1
  56. package/src/rules/lock-file/index.d.ts +0 -13
  57. package/src/rules/lock-file/index.js.map +0 -1
  58. package/src/rules/no-arbitrary-file-access/index.d.ts +0 -12
  59. package/src/rules/no-arbitrary-file-access/index.js.map +0 -1
  60. package/src/rules/no-buffer-overread/index.d.ts +0 -39
  61. package/src/rules/no-buffer-overread/index.js.map +0 -1
  62. package/src/rules/no-cryptojs/index.d.ts +0 -24
  63. package/src/rules/no-cryptojs/index.js.map +0 -1
  64. package/src/rules/no-cryptojs-weak-random/index.d.ts +0 -24
  65. package/src/rules/no-cryptojs-weak-random/index.js.map +0 -1
  66. package/src/rules/no-data-in-temp-storage/index.d.ts +0 -14
  67. package/src/rules/no-data-in-temp-storage/index.js.map +0 -1
  68. package/src/rules/no-deprecated-buffer/index.d.ts +0 -23
  69. package/src/rules/no-deprecated-buffer/index.js.map +0 -1
  70. package/src/rules/no-deprecated-cipher-method/index.d.ts +0 -23
  71. package/src/rules/no-deprecated-cipher-method/index.js.map +0 -1
  72. package/src/rules/no-dynamic-algorithm-selection/index.d.ts +0 -26
  73. package/src/rules/no-dynamic-algorithm-selection/index.js.map +0 -1
  74. package/src/rules/no-dynamic-dependency-loading/index.d.ts +0 -12
  75. package/src/rules/no-dynamic-dependency-loading/index.js.map +0 -1
  76. package/src/rules/no-dynamic-require/index.d.ts +0 -21
  77. package/src/rules/no-dynamic-require/index.js.map +0 -1
  78. package/src/rules/no-ecb-mode/index.d.ts +0 -23
  79. package/src/rules/no-ecb-mode/index.js.map +0 -1
  80. package/src/rules/no-insecure-key-derivation/index.d.ts +0 -24
  81. package/src/rules/no-insecure-key-derivation/index.js.map +0 -1
  82. package/src/rules/no-insecure-rsa-padding/index.d.ts +0 -24
  83. package/src/rules/no-insecure-rsa-padding/index.js.map +0 -1
  84. package/src/rules/no-math-random-crypto/index.d.ts +0 -26
  85. package/src/rules/no-math-random-crypto/index.js.map +0 -1
  86. package/src/rules/no-self-signed-certs/index.d.ts +0 -23
  87. package/src/rules/no-self-signed-certs/index.js.map +0 -1
  88. package/src/rules/no-sha1-hash/index.d.ts +0 -24
  89. package/src/rules/no-sha1-hash/index.js.map +0 -1
  90. package/src/rules/no-shell-injection/index.d.ts +0 -20
  91. package/src/rules/no-shell-injection/index.js.map +0 -1
  92. package/src/rules/no-ssrf/index.d.ts +0 -25
  93. package/src/rules/no-ssrf/index.js.map +0 -1
  94. package/src/rules/no-static-iv/index.d.ts +0 -23
  95. package/src/rules/no-static-iv/index.js.map +0 -1
  96. package/src/rules/no-timing-unsafe-compare/index.d.ts +0 -23
  97. package/src/rules/no-timing-unsafe-compare/index.js.map +0 -1
  98. package/src/rules/no-toctou-vulnerability/index.d.ts +0 -26
  99. package/src/rules/no-toctou-vulnerability/index.js.map +0 -1
  100. package/src/rules/no-unsafe-dynamic-require/index.d.ts +0 -19
  101. package/src/rules/no-unsafe-dynamic-require/index.js.map +0 -1
  102. package/src/rules/no-weak-cipher-algorithm/index.d.ts +0 -25
  103. package/src/rules/no-weak-cipher-algorithm/index.js.map +0 -1
  104. package/src/rules/no-weak-hash-algorithm/index.d.ts +0 -25
  105. package/src/rules/no-weak-hash-algorithm/index.js.map +0 -1
  106. package/src/rules/no-zip-slip/index.d.ts +0 -35
  107. package/src/rules/no-zip-slip/index.js.map +0 -1
  108. package/src/rules/prefer-native-crypto/index.d.ts +0 -23
  109. package/src/rules/prefer-native-crypto/index.js.map +0 -1
  110. package/src/rules/require-dependency-integrity/index.d.ts +0 -12
  111. package/src/rules/require-dependency-integrity/index.js.map +0 -1
  112. package/src/rules/require-secure-credential-storage/index.d.ts +0 -12
  113. package/src/rules/require-secure-credential-storage/index.js.map +0 -1
  114. package/src/rules/require-secure-deletion/index.d.ts +0 -17
  115. package/src/rules/require-secure-deletion/index.js.map +0 -1
  116. package/src/rules/require-storage-encryption/index.d.ts +0 -12
  117. package/src/rules/require-storage-encryption/index.js.map +0 -1
  118. package/src/types/index.js.map +0 -1
@@ -1,57 +1,34 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.noSsrf = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- // HTTP client functions that make outbound requests
11
5
  const HTTP_CLIENT_FUNCTIONS = new Set([
12
- 'fetch', // built-in / node-fetch
13
- 'got', // got
14
- 'nodeFetch', // node-fetch
15
- 'undici', // undici
6
+ 'fetch',
7
+ 'got',
8
+ 'nodeFetch',
9
+ 'undici',
16
10
  ]);
17
- // HTTP client method calls (e.g., axios.get, http.request)
18
11
  const HTTP_CLIENT_METHODS = new Set([
19
12
  'get', 'post', 'put', 'patch', 'delete', 'head', 'options', 'request',
20
13
  ]);
21
- // Object names that are HTTP client libraries
22
14
  const HTTP_CLIENT_OBJECTS = new Set([
23
- 'axios', 'got', 'superagent', 'request', 'http', 'https', 'undici',
15
+ 'axios', 'got', 'superagent', 'request', 'http', 'https', 'undici', 'needle',
24
16
  ]);
25
- // Function names that indicate URL validation
26
17
  const VALIDATION_FUNCTION_NAMES = new Set([
27
18
  'validateUrl', 'validateURL', 'isValidUrl', 'isSafeUrl', 'isAllowed',
28
19
  'isValidURL', 'checkUrl', 'checkURL', 'sanitizeUrl', 'sanitizeURL',
29
20
  ]);
30
- // Substrings in identifier names that suggest user input
31
21
  const USER_INPUT_SUBSTRINGS = [
32
22
  'url', 'endpoint', 'uri', 'href', 'link',
33
23
  'target', 'dest', 'source', 'host',
34
24
  'user', 'input', 'param',
35
25
  ];
36
- /**
37
- * Check if a function parameter name suggests user input
38
- */
39
26
  function isUserInputParamName(name) {
40
27
  const lower = name.toLowerCase();
41
28
  return USER_INPUT_SUBSTRINGS.some(sub => lower.includes(sub));
42
29
  }
43
- // Object roots whose members carry attacker-controlled data in the common
44
- // server frameworks: Express `req`/`request`, Koa `ctx`, Lambda `event`.
45
30
  const REQUEST_ROOT_NAMES = new Set(['req', 'request', 'ctx', 'event']);
46
- // Keys of a Node `http.request(options)` object that actually name a URL.
47
- // `host`/`hostname`/`path`/`port` are deliberately absent: a helper that
48
- // parameterises them is ordinary internal plumbing, not evidence of user flow.
49
31
  const URL_OPTION_KEYS = new Set(['url', 'href', 'uri']);
50
- /**
51
- * True when the expression reads off a request object — `req.query.url`,
52
- * `ctx.request.body.target`, `event.queryStringParameters.u`. Everything
53
- * hanging off a request is untrusted, so the root name is the whole test.
54
- */
55
32
  function isRequestSourced(node) {
56
33
  let current = node;
57
34
  while (current.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression) {
@@ -61,26 +38,6 @@ function isRequestSourced(node) {
61
38
  current.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
62
39
  REQUEST_ROOT_NAMES.has(current.name.toLowerCase()));
63
40
  }
64
- /**
65
- * Does the URL argument carry untrusted data into the request?
66
- *
67
- * The rule reports on evidence of flow, never on the mere presence of a
68
- * dynamic argument. Three shapes qualify:
69
- *
70
- * 1. The whole argument is a user-input-named identifier — `fetch(userUrl)`.
71
- * This is the rule's documented naming heuristic: a bare identifier in
72
- * URL position *is* the URL.
73
- * 2. Any part of the expression reads from a request object —
74
- * `fetch(req.query.url)`, `https.request({ host: req.query.h })`.
75
- * 3. A template literal URL interpolates either of the above —
76
- * `fetch(\`https://\${userHost}/x\`)`.
77
- *
78
- * Everything else is not evidence. In particular an options object whose
79
- * fields are plain locals — `https.request({ host, path, method: 'GET' })`,
80
- * benchmarks/corpus/CWE-444/safe/request-default-parser.js — used to be
81
- * reported unconditionally because a non-Identifier argument bypassed the
82
- * name gate entirely.
83
- */
84
41
  function carriesUntrustedUrl(node) {
85
42
  switch (node.type) {
86
43
  case eslint_devkit_1.AST_NODE_TYPES.Identifier:
@@ -89,15 +46,11 @@ function carriesUntrustedUrl(node) {
89
46
  return isRequestSourced(node);
90
47
  case eslint_devkit_1.AST_NODE_TYPES.TemplateLiteral:
91
48
  return node.expressions.some(carriesUntrustedUrl);
92
- // `new URL(userUrl)` / `String(req.query.url)` — inspect the arguments.
93
49
  case eslint_devkit_1.AST_NODE_TYPES.CallExpression:
94
50
  case eslint_devkit_1.AST_NODE_TYPES.NewExpression:
95
51
  return node.arguments.some(carriesUntrustedUrl);
96
- // `'https://host' + userPath`
97
52
  case eslint_devkit_1.AST_NODE_TYPES.BinaryExpression:
98
53
  return carriesUntrustedUrl(node.left) || carriesUntrustedUrl(node.right);
99
- // `http.request({ url: x, host: y })` — only URL-naming keys count by
100
- // name; every other key still counts if its value is request-sourced.
101
54
  case eslint_devkit_1.AST_NODE_TYPES.ObjectExpression:
102
55
  return node.properties.some(property => {
103
56
  if (property.type !== eslint_devkit_1.AST_NODE_TYPES.Property)
@@ -116,24 +69,17 @@ function carriesUntrustedUrl(node) {
116
69
  return false;
117
70
  }
118
71
  }
119
- /**
120
- * AST-based check: does this node contain a validation pattern?
121
- * Walks the node tree looking for known validation constructs.
122
- */
123
72
  function nodeContainsValidation(node) {
124
- // new URL(x) — URL constructor (parsing/validation)
125
73
  if (node.type === eslint_devkit_1.AST_NODE_TYPES.NewExpression &&
126
74
  node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
127
75
  node.callee.name === 'URL') {
128
76
  return true;
129
77
  }
130
- // validateUrl(x), isValidUrl(x), etc.
131
78
  if (node.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
132
79
  node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
133
80
  VALIDATION_FUNCTION_NAMES.has(node.callee.name)) {
134
81
  return true;
135
82
  }
136
- // arr.includes(x), set.has(x), x.startsWith('...'), regex.test(x)
137
83
  if (node.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
138
84
  node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
139
85
  node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
@@ -142,7 +88,6 @@ function nodeContainsValidation(node) {
142
88
  return true;
143
89
  }
144
90
  }
145
- // hostname === '...' or host === '...'
146
91
  if (node.type === eslint_devkit_1.AST_NODE_TYPES.BinaryExpression &&
147
92
  (node.operator === '===' || node.operator === '==') &&
148
93
  ((node.left.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
@@ -153,13 +98,10 @@ function nodeContainsValidation(node) {
153
98
  (node.right.property.name === 'hostname' || node.right.property.name === 'host')))) {
154
99
  return true;
155
100
  }
156
- // throw new Error(...) — guard clause
157
101
  if (node.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement) {
158
102
  return true;
159
103
  }
160
- // Keys to skip: non-child properties that cause circular refs or aren't AST children
161
104
  const SKIP_KEYS = new Set(['parent', 'range', 'loc', 'tokens', 'comments', 'start', 'end']);
162
- // Recurse into child nodes
163
105
  for (const key of Object.keys(node)) {
164
106
  if (SKIP_KEYS.has(key))
165
107
  continue;
@@ -179,11 +121,7 @@ function nodeContainsValidation(node) {
179
121
  }
180
122
  return false;
181
123
  }
182
- /**
183
- * Check if there is URL validation before the HTTP call using AST walking
184
- */
185
124
  function hasValidationBefore(node) {
186
- // Walk up to find the containing block
187
125
  let current = node.parent;
188
126
  while (current) {
189
127
  const parent = current.parent;
@@ -192,14 +130,12 @@ function hasValidationBefore(node) {
192
130
  if (parent.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement || parent.type === eslint_devkit_1.AST_NODE_TYPES.Program) {
193
131
  const body = parent.body;
194
132
  const idx = body.indexOf(current);
195
- // Check previous sibling statements for validation patterns
196
133
  for (let i = idx - 1; i >= 0 && i >= idx - 10; i--) {
197
134
  if (nodeContainsValidation(body[i])) {
198
135
  return true;
199
136
  }
200
137
  }
201
138
  }
202
- // Check if inside an if-block where the condition contains validation
203
139
  if (parent.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement && parent.test) {
204
140
  if (nodeContainsValidation(parent.test)) {
205
141
  return true;
@@ -253,12 +189,10 @@ exports.noSsrf = (0, eslint_devkit_1.createRule)({
253
189
  return {
254
190
  CallExpression(node) {
255
191
  let isHttpCall = false;
256
- // 1. Direct function call: fetch(url), got(url)
257
192
  if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
258
193
  HTTP_CLIENT_FUNCTIONS.has(node.callee.name)) {
259
194
  isHttpCall = true;
260
195
  }
261
- // 2. Method call: axios.get(url), http.request(url)
262
196
  if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
263
197
  node.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
264
198
  node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
@@ -268,19 +202,12 @@ exports.noSsrf = (0, eslint_devkit_1.createRule)({
268
202
  }
269
203
  if (!isHttpCall)
270
204
  return;
271
- // Check the first argument (the URL)
272
205
  const urlArg = node.arguments[0];
273
206
  if (!urlArg)
274
207
  return;
275
- // Static URLs — fetch('https://api.example.com'), fetch(`https://…`) —
276
- // carry nothing untrusted and fall out at the evidence gate below.
277
- // Check if there is URL validation before this call
278
208
  if (hasValidationBefore(node)) {
279
209
  return;
280
210
  }
281
- // Require evidence that untrusted data reaches the URL. Applies to
282
- // every argument shape — an options object or a template literal used
283
- // to bypass this gate entirely and report unconditionally.
284
211
  if (!carriesUntrustedUrl(urlArg)) {
285
212
  return;
286
213
  }
@@ -292,4 +219,3 @@ exports.noSsrf = (0, eslint_devkit_1.createRule)({
292
219
  };
293
220
  },
294
221
  });
295
- //# sourceMappingURL=index.js.map
@@ -1,18 +1,10 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.noStaticIv = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
- /**
11
- * Patterns that indicate a hardcoded IV
12
- */
13
5
  const STATIC_IV_PATTERNS = [
14
- /^[0-9a-f]+$/i, // Hex string
15
- /^[A-Za-z0-9+/]+=*$/, // Base64
6
+ /^[0-9a-f]+$/i,
7
+ /^[A-Za-z0-9+/]+=*$/,
16
8
  ];
17
9
  exports.noStaticIv = (0, eslint_devkit_1.createRule)({
18
10
  name: 'no-static-iv',
@@ -67,10 +59,8 @@ exports.noStaticIv = (0, eslint_devkit_1.createRule)({
67
59
  const { allowInTests = false } = options;
68
60
  const filename = context.filename;
69
61
  const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
70
- // Track variables assigned from randomBytes
71
62
  const randomIvVariables = new Set();
72
63
  function checkVariableDeclarator(node) {
73
- // Track: const iv = crypto.randomBytes(16)
74
64
  if (node.id.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
75
65
  node.init?.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
76
66
  const init = node.init;
@@ -88,7 +78,6 @@ exports.noStaticIv = (0, eslint_devkit_1.createRule)({
88
78
  function checkCallExpression(node) {
89
79
  if (isTestFile)
90
80
  return;
91
- // Check for createCipheriv calls
92
81
  const isCipherivCall = (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
93
82
  node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
94
83
  (node.callee.property.name === 'createCipheriv' || node.callee.property.name === 'createDecipheriv')) ||
@@ -100,14 +89,12 @@ exports.noStaticIv = (0, eslint_devkit_1.createRule)({
100
89
  }
101
90
  }
102
91
  function checkIvArgument(ivArg) {
103
- // Check for string literal IV
104
92
  if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof ivArg.value === 'string') {
105
93
  const value = ivArg.value;
106
94
  if (STATIC_IV_PATTERNS.some(p => p.test(value)) || value.length >= 8) {
107
95
  reportStaticIv(ivArg);
108
96
  }
109
97
  }
110
- // Check for Buffer.from('static')
111
98
  if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
112
99
  ivArg.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
113
100
  ivArg.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
@@ -118,7 +105,6 @@ exports.noStaticIv = (0, eslint_devkit_1.createRule)({
118
105
  if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof firstArg.value === 'string') {
119
106
  reportStaticIv(ivArg);
120
107
  }
121
- // Check for new Uint8Array([...])
122
108
  if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.ArrayExpression) {
123
109
  const allLiterals = firstArg.elements.every((el) => el?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof el.value === 'number');
124
110
  if (allLiterals) {
@@ -126,11 +112,7 @@ exports.noStaticIv = (0, eslint_devkit_1.createRule)({
126
112
  }
127
113
  }
128
114
  }
129
- // Check for variable reference that's NOT from randomBytes
130
115
  if (ivArg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
131
- // If we've tracked this variable as coming from randomBytes, it's OK
132
- // Otherwise, we can't be sure - only flag if we have evidence it's static
133
- // For now, we don't report variables as we can't always determine their source
134
116
  }
135
117
  }
136
118
  function reportStaticIv(node) {
@@ -145,4 +127,3 @@ exports.noStaticIv = (0, eslint_devkit_1.createRule)({
145
127
  };
146
128
  },
147
129
  });
148
- //# sourceMappingURL=index.js.map
@@ -1,18 +1,11 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.noTimingUnsafeCompare = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
10
5
  const DEFAULT_SECRET_PATTERNS = [
11
- // Common secret names (camelCase, snake_case, kebab-case)
12
6
  'token', 'secret', 'key', 'password', 'hash', 'signature',
13
7
  'mac', 'hmac', 'digest', 'apiKey', 'api_key', 'api-key',
14
8
  'auth', 'credential', 'bearer', 'jwt', 'csrf', 'nonce',
15
- // PII and sensitive data patterns
16
9
  'ssn', 'social_security', 'social-security',
17
10
  'pii', 'private_key', 'private-key', 'privateKey',
18
11
  'access_token', 'access-token', 'accessToken',
@@ -87,12 +80,10 @@ exports.noTimingUnsafeCompare = (0, eslint_devkit_1.createRule)({
87
80
  return false;
88
81
  }
89
82
  function checkBinaryExpression(node) {
90
- // Check for === or == comparisons
91
83
  if (node.operator !== '===' && node.operator !== '==' &&
92
84
  node.operator !== '!==' && node.operator !== '!=') {
93
85
  return;
94
86
  }
95
- // Check if either side looks like a secret
96
87
  const leftIsSecret = isSecretIdentifier(node.left);
97
88
  const rightIsSecret = isSecretIdentifier(node.right);
98
89
  if (leftIsSecret || rightIsSecret) {
@@ -102,7 +93,7 @@ exports.noTimingUnsafeCompare = (0, eslint_devkit_1.createRule)({
102
93
  suggest: [
103
94
  {
104
95
  messageId: 'useTimingSafeEqual',
105
- fix: () => null, // Complex refactoring
96
+ fix: () => null,
106
97
  },
107
98
  ],
108
99
  });
@@ -113,4 +104,3 @@ exports.noTimingUnsafeCompare = (0, eslint_devkit_1.createRule)({
113
104
  };
114
105
  },
115
106
  });
116
- //# sourceMappingURL=index.js.map
@@ -1,9 +1,4 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.noToctouVulnerability = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -86,11 +81,7 @@ exports.noToctouVulnerability = (0, eslint_devkit_2.createRule)({
86
81
  return {};
87
82
  }
88
83
  const sourceCode = context.sourceCode;
89
- /**
90
- * Check for TOCTOU patterns
91
- */
92
84
  function checkCallExpression(node) {
93
- // 1. Identify the file operation (Use)
94
85
  let useMethodName = '';
95
86
  if (node.callee.type === 'MemberExpression' && node.callee.property.type === 'Identifier') {
96
87
  const objectName = node.callee.object.type === 'Identifier' ? node.callee.object.name : '';
@@ -108,22 +99,14 @@ exports.noToctouVulnerability = (0, eslint_devkit_2.createRule)({
108
99
  const useArg = node.arguments[0];
109
100
  if (!useArg)
110
101
  return;
111
- // 2. Walk up to find the condition (Check)
112
102
  let current = node.parent;
113
103
  while (current) {
114
104
  if (current.type === 'IfStatement') {
115
- // Extract the condition node
116
105
  let condition = current.test;
117
- // Handle negated condition: if (!exists(path)) { create(path) } -> also TOCTOU but different logic?
118
- // Actually TOCTOU is usually Check(exists) -> Use(read).
119
- // If (!exists) -> create is Check -> Use.
120
- // But strict TOCTOU is checking state then acting.
121
- // If checking for negation
122
106
  if (condition.type === 'UnaryExpression' && condition.operator === '!') {
123
107
  condition = condition.argument;
124
108
  }
125
109
  if (condition.type === 'CallExpression') {
126
- // Check if it's a file check method
127
110
  let checkMethodName = '';
128
111
  if (condition.callee.type === 'MemberExpression' && condition.callee.property.type === 'Identifier') {
129
112
  checkMethodName = condition.callee.property.name;
@@ -133,15 +116,12 @@ exports.noToctouVulnerability = (0, eslint_devkit_2.createRule)({
133
116
  }
134
117
  const checkMethods = ['existsSync', 'statSync', 'accessSync', 'exists', 'stat', 'access'];
135
118
  if (checkMethods.includes(checkMethodName)) {
136
- // Compare arguments
137
119
  const checkArg = condition.arguments[0];
138
120
  if (checkArg) {
139
- // Method 1: Identifier match (same variable)
140
121
  if (checkArg.type === 'Identifier' && useArg.type === 'Identifier' && checkArg.name === useArg.name) {
141
122
  reportToctou(node);
142
123
  return;
143
124
  }
144
- // Method 2: Text match (fallback)
145
125
  const checkArgText = sourceCode.getText(checkArg).replace(/\s/g, '');
146
126
  const useArgText = sourceCode.getText(useArg).replace(/\s/g, '');
147
127
  if (checkArgText === useArgText) {
@@ -150,7 +130,6 @@ exports.noToctouVulnerability = (0, eslint_devkit_2.createRule)({
150
130
  }
151
131
  }
152
132
  }
153
- // Handle stats.isFile() / stats.isDirectory() pattern
154
133
  if (condition.callee.type === 'MemberExpression' &&
155
134
  condition.callee.property.type === 'Identifier' &&
156
135
  ['isFile', 'isDirectory'].includes(condition.callee.property.name) &&
@@ -214,4 +193,3 @@ exports.noToctouVulnerability = (0, eslint_devkit_2.createRule)({
214
193
  };
215
194
  },
216
195
  });
217
- //# sourceMappingURL=index.js.map
@@ -0,0 +1,87 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.noUnsafeBufferAlloc = void 0;
4
+ const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
+ const UNSAFE_ALLOCATORS = new Set(['allocUnsafe', 'allocUnsafeSlow']);
6
+ exports.noUnsafeBufferAlloc = (0, eslint_devkit_1.createRule)({
7
+ name: 'no-unsafe-buffer-alloc',
8
+ meta: {
9
+ type: 'problem',
10
+ hasSuggestions: true,
11
+ docs: {
12
+ url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-unsafe-buffer-alloc.md',
13
+ description: 'Disallow `Buffer.allocUnsafe()` and `Buffer.allocUnsafeSlow()`, which return uninitialized memory',
14
+ cwe: 'CWE-908',
15
+ cweJustification: 'CWE-908 (Use of Uninitialized Resource) — allocUnsafe returns a view over non-zeroed heap memory; any byte not overwritten before the buffer is read or transmitted discloses prior process memory.',
16
+ cvss: 7.5,
17
+ confidence: 'high',
18
+ },
19
+ messages: {
20
+ unsafeAlloc: (0, eslint_devkit_1.formatLLMMessage)({
21
+ icon: eslint_devkit_1.MessageIcons.SECURITY,
22
+ issueName: 'Uninitialized Buffer Allocation',
23
+ cwe: 'CWE-908',
24
+ cvss: 7.5,
25
+ description: '`Buffer.allocUnsafe(size)` returns memory that has not been zeroed. Every byte not overwritten before the buffer is read or sent leaks whatever the allocator previously stored there.',
26
+ severity: 'HIGH',
27
+ fix: 'Use `Buffer.alloc(size)` (zero-filled), or keep `allocUnsafe` only where the very next statement overwrites the whole buffer — `Buffer.allocUnsafe(size).fill(0)` is accepted by this rule.',
28
+ documentationLink: 'https://nodejs.org/api/buffer.html#static-method-bufferallocunsafesize',
29
+ }),
30
+ unsafeAllocSlow: (0, eslint_devkit_1.formatLLMMessage)({
31
+ icon: eslint_devkit_1.MessageIcons.SECURITY,
32
+ issueName: 'Uninitialized Buffer Allocation (allocUnsafeSlow)',
33
+ cwe: 'CWE-908',
34
+ cvss: 7.5,
35
+ description: '`Buffer.allocUnsafeSlow(size)` allocates outside the shared pool but is equally uninitialized — the returned bytes are whatever was last in that memory.',
36
+ severity: 'HIGH',
37
+ fix: 'Use `Buffer.alloc(size)`, or append `.fill(0)` to zero the allocation at the call site.',
38
+ documentationLink: 'https://nodejs.org/api/buffer.html#static-method-bufferallocunsafeslowsize',
39
+ }),
40
+ useSafeAlloc: 'Replace with `Buffer.alloc()` (zero-filled).',
41
+ },
42
+ schema: [],
43
+ },
44
+ defaultOptions: [],
45
+ create(context) {
46
+ function isFilledInPlace(node) {
47
+ const parent = node.parent;
48
+ if (parent?.type !== eslint_devkit_1.AST_NODE_TYPES.MemberExpression ||
49
+ parent.object !== node ||
50
+ parent.computed ||
51
+ parent.property.type !== eslint_devkit_1.AST_NODE_TYPES.Identifier ||
52
+ parent.property.name !== 'fill') {
53
+ return false;
54
+ }
55
+ const grandparent = parent.parent;
56
+ return (grandparent?.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
57
+ grandparent.callee === parent);
58
+ }
59
+ return {
60
+ CallExpression(node) {
61
+ const callee = node.callee;
62
+ if (callee.type !== eslint_devkit_1.AST_NODE_TYPES.MemberExpression ||
63
+ callee.computed ||
64
+ callee.object.type !== eslint_devkit_1.AST_NODE_TYPES.Identifier ||
65
+ callee.object.name !== 'Buffer' ||
66
+ callee.property.type !== eslint_devkit_1.AST_NODE_TYPES.Identifier ||
67
+ !UNSAFE_ALLOCATORS.has(callee.property.name)) {
68
+ return;
69
+ }
70
+ if (isFilledInPlace(node))
71
+ return;
72
+ context.report({
73
+ node,
74
+ messageId: callee.property.name === 'allocUnsafe'
75
+ ? 'unsafeAlloc'
76
+ : 'unsafeAllocSlow',
77
+ suggest: [
78
+ {
79
+ messageId: 'useSafeAlloc',
80
+ fix: (fixer) => fixer.replaceText(callee.property, 'alloc'),
81
+ },
82
+ ],
83
+ });
84
+ },
85
+ };
86
+ },
87
+ });
@@ -1,9 +1,4 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.noUnsafeDynamicRequire = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -49,14 +44,7 @@ exports.noUnsafeDynamicRequire = (0, eslint_devkit_2.createRule)({
49
44
  },
50
45
  ],
51
46
  create(context) {
52
- /**
53
- * Track variables that reference require
54
- * Maps variable name to the node where it was assigned
55
- */
56
47
  const requireVariables = new Set();
57
- /**
58
- * Check if a node is a reference to require
59
- */
60
48
  const isRequireReference = (node) => {
61
49
  if (node.type === 'Identifier' && node.name === 'require') {
62
50
  return true;
@@ -66,10 +54,6 @@ exports.noUnsafeDynamicRequire = (0, eslint_devkit_2.createRule)({
66
54
  }
67
55
  return false;
68
56
  };
69
- /**
70
- * Check if argument is dynamic (not a literal)
71
- */
72
- // oxlint-disable-next-line consistent-function-scoping
73
57
  const isDynamicArgument = (arg) => {
74
58
  if (arg.type === 'Literal')
75
59
  return false;
@@ -79,7 +63,6 @@ exports.noUnsafeDynamicRequire = (0, eslint_devkit_2.createRule)({
79
63
  };
80
64
  return {
81
65
  VariableDeclarator(node) {
82
- // Track when require is assigned to a variable
83
66
  if (node.id.type === 'Identifier' && node.init) {
84
67
  if (node.init.type === 'Identifier' && node.init.name === 'require') {
85
68
  requireVariables.add(node.id.name);
@@ -87,21 +70,17 @@ exports.noUnsafeDynamicRequire = (0, eslint_devkit_2.createRule)({
87
70
  }
88
71
  },
89
72
  CallExpression(node) {
90
- // Check for require() calls (direct or via variable)
91
73
  if (node.callee.type !== 'Identifier') {
92
74
  return;
93
75
  }
94
- // Check if callee is require or a variable that references require
95
76
  if (!isRequireReference(node.callee)) {
96
77
  return;
97
78
  }
98
- // Must have at least one argument
99
79
  if (node.arguments.length === 0)
100
80
  return;
101
81
  const firstArg = node.arguments[0];
102
82
  if (firstArg.type === 'SpreadElement')
103
83
  return;
104
- // Check if dynamic
105
84
  if (!isDynamicArgument(firstArg))
106
85
  return;
107
86
  context.report({
@@ -112,4 +91,3 @@ exports.noUnsafeDynamicRequire = (0, eslint_devkit_2.createRule)({
112
91
  };
113
92
  },
114
93
  });
115
- //# sourceMappingURL=index.js.map
@@ -1,9 +1,4 @@
1
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
2
  Object.defineProperty(exports, "__esModule", { value: true });
8
3
  exports.noWeakCipherAlgorithm = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -45,17 +40,12 @@ const WEAK_CIPHER_PATTERNS = [
45
40
  replacement: 'aes-256-gcm',
46
41
  },
47
42
  ];
48
- /**
49
- * Check if a string contains a weak cipher algorithm
50
- */
51
43
  function findWeakCipher(value, additionalPatterns) {
52
- // Check standard patterns
53
44
  for (const pattern of WEAK_CIPHER_PATTERNS) {
54
45
  if (pattern.pattern.test(value)) {
55
46
  return pattern;
56
47
  }
57
48
  }
58
- // Check additional patterns
59
49
  for (const additionalPattern of additionalPatterns) {
60
50
  const regex = new RegExp(`\\b${additionalPattern}\\b`, 'i');
61
51
  if (regex.test(value)) {
@@ -137,28 +127,20 @@ exports.noWeakCipherAlgorithm = (0, eslint_devkit_1.createRule)({
137
127
  const { additionalWeakCiphers = [], allowInTests = false, } = options;
138
128
  const filename = context.filename;
139
129
  const isTestFile = allowInTests && /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(filename);
140
- /**
141
- * Check if a call expression uses a weak cipher
142
- */
143
130
  function checkCallExpression(node) {
144
131
  if (isTestFile)
145
132
  return;
146
133
  const cipherMethods = new Set(['createCipher', 'createCipheriv', 'createDecipher', 'createDecipheriv']);
147
- // Check for crypto.createCipher*() pattern
148
134
  if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
149
135
  node.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
150
136
  cipherMethods.has(node.callee.property.name)) {
151
137
  checkCipherArgument(node);
152
138
  }
153
- // Check for standalone createCipher*() pattern
154
139
  if (node.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
155
140
  cipherMethods.has(node.callee.name)) {
156
141
  checkCipherArgument(node);
157
142
  }
158
143
  }
159
- /**
160
- * Check the algorithm argument passed to createCipher*
161
- */
162
144
  function checkCipherArgument(node) {
163
145
  const firstArg = node.arguments[0];
164
146
  if (firstArg?.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof firstArg.value === 'string') {
@@ -190,4 +172,3 @@ exports.noWeakCipherAlgorithm = (0, eslint_devkit_1.createRule)({
190
172
  };
191
173
  },
192
174
  });
193
- //# sourceMappingURL=index.js.map