eslint-plugin-node-security 4.6.0 → 4.7.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 (120) hide show
  1. package/README.md +1 -0
  2. package/package.json +5 -18
  3. package/src/index.d.ts +17 -10
  4. package/src/index.js +5 -36
  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 +4 -80
  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 +0 -13
  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 -413
  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-buffer-alloc/index.d.ts +0 -47
  101. package/src/rules/no-unsafe-buffer-alloc/index.js.map +0 -1
  102. package/src/rules/no-unsafe-dynamic-require/index.d.ts +0 -19
  103. package/src/rules/no-unsafe-dynamic-require/index.js.map +0 -1
  104. package/src/rules/no-weak-cipher-algorithm/index.d.ts +0 -25
  105. package/src/rules/no-weak-cipher-algorithm/index.js.map +0 -1
  106. package/src/rules/no-weak-hash-algorithm/index.d.ts +0 -25
  107. package/src/rules/no-weak-hash-algorithm/index.js.map +0 -1
  108. package/src/rules/no-zip-slip/index.d.ts +0 -35
  109. package/src/rules/no-zip-slip/index.js.map +0 -1
  110. package/src/rules/prefer-native-crypto/index.d.ts +0 -23
  111. package/src/rules/prefer-native-crypto/index.js.map +0 -1
  112. package/src/rules/require-dependency-integrity/index.d.ts +0 -12
  113. package/src/rules/require-dependency-integrity/index.js.map +0 -1
  114. package/src/rules/require-secure-credential-storage/index.d.ts +0 -12
  115. package/src/rules/require-secure-credential-storage/index.js.map +0 -1
  116. package/src/rules/require-secure-deletion/index.d.ts +0 -17
  117. package/src/rules/require-secure-deletion/index.js.map +0 -1
  118. package/src/rules/require-storage-encryption/index.d.ts +0 -12
  119. package/src/rules/require-storage-encryption/index.js.map +0 -1
  120. package/src/types/index.js.map +0 -1
@@ -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.detectEvalWithExpression = exports.generateRefactoringSteps = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -35,7 +30,6 @@ const EVAL_PATTERNS = [
35
30
  safeAlternative: 'Template literals or template engine',
36
31
  example: {
37
32
  bad: 'eval(\'Hello \' + userName + \'!\')',
38
- // oxlint-disable-next-line no-template-curly-in-string
39
33
  good: 'const template = `Hello ${userName}!`;'
40
34
  },
41
35
  effort: '3 minutes'
@@ -51,10 +45,6 @@ const EVAL_PATTERNS = [
51
45
  effort: '8 minutes'
52
46
  }
53
47
  ];
