eslint-plugin-node-security 4.8.0 → 4.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/package.json +2 -2
  2. package/src/index.js +1 -95
  3. package/src/oxlint.js +1 -3
  4. package/src/rules/detect-child-process/index.js +1 -625
  5. package/src/rules/detect-eval-with-expression/index.js +1 -364
  6. package/src/rules/detect-non-literal-fs-filename/index.js +1 -516
  7. package/src/rules/detect-suspicious-dependencies/index.js +1 -69
  8. package/src/rules/lock-file/index.js +1 -92
  9. package/src/rules/no-arbitrary-file-access/index.js +1 -152
  10. package/src/rules/no-buffer-overread/index.js +1 -543
  11. package/src/rules/no-cryptojs/index.js +1 -99
  12. package/src/rules/no-cryptojs-weak-random/index.js +1 -103
  13. package/src/rules/no-data-in-temp-storage/index.js +1 -85
  14. package/src/rules/no-deprecated-buffer/index.js +1 -84
  15. package/src/rules/no-deprecated-cipher-method/index.js +1 -112
  16. package/src/rules/no-dynamic-algorithm-selection/index.js +1 -72
  17. package/src/rules/no-dynamic-command-string/index.js +1 -201
  18. package/src/rules/no-dynamic-dependency-loading/index.js +1 -45
  19. package/src/rules/no-dynamic-require/index.js +1 -96
  20. package/src/rules/no-ecb-mode/index.js +1 -108
  21. package/src/rules/no-insecure-key-derivation/index.js +1 -109
  22. package/src/rules/no-insecure-rsa-padding/index.js +1 -104
  23. package/src/rules/no-math-random-crypto/index.js +1 -192
  24. package/src/rules/no-self-signed-certs/index.js +1 -110
  25. package/src/rules/no-sha1-hash/index.js +1 -121
  26. package/src/rules/no-shell-injection/index.js +1 -68
  27. package/src/rules/no-ssrf/index.js +1 -221
  28. package/src/rules/no-static-iv/index.js +1 -129
  29. package/src/rules/no-timing-unsafe-compare/index.js +1 -106
  30. package/src/rules/no-toctou-vulnerability/index.js +1 -195
  31. package/src/rules/no-unsafe-buffer-alloc/index.js +1 -87
  32. package/src/rules/no-unsafe-dynamic-require/index.js +1 -93
  33. package/src/rules/no-weak-cipher-algorithm/index.js +1 -174
  34. package/src/rules/no-weak-hash-algorithm/index.js +1 -199
  35. package/src/rules/no-zip-slip/index.js +1 -410
  36. package/src/rules/prefer-native-crypto/index.js +1 -119
  37. package/src/rules/require-dependency-integrity/index.js +1 -62
  38. package/src/rules/require-secure-credential-storage/index.js +1 -45
  39. package/src/rules/require-secure-deletion/index.js +1 -82
  40. package/src/rules/require-storage-encryption/index.js +1 -45
  41. package/src/types/index.js +1 -2
