T09 · Insecure Skill Coding Practices
Warning
- Location
- dataloader.js:33
- Finding
- Unvalidated maxBatchSize Allows Infinite Dispatch Rescheduling and Denial of Service<![CDATA[ ## Vulnerability Details **File Location**: `dataloader.js:33` and `dataloader.js:116-121` **Vulnerability Type**: Improper input validation leading to resource-exhaustion denial of service **Risk Level**: Medium ### Vulnerable Code ```javascript this._maxBatchSize = options.maxBatchSize || 100; ``` ```javascript const batch = this._queue.slice(0, this._maxBatchSize); this._queue = this._queue.slice(this._maxBatchSize); this._scheduled = this._queue.length > 0; if (this._queue.length > 0) { setImmediate(() => this._dispatchBatch()); } ``` ### Technical Analysis The constructor does not verify that `maxBatchSize` is a finite positive integer. Because negative numbers are truthy in JavaScript, a value such as `-1` is accepted instead of falling back to the default. When `_dispatchBatch()` runs with `maxBatchSize: -1`, `this._queue.slice(0, -1)` excludes the final queued item, while `this._queue.slice(-1)` retains that item. The queue therefore never becomes empty. The implementation continuously schedules another dispatch through `setImmediate()`, and the retained load promise never resolves. The same underlying problem applies to other negative values, which can leave one or more entries permanently queued. Non-integer, infinite, or otherwise invalid values also lack explicit validation and can produce unexpected batching behavior. ### Attack Path 1. An attacker or untrusted caller gains influence over the options passed to the `DataLoader` constructor. 2. The caller creates a loader with a negative batch size: ```javascript const loader = new DataLoader(async keys => keys, { maxBatchSize: -1 }); ``` 3. The caller queues at least one load: ```javascript await loader.load('attacker-controlled-key'); ``` 4. `_dispatchBatch()` retains the queued item because of the negative `slice()` index. 5. Since the queue remains non-empty, the implementation repeatedly invokes `setImmediate()` and dispatches again. 6. The load p ...[truncated 615 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions Validate constructor options before storing or using them. Require `maxBatchSize` to be a finite positive integer: ```javascript constructor(batchLoadFn, options = {}) { if (typeof batchLoadFn !== 'function') { throw new TypeError( 'DataLoader requires a batchLoadFn as first argument' ); } if ( options.maxBatchSize !== undefined && (!Number.isInteger(options.maxBatchSize) || options.maxBatchSize <= 0) ) { throw new TypeError('maxBatchSize must be a positive integer'); } this._batchLoadFn = batchLoadFn; this._maxBatchSize = options.maxBatchSize ?? 100; } ``` Use nullish coalescing (`??`) instead of logical OR (`||`) after validation so defaults are applied only when an option is absent, rather than silently replacing arbitrary falsy values. Also add tests covering: - `maxBatchSize` values of `-1`, `0`, `NaN`, `Infinity`, fractional numbers, strings, and `null`. - Confirmation that invalid values fail synchronously. - Confirmation that valid positive batch sizes drain the queue and settle every returned promise. - A defensive invariant in `_dispatchBatch()` that refuses to dispatch when the configured batch size is invalid. ]]>