54
- /**
55
- * Generate refactoring steps based on pattern.
56
- * Module-scope so it is directly unit-testable (Layer-2).
57
- */
58
48
  const generateRefactoringSteps = (pattern) => {
59
49
  if (!pattern) {
60
50
  return [
@@ -80,10 +70,8 @@ const generateRefactoringSteps = (pattern) => {
80
70
  ' 3. Validate inputs are numbers',
81
71
  ' 4. Consider using a math expression parser library'
82
72
  ].join('\n');
83
- // oxlint-disable-next-line no-template-curly-in-string
84
73
  case 'template':
85
74
  return [
86
- // oxlint-disable-next-line no-template-curly-in-string
87
75
  ' 1. Use template literals: `Hello ${name}`',
88
76
  ' 2. Sanitize variables before interpolation',
89
77
  ' 3. Use a template engine like Handlebars if complex',
@@ -118,7 +106,6 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
118
106
  confidence: 'high',
119
107
  },
120
108
  messages: {
121
- // 🎯 Token optimization: 38% reduction (47→29 tokens) - compact format saves LLM processing
122
109
  evalWithExpression: (0, eslint_devkit_1.formatLLMMessage)({
123
110
  icon: eslint_devkit_1.MessageIcons.SECURITY,
124
111
  issueName: 'eval() with dynamic code',
@@ -151,9 +138,7 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
151
138
  issueName: 'Unsafe eval() for string interpolation',
152
139
  cwe: 'CWE-95',
153
140
  description: 'Use template literals instead of eval() for string interpolation',
154
- // oxlint-disable-next-line no-template-curly-in-string
155
141
  severity: 'HIGH',
156
- // oxlint-disable-next-line no-template-curly-in-string
157
142
  fix: 'Replace eval() with template literals: `Hello ${name}`',
158
143
  documentationLink: 'https://owasp.org/www-community/attacks/Code_Injection',
159
144
  }),
@@ -239,25 +224,14 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
239
224
  create(context) {
240
225
  const options = context.options[0] || {};
241
226
  const { allowLiteralStrings = false, additionalEvalFunctions = [], strategy = 'auto' } = options;
242
- /**
243
- * All functions that can execute arbitrary code
244
- * NOTE: setTimeout/setInterval are NOT eval-like - they don't execute code strings
245
- */
246
227
  const evalFunctions = new Set([
247
228
  'eval',
248
229
  'Function',
249
230
  ...additionalEvalFunctions
250
231
  ]);
251
- /**
252
- * Check if a node is a literal string (safe)
253
- */
254
- // oxlint-disable-next-line consistent-function-scoping
255
232
  const isLiteralString = (node) => {
256
233
  return node.type === 'Literal' && typeof node.value === 'string';
257
234
  };
258
- /**
259
- * Select message ID based on strategy
260
- */
261
235
  const selectStrategyMessage = (pattern) => {
262
236
  switch (strategy) {
263
237
  case 'remove':
@@ -268,7 +242,6 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
268
242
  return 'strategyValidate';
269
243
  case 'auto':
270
244
  default:
271
- // Auto mode: choose based on pattern confidence
272
245
  if (pattern && pattern.category === 'json') {
273
246
  return 'useJsonParse';
274
247
  }
@@ -278,12 +251,9 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
278
251
  if (pattern && pattern.category === 'template') {
279
252
  return 'useTemplateLiteral';
280
253
  }
281
- return 'strategyRefactor'; // Default to refactor for unknown patterns
254
+ return 'strategyRefactor';
282
255
  }
283
256
  };
284
- /**
285
- * Detect the pattern category from the expression
286
- */
287
257
  const detectPattern = (expression) => {
288
258
  for (const pattern of EVAL_PATTERNS) {
289
259
  if (new RegExp(pattern.pattern, 'i').test(expression)) {
@@ -292,31 +262,21 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
292
262
  }
293
263
  return null;
294
264
  };
295
- /**
296
- * Extract expression text for pattern analysis
297
- */
298
265
  const extractExpression = (node) => {
299
266
  const sourceCode = context.sourceCode;
300
- // Try to get the argument text
301
267
  if (node.arguments.length > 0) {
302
268
  return sourceCode.getText(node.arguments[0]);
303
269
  }
304
270
  return 'dynamic expression';
305
271
  };
306
- /**
307
- * Check call expressions for dangerous eval usage
308
- */
309
272
  const checkCallExpression = (node) => {
310
- // Check if it's a call to an eval-like function
311
273
  if (node.callee.type === 'Identifier' &&
312
274
  evalFunctions.has(node.callee.name)) {
313
- // Skip if it's a literal string and literals are allowed
314
275
  if (allowLiteralStrings &&
315
276
  node.arguments.length > 0 &&
316
277
  isLiteralString(node.arguments[0])) {
317
278
  return;
318
279
  }
319
- // Skip if it's a direct string literal (safe)
320
280
  if (node.arguments.length > 0 &&
321
281
  node.callee.name === 'eval' &&
322
282
  isLiteralString(node.arguments[0])) {
@@ -343,12 +303,11 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
343
303
  safeAlternative: pattern.safeAlternative,
344
304
  alternative: pattern.safeAlternative
345
305
  },
346
- fix: () => null // Complex refactoring, cannot auto-fix safely
306
+ fix: () => null
347
307
  }
348
308
  ] : undefined
349
309
  });
350
310
  }
351
- // Also check for Function constructor usage
352
311
  if (node.callee.type === 'NewExpression' &&
353
312
  node.callee.callee.type === 'Identifier' &&
354
313
  node.callee.callee.name === 'Function') {
@@ -373,11 +332,7 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
373
332
  });
374
333
  }
375
334
  };