@@ -1,543 +1 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.noBufferOverread = void 0;
4
- const eslint_devkit_1 = require("@interlace/eslint-devkit");
5
- exports.noBufferOverread = (0, eslint_devkit_1.createRule)({
6
- name: 'no-buffer-overread',
7
- meta: {
8
- type: 'problem',
9
- docs: {
10
- url: 'https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-buffer-overread.md',
11
- description: 'Detects buffer access beyond bounds',
12
- cwe: 'CWE-126',
13
- },
14
- hasSuggestions: true,
15
- messages: {
16
- bufferOverread: (0, eslint_devkit_1.formatLLMMessage)({
17
- icon: eslint_devkit_1.MessageIcons.SECURITY,
18
- issueName: 'Buffer Overread',
19
- cwe: 'CWE-126',
20
- description: 'Buffer access beyond allocated bounds',
21
- severity: '{{severity}}',
22
- fix: '{{safeAlternative}}',
23
- documentationLink: 'https://cwe.mitre.org/data/definitions/126.html',
24
- }),
25
- unsafeBufferAccess: (0, eslint_devkit_1.formatLLMMessage)({
26
- icon: eslint_devkit_1.MessageIcons.SECURITY,
27
- issueName: 'Unsafe Buffer Access',
28
- cwe: 'CWE-126',
29
- description: 'Buffer accessed without bounds validation',
30
- severity: 'HIGH',
31
- fix: 'Add bounds check before buffer access',
32
- documentationLink: 'https://nodejs.org/api/buffer.html',
33
- }),
34
- missingBoundsCheck: (0, eslint_devkit_1.formatLLMMessage)({
35
- icon: eslint_devkit_1.MessageIcons.SECURITY,
36
- issueName: 'Missing Bounds Check',
37
- cwe: 'CWE-126',
38
- description: 'Buffer operation missing bounds validation',
39
- severity: 'MEDIUM',
40
- fix: 'Validate indices before buffer operations',
41
- documentationLink: 'https://cwe.mitre.org/data/definitions/126.html',
42
- }),
43
- negativeBufferIndex: (0, eslint_devkit_1.formatLLMMessage)({
44
- icon: eslint_devkit_1.MessageIcons.SECURITY,
45
- issueName: 'Negative Buffer Index',
46
- cwe: 'CWE-126',
47
- description: 'Negative index used for buffer access',
48
- severity: 'MEDIUM',
49
- fix: 'Ensure buffer indices are non-negative',
50
- documentationLink: 'https://nodejs.org/api/buffer.html',
51
- }),
52
- userControlledBufferIndex: (0, eslint_devkit_1.formatLLMMessage)({
53
- icon: eslint_devkit_1.MessageIcons.SECURITY,
54
- issueName: 'User Controlled Buffer Index',
55
- cwe: 'CWE-126',
56
- description: 'Buffer accessed with user-controlled index',
57
- severity: 'HIGH',
58
- fix: 'Validate user input before using as buffer index',
59
- documentationLink: 'https://cwe.mitre.org/data/definitions/126.html',
60
- }),
61
- unsafeBufferSlice: (0, eslint_devkit_1.formatLLMMessage)({
62
- icon: eslint_devkit_1.MessageIcons.SECURITY,
63
- issueName: 'Unsafe Buffer Slice',
64
- cwe: 'CWE-126',
65
- description: 'Buffer slice with unvalidated indices',
66
- severity: 'MEDIUM',
67
- fix: 'Validate slice start/end indices',
68
- documentationLink: 'https://nodejs.org/api/buffer.html#bufslicestart-end',
69
- }),
70
- bufferLengthNotChecked: (0, eslint_devkit_1.formatLLMMessage)({
71
- icon: eslint_devkit_1.MessageIcons.SECURITY,
72
- issueName: 'Buffer Length Not Checked',
73
- cwe: 'CWE-126',
74
- description: 'Buffer length not validated before access',
75
- severity: 'MEDIUM',
76
- fix: 'Check buffer.length before operations',
77
- documentationLink: 'https://nodejs.org/api/buffer.html#buflength',
78
- }),
79
- useSafeBufferAccess: (0, eslint_devkit_1.formatLLMMessage)({
80
- icon: eslint_devkit_1.MessageIcons.INFO,
81
- issueName: 'Use Safe Buffer Access',
82
- description: 'Use bounds-checked buffer access methods',
83
- severity: 'LOW',
84
- fix: 'Use buffer.read*() with offset validation or safe wrapper functions',
85
- documentationLink: 'https://nodejs.org/api/buffer.html',
86
- }),
87
- validateBufferIndices: (0, eslint_devkit_1.formatLLMMessage)({
88
- icon: eslint_devkit_1.MessageIcons.INFO,
89
- issueName: 'Validate Buffer Indices',
90
- description: 'Validate buffer indices before use',
91
- severity: 'LOW',
92
- fix: 'Check 0 <= index < buffer.length',
93
- documentationLink: 'https://cwe.mitre.org/data/definitions/126.html',
94
- }),
95
- checkBufferBounds: (0, eslint_devkit_1.formatLLMMessage)({
96
- icon: eslint_devkit_1.MessageIcons.INFO,
97
- issueName: 'Check Buffer Bounds',
98
- description: 'Always check buffer bounds',
99
- severity: 'LOW',
100
- fix: 'Validate buffer operations against buffer.length',
101
- documentationLink: 'https://nodejs.org/api/buffer.html#buflength',
102
- }),
103
- strategyBoundsChecking: (0, eslint_devkit_1.formatLLMMessage)({
104
- icon: eslint_devkit_1.MessageIcons.STRATEGY,
105
- issueName: 'Bounds Checking Strategy',
106
- description: 'Implement comprehensive bounds checking',
107
- severity: 'LOW',
108
- fix: 'Validate all buffer indices and lengths before operations',
109
- documentationLink: 'https://cwe.mitre.org/data/definitions/126.html',
110
- }),
111
- strategyInputValidation: (0, eslint_devkit_1.formatLLMMessage)({
112
- icon: eslint_devkit_1.MessageIcons.STRATEGY,
113
- issueName: 'Input Validation Strategy',
114
- description: 'Validate user input used as buffer indices',
115
- severity: 'LOW',
116
- fix: 'Sanitize and validate all user input before buffer operations',
117
- documentationLink: 'https://nodejs.org/api/buffer.html',
118
- }),
119
- strategySafeBuffers: (0, eslint_devkit_1.formatLLMMessage)({
120
- icon: eslint_devkit_1.MessageIcons.STRATEGY,
121
- issueName: 'Safe Buffer Strategy',
122
- description: 'Use safe buffer wrapper libraries',
123
- severity: 'LOW',
124
- fix: 'Use libraries that provide bounds-checked buffer operations',
125
- documentationLink: 'https://www.npmjs.com/package/safe-buffer',
126
- })
127
- },
128
- schema: [
129
- {
130
- type: 'object',
131
- properties: {
132
- bufferMethods: {
133
- type: 'array',
134
- items: { type: 'string' },
135
- default: ['readUInt8', 'readUInt16LE', 'readUInt32LE', 'readInt8', 'readInt16LE', 'readInt32LE', 'writeUInt8', 'writeUInt16LE', 'writeUInt32LE', 'slice', 'copy'], description: 'Buffer read/write methods checked for bounds'
136
- },
137
- boundsCheckFunctions: {
138
- type: 'array',
139
- items: { type: 'string' },
140
- default: ['validateIndex', 'checkBounds', 'safeIndex', 'validateBufferIndex'], description: 'Function names that count as a bounds check'
141
- },
142
- bufferTypes: {
143
- type: 'array',
144
- items: { type: 'string' },
145
- default: ['Buffer', 'Uint8Array', 'ArrayBuffer', 'DataView'], description: 'Constructor names treated as buffer types'
146
- },
147
- trustedSanitizers: {
148
- type: 'array',
149
- items: { type: 'string' },
150
- default: [],
151
- description: 'Additional function names to consider as buffer index validators',
152
- },
153
- trustedAnnotations: {
154
- type: 'array',
155
- items: { type: 'string' },
156
- default: [],
157
- description: 'Additional JSDoc annotations to consider as safe markers',
158
- },
159
- strictMode: {
160
- type: 'boolean',
161
- default: false,
162
- description: 'Disable all false positive detection (strict mode)',
163
- },
164
- },
165
- additionalProperties: false,
166
- },
167
- ],
168
- },
169
- defaultOptions: [
170
- {
171
- bufferMethods: ['readUInt8', 'readUInt16LE', 'readUInt32LE', 'readInt8', 'readInt16LE', 'readInt32LE', 'writeUInt8', 'writeUInt16LE', 'writeUInt32LE', 'slice', 'copy'],
172
- boundsCheckFunctions: ['validateIndex', 'checkBounds', 'safeIndex', 'validateBufferIndex'],
173
- bufferTypes: ['Buffer', 'Uint8Array', 'ArrayBuffer', 'DataView'],
174
- trustedSanitizers: [],
175
- trustedAnnotations: [],
176
- strictMode: false,
177
- },
178
- ],
179
- create(context) {
180
- const options = context.options[0] || {};
181
- const { bufferMethods = ['readUInt8', 'readUInt16LE', 'readUInt32LE', 'readInt8', 'readInt16LE', 'readInt32LE', 'writeUInt8', 'writeUInt16LE', 'writeUInt32LE', 'slice', 'copy'], boundsCheckFunctions = ['validateIndex', 'checkBounds', 'safeIndex', 'validateBufferIndex'], bufferTypes = ['Buffer', 'Uint8Array', 'ArrayBuffer', 'DataView'], trustedSanitizers = [], trustedAnnotations = [], strictMode = false, } = options;
182
- const sourceCode = context.sourceCode;
183
- const filename = context.filename;
184
- const safetyChecker = (0, eslint_devkit_1.createSafetyChecker)({
185
- trustedSanitizers,
186
- trustedAnnotations,
187
- trustedOrmPatterns: [],
188
- strictMode,
189
- });
190
- const bufferTypesSet = new Set(bufferTypes.map(t => t.toLowerCase()));
191
- const userControlledKeywords = new Set(['req', 'request', 'query', 'params', 'input', 'user', 'offset', 'index', 'body']);
192
- const bufferVars = new Set();
193
- const isBufferType = (varName) => {
194
- if (bufferVars.has(varName))
195
- return true;
196
- const lowerName = varName.toLowerCase();
197
- for (const type of bufferTypesSet) {
198
- if (lowerName.includes(type))
199
- return true;
200
- }
201
- if (lowerName === 'buf' || lowerName === 'bytes')
202
- return true;
203
- return false;
204
- };
205
- const isUserControlledIndex = (indexNode) => {
206
- if (indexNode.type === 'MemberExpression') {
207
- let walker = indexNode;
208
- while (walker.type === 'MemberExpression') {
209
- walker = walker.object;
210
- }
211
- if (walker.type === 'Identifier') {
212
- const root = walker.name.toLowerCase();
213
- if (['req', 'request', 'event', 'ctx', 'context'].includes(root)) {
214
- return true;
215
- }
216
- for (const keyword of userControlledKeywords) {
217
- if (root.includes(keyword))
218
- return true;
219
- }
220
- }
221
- }
222
- if (indexNode.type === 'Identifier') {
223
- const varName = indexNode.name.toLowerCase();
224
- for (const keyword of userControlledKeywords) {
225
- if (varName.includes(keyword))
226
- return true;
227
- }
228
- let currentScope = sourceCode.getScope(indexNode);
229
- let variable = null;
230
- while (currentScope) {
231
- variable = currentScope.variables.find(v => v.name === indexNode.name) || null;
232
- if (variable)
233
- break;
234
- currentScope = currentScope.upper;
235
- }
236
- if (variable && variable.defs.length > 0) {
237
- const def = variable.defs[0];
238
- if (def.type === 'Variable' && def.node.init) {
239
- const init = def.node.init;
240
- if (init.type === 'MemberExpression') {
241
- const objectText = sourceCode.getText(init.object).toLowerCase();
242
- const propertyText = sourceCode.getText(init.property).toLowerCase();
243
- const keywords = ['req', 'request', 'query', 'params', 'input', 'user', 'body'];
244
- if (keywords.some(k => objectText.includes(k) || propertyText.includes(k))) {
245
- return true;
246
- }
247
- }
248
- if (init.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
249
- const typeConversionFunctions = ['number', 'parseint', 'parsefloat', 'string', 'boolean'];
250
- let isTypeConversion = false;
251
- if (init.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
252
- isTypeConversion = typeConversionFunctions.includes(init.callee.name.toLowerCase());
253
- }
254
- if (isTypeConversion && init.arguments.length > 0) {
255
- return isUserControlledIndex(init.arguments[0]);
256
- }
257
- }
258
- if (init.type === 'Identifier' && init.name !== indexNode.name) {
259
- return isUserControlledIndex(init);
260
- }
261
- }
262
- }
263
- }
264
- if (indexNode.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
265
- const typeConversionFunctions = ['Number', 'parseInt', 'parseFloat', 'String', 'Boolean'];
266
- if (indexNode.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
267
- typeConversionFunctions.includes(indexNode.callee.name)) {
268
- for (const arg of indexNode.arguments) {
269
- if (isUserControlledIndex(arg)) {
270
- return true;
271
- }
272
- }
273
- }
274
- }
275
- if (indexNode.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression) {
276
- const text = sourceCode.getText(indexNode).toLowerCase();
277
- const keywords = ['req.', 'request.', 'query.', 'params.', 'body.', 'input.', 'user.'];
278
- if (keywords.some(k => text.includes(k))) {
279
- return true;
280
- }
281
- }
282
- return false;
283
- };
284
- const isIndexValidated = (indexNode) => {
285
- if (indexNode.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof indexNode.value === 'number') {
286
- return indexNode.value >= 0;
287
- }
288
- if (indexNode.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
289
- let current = indexNode;
290
- while (current) {
291
- if (current.type === eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator &&
292
- current.id.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
293
- current.id.name === indexNode.name &&
294
- current.init) {
295
- const init = current.init;
296
- if (init.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
297
- init.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
298
- boundsCheckFunctions.includes(init.callee.name)) {
299
- return true;
300
- }
301
- if (init.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
302
- init.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
303
- init.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
304
- init.callee.object.name === 'Math' &&
305
- init.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
306
- (init.callee.property.name === 'min' || init.callee.property.name === 'max')) {
307
- return true;
308
- }
309
- break;
310
- }
311
- if (current.type === eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration ||
312
- current.type === eslint_devkit_1.AST_NODE_TYPES.FunctionExpression ||
313
- current.type === eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression) {
314
- const params = current.params;
315
- for (const param of params) {
316
- if (param.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && param.name === indexNode.name) {
317
- return true;
318
- }
319
- }
320
- }
321
- current = current.parent;
322
- }
323
- }
324
- if (indexNode.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
325
- indexNode.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
326
- boundsCheckFunctions.includes(indexNode.callee.name)) {
327
- return true;
328
- }
329
- return false;
330
- };
331
- const hasBoundsCheck = (bufferName, indexNode) => {
332
- let current = indexNode;
333
- while (current) {
334
- if (current.type === eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration ||
335
- current.type === eslint_devkit_1.AST_NODE_TYPES.FunctionExpression ||
336
- current.type === eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression) {
337
- break;
338
- }
339
- if (current.type === eslint_devkit_1.AST_NODE_TYPES.IfStatement) {
340
- const condition = current.test;
341
- const conditionText = sourceCode.getText(condition).toLowerCase();
342
- if (conditionText.includes(`${bufferName}.length`) &&
343
- (conditionText.includes('<') || conditionText.includes('<=') ||
344
- conditionText.includes('>') || conditionText.includes('>=') ||
345
- conditionText.includes('&&') || conditionText.includes('||'))) {
346
- return true;
347
- }
348
- }
349
- if (current.type === eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration) {
350
- for (const declarator of current.declarations) {
351
- if (declarator.init) {
352
- const initText = sourceCode.getText(declarator.init).toLowerCase();
353
- if (initText.includes(`${bufferName}.length`) &&
354
- (initText.includes('math.min') || initText.includes('math.max') ||
355
- initText.includes('mathmin') || initText.includes('mathmax'))) {
356
- return true;
357
- }
358
- }
359
- }
360
- }
361
- if (current.type === eslint_devkit_1.AST_NODE_TYPES.ReturnStatement && current.argument) {
362
- const returnText = sourceCode.getText(current.argument).toLowerCase();
363
- if (returnText.includes(`${bufferName}.length`)) {
364
- return true;
365
- }
366
- }
367
- current = current.parent;
368
- }
369
- return false;
370
- };
371
- const couldBeNegative = (indexNode) => {
372
- if (indexNode.type === eslint_devkit_1.AST_NODE_TYPES.Literal && typeof indexNode.value === 'number') {
373
- return indexNode.value < 0;
374
- }
375
- if (indexNode.type === eslint_devkit_1.AST_NODE_TYPES.UnaryExpression &&
376
- indexNode.operator === '-' &&
377
- indexNode.argument.type === eslint_devkit_1.AST_NODE_TYPES.Literal &&
378
- typeof indexNode.argument.value === 'number') {
379
- return true;
380
- }
381
- if (indexNode.type === eslint_devkit_1.AST_NODE_TYPES.BinaryExpression && indexNode.operator === '-') {
382
- return true;
383
- }
384
- if (indexNode.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
385
- let current = indexNode;
386
- while (current) {
387
- if (current.type === eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator && current.init) {
388
- if (current.init.type === eslint_devkit_1.AST_NODE_TYPES.Literal &&
389
- typeof current.init.value === 'number' &&
390
- current.init.value < 0) {
391
- return true;
392
- }
393
- if (current.init.type === eslint_devkit_1.AST_NODE_TYPES.UnaryExpression &&
394
- current.init.operator === '-' &&
395
- current.init.argument.type === eslint_devkit_1.AST_NODE_TYPES.Literal &&
396
- typeof current.init.argument.value === 'number') {
397
- return true;
398
- }
399
- }
400
- current = current.parent;
401
- }
402
- }
403
- return false;
404
- };
405
- return {
406
- VariableDeclarator(node) {
407
- if (node.id.type === eslint_devkit_1.AST_NODE_TYPES.Identifier && node.init) {
408
- const varName = node.id.name;
409
- if (node.init.type === eslint_devkit_1.AST_NODE_TYPES.NewExpression &&
410
- node.init.callee.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
411
- bufferTypes.includes(node.init.callee.name)) {
412
- bufferVars.add(varName);
413
- }
414
- if (node.init.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression &&
415
- node.init.callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
416
- node.init.callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
417
- node.init.callee.object.name === 'Buffer' &&
418
- node.init.callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
419
- ['from', 'alloc', 'allocUnsafe'].includes(node.init.callee.property.name)) {
420
- bufferVars.add(varName);
421
- }
422
- if (node.init.type === eslint_devkit_1.AST_NODE_TYPES.CallExpression) {
423
- const callee = node.init.callee;
424
- if (callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
425
- callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
426
- bufferMethods.includes(callee.property.name)) {
427
- bufferVars.add(varName);
428
- }
429
- }
430
- if (bufferTypes.some(type => varName.toLowerCase().includes(type.toLowerCase()))) {
431
- bufferVars.add(varName);
432
- }
433
- }
434
- },
435
- MemberExpression(node) {
436
- if (node.computed && node.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier) {
437
- const bufferName = node.object.name;
438
- const indexNode = node.property;
439
- if (isBufferType(bufferName)) {
440
- if (couldBeNegative(indexNode)) {
441
- context.report({
442
- node,
443
- messageId: 'negativeBufferIndex',
444
- data: {
445
- filePath: filename,
446
- line: String(node.loc?.start.line ?? 0),
447
- },
448
- });
449
- return;
450
- }
451
- if (isUserControlledIndex(indexNode) && !isIndexValidated(indexNode)) {
452
- if (!hasBoundsCheck(bufferName, indexNode)) {
453
- if (safetyChecker.isSafe(node, context)) {
454
- return;
455
- }
456
- context.report({
457
- node,
458
- messageId: 'userControlledBufferIndex',
459
- data: {
460
- filePath: filename,
461
- line: String(node.loc?.start.line ?? 0),
462
- },
463
- });
464
- return;
465
- }
466
- }
467
- if (!hasBoundsCheck(bufferName, indexNode) && !isIndexValidated(indexNode)) {
468
- if (safetyChecker.isSafe(node, context)) {
469
- return;
470
- }
471
- context.report({
472
- node,
473
- messageId: 'unsafeBufferAccess',
474
- data: {
475
- filePath: filename,
476
- line: String(node.loc?.start.line ?? 0),
477
- },
478
- });
479
- }
480
- }
481
- }
482
- if (node.property.type === 'Identifier' &&
483
- bufferMethods.includes(node.property.name) &&
484
- node.object.type === 'Identifier' &&
485
- isBufferType(node.object.name)) {
486
- }
487
- },
488
- CallExpression(node) {
489
- const callee = node.callee;
490
- if (callee.type === 'MemberExpression' &&
491
- callee.property.type === 'Identifier' &&
492
- callee.property.name === 'slice' &&
493
- callee.object.type === 'Identifier' &&
494
- isBufferType(callee.object.name)) {
495
- const args = node.arguments;
496
- for (const arg of args) {
497
- if (isUserControlledIndex(arg) && !isIndexValidated(arg)) {
498
- if (safetyChecker.isSafe(node, context)) {
499
- continue;
500
- }
501
- context.report({
502
- node: arg,
503
- messageId: 'unsafeBufferSlice',
504
- data: {
505
- filePath: filename,
506
- line: String(node.loc?.start.line ?? 0),
507
- },
508
- });
509
- }
510
- }
511
- }
512
- if (callee.type === eslint_devkit_1.AST_NODE_TYPES.MemberExpression &&
513
- callee.property.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
514
- bufferMethods.includes(callee.property.name) &&
515
- callee.object.type === eslint_devkit_1.AST_NODE_TYPES.Identifier &&
516
- isBufferType(callee.object.name)) {
517
- const args = node.arguments;
518
- for (const arg of args) {
519
- if (isUserControlledIndex(arg) && !isIndexValidated(arg)) {
520
- if (safetyChecker.isSafe(node, context)) {
521
- continue;
522
- }
523
- context.report({
524
- node: arg,
525
- messageId: 'missingBoundsCheck',
526
- data: {
527
- filePath: filename,
528
- line: String(node.loc?.start.line ?? 0),
529
- },
530
- });
531
- }
532
- }
533
- }
534
- },
535
- BinaryExpression(node) {
536
- const leftText = sourceCode.getText(node.left);
537
- const rightText = sourceCode.getText(node.right);
538
- if (leftText.includes('.length') || rightText.includes('.length')) {
539
- }
540
- }
541
- };
542
- },
543
- });
1
+ "use strict";Object.defineProperty(exports,"__esModule",{value:true});exports.noBufferOverread=void 0;const eslint_devkit_1=require("@interlace/eslint-devkit");exports.noBufferOverread=(0,eslint_devkit_1.createRule)({name:"no-buffer-overread",meta:{type:"problem",docs:{url:"https://github.com/ofri-peretz/eslint/blob/main/packages/eslint-plugin-node-security/docs/rules/no-buffer-overread.md",description:"Detects buffer access beyond bounds",cwe:"CWE-126"},hasSuggestions:true,messages:{bufferOverread:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Buffer Overread",cwe:"CWE-126",description:"Buffer access beyond allocated bounds",severity:"{{severity}}",fix:"{{safeAlternative}}",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),unsafeBufferAccess:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Access",cwe:"CWE-126",description:"Buffer accessed without bounds validation",severity:"HIGH",fix:"Add bounds check before buffer access",documentationLink:"https://nodejs.org/api/buffer.html"}),missingBoundsCheck:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Missing Bounds Check",cwe:"CWE-126",description:"Buffer operation missing bounds validation",severity:"MEDIUM",fix:"Validate indices before buffer operations",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),negativeBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Negative Buffer Index",cwe:"CWE-126",description:"Negative index used for buffer access",severity:"MEDIUM",fix:"Ensure buffer indices are non-negative",documentationLink:"https://nodejs.org/api/buffer.html"}),userControlledBufferIndex:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"User Controlled Buffer Index",cwe:"CWE-126",description:"Buffer accessed with user-controlled index",severity:"HIGH",fix:"Validate user input before using as buffer index",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),unsafeBufferSlice:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Unsafe Buffer Slice",cwe:"CWE-126",description:"Buffer slice with unvalidated indices",severity:"MEDIUM",fix:"Validate slice start/end indices",documentationLink:"https://nodejs.org/api/buffer.html#bufslicestart-end"}),bufferLengthNotChecked:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.SECURITY,issueName:"Buffer Length Not Checked",cwe:"CWE-126",description:"Buffer length not validated before access",severity:"MEDIUM",fix:"Check buffer.length before operations",documentationLink:"https://nodejs.org/api/buffer.html#buflength"}),useSafeBufferAccess:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Use Safe Buffer Access",description:"Use bounds-checked buffer access methods",severity:"LOW",fix:"Use buffer.read*() with offset validation or safe wrapper functions",documentationLink:"https://nodejs.org/api/buffer.html"}),validateBufferIndices:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Validate Buffer Indices",description:"Validate buffer indices before use",severity:"LOW",fix:"Check 0 <= index < buffer.length",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),checkBufferBounds:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.INFO,issueName:"Check Buffer Bounds",description:"Always check buffer bounds",severity:"LOW",fix:"Validate buffer operations against buffer.length",documentationLink:"https://nodejs.org/api/buffer.html#buflength"}),strategyBoundsChecking:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.STRATEGY,issueName:"Bounds Checking Strategy",description:"Implement comprehensive bounds checking",severity:"LOW",fix:"Validate all buffer indices and lengths before operations",documentationLink:"https://cwe.mitre.org/data/definitions/126.html"}),strategyInputValidation:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.STRATEGY,issueName:"Input Validation Strategy",description:"Validate user input used as buffer indices",severity:"LOW",fix:"Sanitize and validate all user input before buffer operations",documentationLink:"https://nodejs.org/api/buffer.html"}),strategySafeBuffers:(0,eslint_devkit_1.formatLLMMessage)({icon:eslint_devkit_1.MessageIcons.STRATEGY,issueName:"Safe Buffer Strategy",description:"Use safe buffer wrapper libraries",severity:"LOW",fix:"Use libraries that provide bounds-checked buffer operations",documentationLink:"https://www.npmjs.com/package/safe-buffer"})},schema:[{type:"object",properties:{bufferMethods:{type:"array",items:{type:"string"},default:["readUInt8","readUInt16LE","readUInt32LE","readInt8","readInt16LE","readInt32LE","writeUInt8","writeUInt16LE","writeUInt32LE","slice","copy"],description:"Buffer read/write methods checked for bounds"},boundsCheckFunctions:{type:"array",items:{type:"string"},default:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],description:"Function names that count as a bounds check"},bufferTypes:{type:"array",items:{type:"string"},default:["Buffer","Uint8Array","ArrayBuffer","DataView"],description:"Constructor names treated as buffer types"},trustedSanitizers:{type:"array",items:{type:"string"},default:[],description:"Additional function names to consider as buffer index validators"},trustedAnnotations:{type:"array",items:{type:"string"},default:[],description:"Additional JSDoc annotations to consider as safe markers"},strictMode:{type:"boolean",default:false,description:"Disable all false positive detection (strict mode)"}},additionalProperties:false}]},defaultOptions:[{bufferMethods:["readUInt8","readUInt16LE","readUInt32LE","readInt8","readInt16LE","readInt32LE","writeUInt8","writeUInt16LE","writeUInt32LE","slice","copy"],boundsCheckFunctions:["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes:["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers:[],trustedAnnotations:[],strictMode:false}],create(context){const options=context.options[0]||{};const{bufferMethods=["readUInt8","readUInt16LE","readUInt32LE","readInt8","readInt16LE","readInt32LE","writeUInt8","writeUInt16LE","writeUInt32LE","slice","copy"],boundsCheckFunctions=["validateIndex","checkBounds","safeIndex","validateBufferIndex"],bufferTypes=["Buffer","Uint8Array","ArrayBuffer","DataView"],trustedSanitizers=[],trustedAnnotations=[],strictMode=false}=options;const sourceCode=context.sourceCode;const filename=context.filename;const safetyChecker=(0,eslint_devkit_1.createSafetyChecker)({trustedSanitizers,trustedAnnotations,trustedOrmPatterns:[],strictMode});const bufferTypesSet=new Set(bufferTypes.map(t=>t.toLowerCase()));const userControlledKeywords=new Set(["req","request","query","params","input","user","offset","index","body"]);const bufferVars=new Set;const isBufferType=varName=>{if(bufferVars.has(varName))return true;const lowerName=varName.toLowerCase();for(const type of bufferTypesSet){if(lowerName.includes(type))return true}if(lowerName==="buf"||lowerName==="bytes")return true;return false};const isUserControlledIndex=indexNode=>{if(indexNode.type==="MemberExpression"){let walker=indexNode;while(walker.type==="MemberExpression"){walker=walker.object}if(walker.type==="Identifier"){const root=walker.name.toLowerCase();if(["req","request","event","ctx","context"].includes(root)){return true}for(const keyword of userControlledKeywords){if(root.includes(keyword))return true}}}if(indexNode.type==="Identifier"){const varName=indexNode.name.toLowerCase();for(const keyword of userControlledKeywords){if(varName.includes(keyword))return true}let currentScope=sourceCode.getScope(indexNode);let variable=null;while(currentScope){variable=currentScope.variables.find(v=>v.name===indexNode.name)||null;if(variable)break;currentScope=currentScope.upper}if(variable&&variable.defs.length>0){const def=variable.defs[0];if(def.type==="Variable"&&def.node.init){const init=def.node.init;if(init.type==="MemberExpression"){const objectText=sourceCode.getText(init.object).toLowerCase();const propertyText=sourceCode.getText(init.property).toLowerCase();const keywords=["req","request","query","params","input","user","body"];if(keywords.some(k=>objectText.includes(k)||propertyText.includes(k))){return true}}if(init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const typeConversionFunctions=["number","parseint","parsefloat","string","boolean"];let isTypeConversion=false;if(init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){isTypeConversion=typeConversionFunctions.includes(init.callee.name.toLowerCase())}if(isTypeConversion&&init.arguments.length>0){return isUserControlledIndex(init.arguments[0])}}if(init.type==="Identifier"&&init.name!==indexNode.name){return isUserControlledIndex(init)}}}}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const typeConversionFunctions=["Number","parseInt","parseFloat","String","Boolean"];if(indexNode.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&typeConversionFunctions.includes(indexNode.callee.name)){for(const arg of indexNode.arguments){if(isUserControlledIndex(arg)){return true}}}}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression){const text=sourceCode.getText(indexNode).toLowerCase();const keywords=["req.","request.","query.","params.","body.","input.","user."];if(keywords.some(k=>text.includes(k))){return true}}return false};const isIndexValidated=indexNode=>{if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof indexNode.value==="number"){return indexNode.value>=0}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){let current=indexNode;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&current.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&current.id.name===indexNode.name&&current.init){const init=current.init;if(init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&boundsCheckFunctions.includes(init.callee.name)){return true}if(init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&init.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&init.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&init.callee.object.name==="Math"&&init.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&(init.callee.property.name==="min"||init.callee.property.name==="max")){return true}break}if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){const params=current.params;for(const param of params){if(param.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&param.name===indexNode.name){return true}}}current=current.parent}}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&indexNode.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&boundsCheckFunctions.includes(indexNode.callee.name)){return true}return false};const hasBoundsCheck=(bufferName,indexNode)=>{let current=indexNode;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionDeclaration||current.type===eslint_devkit_1.AST_NODE_TYPES.FunctionExpression||current.type===eslint_devkit_1.AST_NODE_TYPES.ArrowFunctionExpression){break}if(current.type===eslint_devkit_1.AST_NODE_TYPES.IfStatement){const condition=current.test;const conditionText=sourceCode.getText(condition).toLowerCase();if(conditionText.includes(`${bufferName}.length`)&&(conditionText.includes("<")||conditionText.includes("<=")||conditionText.includes(">")||conditionText.includes(">=")||conditionText.includes("&&")||conditionText.includes("||"))){return true}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclaration){for(const declarator of current.declarations){if(declarator.init){const initText=sourceCode.getText(declarator.init).toLowerCase();if(initText.includes(`${bufferName}.length`)&&(initText.includes("math.min")||initText.includes("math.max")||initText.includes("mathmin")||initText.includes("mathmax"))){return true}}}}if(current.type===eslint_devkit_1.AST_NODE_TYPES.ReturnStatement&&current.argument){const returnText=sourceCode.getText(current.argument).toLowerCase();if(returnText.includes(`${bufferName}.length`)){return true}}current=current.parent}return false};const couldBeNegative=indexNode=>{if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof indexNode.value==="number"){return indexNode.value<0}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.UnaryExpression&&indexNode.operator==="-"&&indexNode.argument.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof indexNode.argument.value==="number"){return true}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.BinaryExpression&&indexNode.operator==="-"){return true}if(indexNode.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){let current=indexNode;while(current){if(current.type===eslint_devkit_1.AST_NODE_TYPES.VariableDeclarator&&current.init){if(current.init.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof current.init.value==="number"&&current.init.value<0){return true}if(current.init.type===eslint_devkit_1.AST_NODE_TYPES.UnaryExpression&&current.init.operator==="-"&&current.init.argument.type===eslint_devkit_1.AST_NODE_TYPES.Literal&&typeof current.init.argument.value==="number"){return true}}current=current.parent}}return false};return{VariableDeclarator(node){if(node.id.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init){const varName=node.id.name;if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.NewExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferTypes.includes(node.init.callee.name)){bufferVars.add(varName)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression&&node.init.callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&node.init.callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&node.init.callee.object.name==="Buffer"&&node.init.callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&["from","alloc","allocUnsafe"].includes(node.init.callee.property.name)){bufferVars.add(varName)}if(node.init.type===eslint_devkit_1.AST_NODE_TYPES.CallExpression){const callee=node.init.callee;if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)){bufferVars.add(varName)}}if(bufferTypes.some(type=>varName.toLowerCase().includes(type.toLowerCase()))){bufferVars.add(varName)}}},MemberExpression(node){if(node.computed&&node.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier){const bufferName=node.object.name;const indexNode=node.property;if(isBufferType(bufferName)){if(couldBeNegative(indexNode)){context.report({node,messageId:"negativeBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}if(isUserControlledIndex(indexNode)&&!isIndexValidated(indexNode)){if(!hasBoundsCheck(bufferName,indexNode)){if(safetyChecker.isSafe(node,context)){return}context.report({node,messageId:"userControlledBufferIndex",data:{filePath:filename,line:String(node.loc?.start.line??0)}});return}}if(!hasBoundsCheck(bufferName,indexNode)&&!isIndexValidated(indexNode)){if(safetyChecker.isSafe(node,context)){return}context.report({node,messageId:"unsafeBufferAccess",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(node.property.type==="Identifier"&&bufferMethods.includes(node.property.name)&&node.object.type==="Identifier"&&isBufferType(node.object.name)){}},CallExpression(node){const callee=node.callee;if(callee.type==="MemberExpression"&&callee.property.type==="Identifier"&&callee.property.name==="slice"&&callee.object.type==="Identifier"&&isBufferType(callee.object.name)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)){continue}context.report({node:arg,messageId:"unsafeBufferSlice",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}if(callee.type===eslint_devkit_1.AST_NODE_TYPES.MemberExpression&&callee.property.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&bufferMethods.includes(callee.property.name)&&callee.object.type===eslint_devkit_1.AST_NODE_TYPES.Identifier&&isBufferType(callee.object.name)){const args=node.arguments;for(const arg of args){if(isUserControlledIndex(arg)&&!isIndexValidated(arg)){if(safetyChecker.isSafe(node,context)){continue}context.report({node:arg,messageId:"missingBoundsCheck",data:{filePath:filename,line:String(node.loc?.start.line??0)}})}}}},BinaryExpression(node){const leftText=sourceCode.getText(node.left);const rightText=sourceCode.getText(node.right);if(leftText.includes(".length")||rightText.includes(".length")){}}}}});