376
- /**
377
- * Check NewExpression for Function constructor usage
378
- */
379
335
  const checkNewExpression = (node) => {
380
- // Check for new Function() usage
381
336
  if (node.callee.type === 'Identifier' && node.callee.name === 'Function') {
382
337
  const sourceCode = context.sourceCode;
383
338
  const expression = node.arguments.map((arg) => sourceCode.getText(arg)).join(', ');
@@ -407,4 +362,3 @@ exports.detectEvalWithExpression = (0, eslint_devkit_2.createRule)({
407
362
  };
408
363
  },
409
364
  });
410
- //# 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.detectNonLiteralFsFilename = exports.determineRiskLevel = exports.generateRefactoringSteps = void 0;
9
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
@@ -54,18 +49,9 @@ const FS_OPERATIONS = [
54
49
  effort: '15-20 minutes'
55
50
  }
56
51
  ];
57
- /**
58
- * Check if path has dangerous patterns like ../ or ..\
59
- * Module-scope so it is directly unit-testable (Layer-2).
60
- */
61
52
  const hasTraversalPatterns = (pathStr) => {
62
53
  return /\.\.[/\\]/.test(pathStr) || /^\.\.[/\\]/.test(pathStr);
63
54
  };
64
- /**
65
- * Generate refactoring steps based on the operation.
66
- * Module-scope so the default arm (methods without a dedicated entry) is
67
- * directly unit-testable (Layer-2).
68
- */
69
55
  const generateRefactoringSteps = (operation) => {
70
56
  switch (operation.method) {
71
57
  case 'readFile':
@@ -104,11 +90,6 @@ const generateRefactoringSteps = (operation) => {
104
90
  }
105
91
  };
106
92
  exports.generateRefactoringSteps = generateRefactoringSteps;
107
- /**
108
- * Determine risk level based on the operation and path.
109
- * Module-scope so the non-dangerous fallback (no FS_OPERATIONS entry sets
110
- * dangerous: false today) is directly unit-testable (Layer-2).
111
- */
112
93
  const determineRiskLevel = (operation, pathStr) => {
113
94
  if (hasTraversalPatterns(pathStr)) {
114
95
  return 'CRITICAL';
@@ -130,7 +111,6 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
130
111
  confidence: 'medium',
131
112
  },
132
113
  messages: {
133
- // 🎯 Token optimization: 39% reduction (49→30 tokens) - template variables still work
134
114
  fsPathTraversal: (0, eslint_devkit_1.formatLLMMessage)({
135
115
  icon: '🔑',
136
116
  issueName: 'Path traversal',
@@ -216,9 +196,6 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
216
196
  create(context) {
217
197
  const options = context.options[0] || {};
218
198
  const { allowLiterals = false, additionalMethods = [] } = options;
219
- /**
220
- * File system methods that can be dangerous with user input
221
- */
222
199
  const dangerousMethods = new Set([
223
200
  'readFile', 'readFileSync',
224
201
  'writeFile', 'writeFileSync',
@@ -233,48 +210,29 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
233
210
  'createReadStream', 'createWriteStream',
234
211
  ...additionalMethods
235
212
  ]);
236
- /**
237
- * Check if a node is a literal string (safe)
238
- */
239
- // oxlint-disable-next-line consistent-function-scoping
240
213
  const isLiteralString = (node) => {
241
214
  return node.type === 'Literal' && typeof node.value === 'string';
242
215
  };
243
- /**
244
- * Extract path argument from fs call.
245
- * `method` comes from checkFsCall, which already resolved the callee
246
- * shape — re-deriving it here would hide an unreachable defensive branch.
247
- */
248
216
  const extractPathArgument = (node, method) => {
249
217
  const operation = FS_OPERATIONS.find(op => op.method === method) || null;
250
- // First argument is usually the path
251
218
  const pathNode = node.arguments.length > 0 ? node.arguments[0] : null;
252
219
  const sourceCode = context.sourceCode;
253
220
  const path = pathNode ? sourceCode.getText(pathNode) : '';
254
221
  return { path, pathNode, operation };
255
222
  };
256
- /**
257
- * Determine if the path argument is potentially dangerous
258
- */
259
223
  const isDangerousPath = (pathNode, pathStr) => {
260
- // Allow literals if configured
261
224
  if (allowLiterals && pathNode && isLiteralString(pathNode)) {
262
225
  return false;
263
226
  }
264
- // Check for obvious traversal patterns in literals
265
227
  if (pathNode && isLiteralString(pathNode) && hasTraversalPatterns(pathStr)) {
266
228
  return true;
267
229
  }
268
- // SAFE: path.join(__dirname, 'literal', 'path') with all literal args
269
230
  if (pathNode && isSafePathConstruction(pathNode)) {
270
231
  return false;
271
232
  }
272
- // SAFE: Path variable inside validated if-block with startsWith check
273
233
  if (pathNode && hasPathValidation(pathNode)) {
274
234
  return false;
275
235
  }
276
- // SAFE: path.join("./base", dynamicVar) where dynamicVar has been validated
277
- // Extract dynamic identifiers from path.join/resolve calls and check their validation
278
236
  if (pathNode && pathNode.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
279
237
  const callee = pathNode.callee;
280
238
  if (callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
@@ -282,23 +240,14 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
282
240
  callee.object.name === 'path' &&
283
241
  callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
284
242
  ['join', 'resolve'].includes(callee.property.name)) {
285
- // Find any dynamic (non-literal) identifier args and check if they're validated
286
243
  const dynamicArgs = pathNode.arguments.filter((arg) => arg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && arg.name !== '__dirname');
287
244
  if (dynamicArgs.length > 0 && dynamicArgs.every((arg) => hasPathValidation(arg))) {
288
245
  return false;
289
246
  }
290
247
  }
291
248
  }
292
- // Any non-literal is dangerous
293
249
  return !pathNode || !isLiteralString(pathNode);
294
250
  };
295
- /**
296
- * Check if path is constructed safely using path.join/__dirname with literal args
297
- *
298
- * Safe patterns:
299
- * - path.join(__dirname, 'data', 'file.json')
300
- * - path.resolve(__dirname, 'uploads')
301
- */
302
251
  const isSafePathConstruction = (pathNode) => {
303
252
  if (pathNode.type !== eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
304
253
  return false;
@@ -318,44 +267,29 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
318
267
  if (args.length === 0) {
319
268
  return false;
320
269
  }
321
- // First arg should be __dirname or a literal
322
270
  const firstArg = args[0];
323
271
  const isFirstArgSafe = (firstArg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && firstArg.name === '__dirname') ||
324
272
  (firstArg.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof firstArg.value === 'string');
325
273
  if (!isFirstArgSafe) {
326
274
  return false;
327
275
  }
328
- // All remaining args should be literals
329
276
  for (let i = 1; i < args.length; i++) {
330
277
  const arg = args[i];
331
278
  if (arg.type !== eslint_devkit_1.AST_NODE_TYPES.Literal || typeof arg.value !== 'string') {
332
279
  return false;
333
280
  }
334
- // Also check for traversal patterns in literals
335
281
  if (hasTraversalPatterns(String(arg.value))) {
336
282
  return false;
337
283
  }
338
284
  }
339
285
  return true;
340
286
  };
341
- /**
342
- * Check if the path variable has been validated with startsWith()
343
- *
344
- * Safe patterns:
345
- * 1. Inside if-block: if (safePath.startsWith(SAFE_DIR)) { fs.readFileSync(safePath); }
346
- * 2. After guard clause: if (!safePath.startsWith(SAFE_DIR)) { throw }; fs.readFileSync(safePath);
347
- */
348
287
  const hasPathValidation = (pathNode) => {
349
288
  if (pathNode.type !== eslint_devkit_1.AST_NODE_TYPES.Identifier) {
350
289
  return false;
351
290
  }
352
291
  const varName = pathNode.name;
353
- // AST-based validation detection (faster than getText + regex)
354
292
  const isValidationCall = (testNode) => {
355
- // Handle negation: !path.startsWith(...). The negation flag was
356
- // tracked here but never read afterwards (CodeQL:
357
- // `js/useless-assignment-to-local`); current callers only need to
358
- // know whether the call matches a validation idiom.
359
293
  if (testNode.type === eslint_devkit_1.AST_NODE_TYPES.UnaryExpression &&
360
294
  testNode.operator === '!' &&
361
295
  testNode.argument.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
@@ -364,7 +298,6 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
364
298
  if (testNode.type !== eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
365
299
  return false;
366
300
  }
367
- // Pattern 1: varName.startsWith(...) or varName.includes(...)
368
301
  if (testNode.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
369
302
  testNode.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
370
303
  testNode.callee.object.name === varName &&
@@ -373,22 +306,18 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
373
306
  testNode.callee.property.name === 'includes')) {
374
307
  return true;
375
308
  }
376
- // Pattern 2: ALLOWED_FILES.includes(varName) - allowlist validation
377
309
  if (testNode.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
378
310
  testNode.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
379
311
  testNode.callee.property.name === 'includes') {
380
- // Check if varName is in the arguments
381
312
  for (const arg of testNode.arguments) {
382
313
  if (arg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && arg.name === varName) {
383
314
  return true;
384
315
  }
385
316
  }
386
317
  }
387
- // Pattern 3: /regex/.test(varName) - regex validation
388
318
  if (testNode.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
389
319
  testNode.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
390
320
  testNode.callee.property.name === 'test') {
391
- // Check if varName is in the arguments
392
321
  for (const arg of testNode.arguments) {
393
322
  if (arg.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && arg.name === varName) {
394
323
  return true;
@@ -405,17 +334,14 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
405
334
  return consequent.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement ||
406
335
  consequent.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement;
407
336
  };
408
- // Walk up to find enclosing IfStatement or BlockStatement
409
337
  let current = pathNode.parent;
410
338
  let foundFunctionBody = false;
411
339
  while (current && !foundFunctionBody) {
412
- // Check 1: Inside an if-block with validation
413
340
  if (current.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
414
341
  if (isValidationCall(current.test)) {
415
342
  return true;
416
343
  }
417
344
  }
418
- // Check 2: In a function body, look for preceding sibling if-statements with guard clause
419
345
  if (current.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement && current.parent && (current.parent.type === eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration ||
420
346
  current.parent.type === eslint_devkit_1.AST_NODE_TYPES.FunctionExpression ||
421
347
  current.parent.type === eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression)) {
@@ -430,7 +356,6 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
430
356
  }
431
357
  return false;
432
358
  });
433
- // Look at preceding statements for validation patterns with early exit
434
359
  for (let i = 0; i < nodeIndex; i++) {
435
360
  const stmt = blockBody[i];
436
361
  if (stmt.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement &&
@@ -444,11 +369,7 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
444
369
  }
445
370
  return false;
446
371
  };
447
- /**
448
- * Check fs method calls for path traversal vulnerabilities
449
- */
450
372
  const checkFsCall = (node) => {
451
- // Check if it's an fs method call
452
373
  if (node.callee.type !== 'MemberExpression' ||
453
374
  node.callee.object.type !== 'Identifier' ||
454
375
  node.callee.object.name !== 'fs' ||
@@ -456,13 +377,11 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
456
377
  return;
457
378
  }
458
379
  const methodName = node.callee.property.name;
459
- // Skip if not a dangerous method
460
380
  if (!dangerousMethods.has(methodName)) {
461
381
  return;
462
382
  }
463
383
  const { path, pathNode, operation } = extractPathArgument(node, methodName);
464
384
  const method = methodName;
465
- // Check if the path argument is dangerous
466
385
  if (!isDangerousPath(pathNode, path)) {
467
386
  return;
468
387
  }
@@ -510,4 +429,3 @@ exports.detectNonLiteralFsFilename = (0, eslint_devkit_2.createRule)({
510
429
  };
511
430
  },
512
431
  });
513
- //# sourceMappingURL=index.js.map
@@ -1,16 +1,6 @@
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.detectSuspiciousDependencies = void 0;
9
- /**
10
- * @fileoverview Detect potential typosquatting in dependencies
11
- * @see https://owasp.org/www-project-mobile-top-10/
12
- * @see https://cwe.mitre.org/data/definitions/506.html
13
- */
14
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
15
5
  exports.detectSuspiciousDependencies = (0, eslint_devkit_1.createRule)({
16
6
  name: 'detect-suspicious-dependencies',
@@ -38,7 +28,6 @@ exports.detectSuspiciousDependencies = (0, eslint_devkit_1.createRule)({
38
28
  defaultOptions: [],
39
29
  create(context) {
40
30
  const popularPackages = ['react', 'lodash', 'express', 'axios', 'webpack'];
41
- // oxlint-disable-next-line consistent-function-scoping
42
31
  function levenshtein(a, b) {
43
32
  const matrix = [];
44
33
  for (let i = 0; i <= b.length; i++) {
@@ -78,4 +67,3 @@ exports.detectSuspiciousDependencies = (0, eslint_devkit_1.createRule)({
78
67
  };
79
68
  },
80
69
  });
81
- //# sourceMappingURL=index.js.map
@@ -1,16 +1,6 @@
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.lockFile = void 0;
9
- /**
10
- * @fileoverview Ensure package lock file exists
11
- * @see https://owasp.org/www-project-mobile-top-10/
12
- * @see https://cwe.mitre.org/data/definitions/829.html
13
- */
14
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
15
5
  exports.lockFile = (0, eslint_devkit_1.createRule)({
16
6
  name: 'lock-file',
@@ -50,7 +40,6 @@ exports.lockFile = (0, eslint_devkit_1.createRule)({
50
40
  create(context) {
51
41
  const fs = require('node:fs');
52
42
  const path = require('node:path');
53
- // Check once per file
54
43
  let checked = false;
55
44
  const options = context.options[0] || {};
56
45
  const userPackageManager = options.packageManager;
@@ -59,10 +48,6 @@ exports.lockFile = (0, eslint_devkit_1.createRule)({
59
48
  yarn: 'yarn.lock',
60
49
  pnpm: 'pnpm-lock.yaml',
61
50
  };
62
- // When no packageManager is configured, accept ANY of the three common
63
- // lock files. Otherwise look only for the configured one. This avoids
64
- // firing on every file in a pnpm/yarn monorepo just because the rule
65
- // defaulted to looking for package-lock.json.
66
51
  const targetLockFiles = userPackageManager
67
52
  ? [lockFiles[userPackageManager]]
68
53
  : Object.values(lockFiles);
@@ -75,10 +60,8 @@ exports.lockFile = (0, eslint_devkit_1.createRule)({
75
60
  if (checked)
76
61
  return;
77
62
  checked = true;
78
- // Find project root (simplified)
79
63
  let dir = path.dirname(context.filename);
80
64
  let found = false;
81
- // Search up to 10 levels for any acceptable lock file
82
65
  for (let i = 0; i < 10; i++) {
83
66
  for (const lf of targetLockFiles) {
84
67
  if (fs.existsSync(path.join(dir, lf))) {
@@ -107,4 +90,3 @@ exports.lockFile = (0, eslint_devkit_1.createRule)({
107
90
  };
108
91
  },
109
92
  });
110
- //# sourceMappingURL=index.js.map
@@ -1,21 +1,6 @@
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.noArbitraryFileAccess = void 0;
9
- /**
10
- * @fileoverview Prevent file access from user input
11
- *
12
- * False Positive Reduction:
13
- * This rule detects safe patterns including:
14
- * - path.basename() sanitization
15
- * - path.join() with validated base directories
16
- * - startsWith() validation guards
17
- * - Early-return throw patterns
18
- */
19
4
  const eslint_devkit_1 = require("@interlace/eslint-devkit");
20
5
  exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
21
6
  name: 'no-arbitrary-file-access',
@@ -49,20 +34,14 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
49
34
  const fsReadMethods = ['readFile', 'readFileSync', 'readdir', 'readdirSync', 'stat', 'statSync'];
50
35
  const fsWriteMethods = ['writeFile', 'writeFileSync', 'appendFile', 'appendFileSync'];
51
36
  const userInputSources = new Set(['req', 'request', 'params', 'query', 'body']);
52
- // Track variables that have been sanitized with path.basename()
53
37
  const sanitizedVariables = new Set();
54
- // Track variables that have been validated with startsWith() guards
55
38
  const validatedVariables = new Set();
56
- /**
57
- * Check if a variable is assigned from path.basename() or path.join() with basename
58
- */
59
39
  function checkVariableDeclaration(node) {
60
40
  if (node.id.type !== 'Identifier' || !node.init) {
61
41
  return;
62
42
  }
63
43
  const varName = node.id.name;
64
44
  const init = node.init;
65
- // Check for path.basename() assignment
66
45
  if (init.type === 'CallExpression' &&
67
46
  init.callee.type === 'MemberExpression' &&
68
47
  init.callee.object.type === 'Identifier' &&
@@ -71,16 +50,13 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
71
50
  init.callee.property.name === 'basename') {
72
51
  sanitizedVariables.add(varName);
73
52
  }
74
- // Check for path.join() with a sanitized variable or literal base
75
53
  if (init.type === 'CallExpression' &&
76
54
  init.callee.type === 'MemberExpression' &&
77
55
  init.callee.object.type === 'Identifier' &&
78
56
  init.callee.object.name === 'path' &&
79
57
  init.callee.property.type === 'Identifier' &&
80
58
  init.callee.property.name === 'join') {
81
- // Check if any argument is a sanitized variable
82
59
  const hasSanitizedArg = init.arguments.some((arg) => arg.type === 'Identifier' && sanitizedVariables.has(arg.name));
83
- // Check if first arg is a safe base (literal or known safe variable)
84
60
  const firstArg = init.arguments[0];
85
61
  const hasSafeBase = firstArg && (firstArg.type === 'Literal' ||
86
62
  (firstArg.type === 'Identifier' && /^(SAFE|BASE|ROOT|UPLOAD|PUBLIC)/i.test(firstArg.name)));
@@ -89,32 +65,19 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
89
65
  }
90
66
  }
91
67
  }
92
- /**
93
- * Check if there's a startsWith() guard validation for this variable
94
- * Looks for patterns like:
95
- * if (!path.startsWith(baseDir)) { throw ... }
96
- * if (!path.startsWith(baseDir)) { return ... }
97
- */
98
68
  function hasStartsWithGuard(node, varName) {
99
- // Already validated
100
69
  if (validatedVariables.has(varName)) {
101
70
  return true;
102
71
  }
103
- // Walk up to find the containing block or function
104
72
  let current = node.parent;
105
73
  while (current) {
106
- // If we've reached a function body or block, search its statements
107
74
  if (current.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement) {
108
75
  const statements = current.body;
109
- // Look for IF statements in this block that validate our variable
110
76
  for (const stmt of statements) {
111
77
  if (stmt.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
112
78
  const testText = sourceCode.getText(stmt.test).toLowerCase();
113
- // Check for startsWith() validation pattern with our variable
114
79
  if (testText.includes('startswith') && testText.includes(varName.toLowerCase())) {
115
- // Check if this is a guard clause (negated condition with throw/return)
116
80
  const consequent = stmt.consequent;
117
- // Handle block statement: if (...) { throw/return; }
118
81
  if (consequent.type === eslint_devkit_1.AST_NODE_TYPES.BlockStatement && consequent.body.length > 0) {
119
82
  const firstStmt = consequent.body[0];
120
83
  if (firstStmt.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement || firstStmt.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement) {
@@ -122,7 +85,6 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
122
85
  return true;
123
86
  }
124
87
  }
125
- // Handle direct statement: if (...) throw/return;
126
88
  if (consequent.type === eslint_devkit_1.AST_NODE_TYPES.ThrowStatement || consequent.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement) {
127
89
  validatedVariables.add(varName);
128
90
  return true;
@@ -131,7 +93,6 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
131
93
  }
132
94
  }
133
95
  }
134
- // Also check if current IS an if statement (when node is inside the consequent)
135
96
  if (current.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
136
97
  const testText = sourceCode.getText(current.test).toLowerCase();
137
98
  if (testText.includes('startswith') && testText.includes(varName.toLowerCase())) {
@@ -143,52 +104,40 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
143
104
  }
144
105
  return false;
145
106
  }
146
- /**
147
- * Check if a variable comes from a sanitized/validated source
148
- */
149
107
  function isVariableSafe(varName, node) {
150
- // Already tracked as sanitized
151
108
  if (sanitizedVariables.has(varName)) {
152
109
  return true;
153
110
  }
154
- // Has startsWith guard validation
155
111
  if (hasStartsWithGuard(node, varName)) {
156
112
  return true;
157
113
  }
158
- // Check naming conventions that suggest safety
159
114
  if (/^(safe|sanitized|validated|clean)/i.test(varName)) {
160
115
  return true;
161
116
  }
162
117
  return false;
163
118
  }
164
119
  return {
165
- // Track variable declarations for sanitization patterns
166
120
  VariableDeclarator(node) {
167
121
  checkVariableDeclaration(node);
168
122
  },
169
123
  CallExpression(node) {
170
- // Detect fs.* with user input
171
124
  if (node.callee.type === 'MemberExpression' &&
172
125
  node.callee.object.type === 'Identifier' &&
173
126
  node.callee.object.name === 'fs' &&
174
127
  node.callee.property.type === 'Identifier' &&
175
128
  [...fsReadMethods, ...fsWriteMethods].includes(node.callee.property.name)) {
176
129
  const pathArg = node.arguments[0];
177
- // Skip if path is a literal (safe)
178
130
  if (pathArg && pathArg.type === 'Literal') {
179
131
  return;
180
132
  }
181
- // Check if path is a variable
182
133
  if (pathArg && pathArg.type === 'Identifier') {
183
134
  const varName = pathArg.name;
184
- // Skip if variable is sanitized or validated
185
135
  if (isVariableSafe(varName, node)) {
186
136
  return;
187
137
  }
188
138
  report(node);
189
139
  return;
190
140
  }
191
- // Flag if path is from a member expression (user input sources)
192
141
  if (pathArg?.type === 'MemberExpression' &&
193
142
  pathArg.object.type === 'Identifier') {
194
143
  const objName = pathArg.object.name.toLowerCase();
@@ -201,4 +150,3 @@ exports.noArbitraryFileAccess = (0, eslint_devkit_1.createRule)({
201
150
  };
202
151
  },
203
152
  });
204
- //# sourceMappingURL=index.js.map