Back to skill

Security audit

Batch Processing (DataLoader)

Security checks for vulnerabilities and agentic risk

Overview

This is a self-contained JavaScript DataLoader utility with some quality caveats, but no hidden persistence, credential access, or deceptive data movement.

Before using this in production, validate DataLoader options such as maxBatchSize and treat the benchmark examples as illustrative rather than authoritative. Only batch APIs or databases you intentionally provide through your own batchLoadFn, and avoid sharing loader instances across unrelated requests if cached data is sensitive.

Vulnerability Patterns
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

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. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (7)

Lp3

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding
Without declared permissions the skill's intent is opaque and cannot be validated.

External Transmission

Medium
Category
Data Exfiltration
Content
console.log('\n=== Example 2: API Batching ===');
  
  const urls = [
    'https://api.example.com/users/1',
    'https://api.example.com/users/2',
    'https://api.example.com/users/3',
  ];
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
console.log('\n=== Example 2: API Batching ===');
  
  const urls = [
    'https://api.example.com/users/1',
    'https://api.example.com/users/2',
    'https://api.example.com/users/3',
  ];
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
console.log('\n=== Example 2: API Batching ===');
  
  const urls = [
    'https://api.example.com/users/1',
    'https://api.example.com/users/2',
    'https://api.example.com/users/3',
  ];
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
console.log('\n=== Example 2: API Batching ===');
  
  const urls = [
    'https://api.example.com/users/1',
    'https://api.example.com/users/2',
    'https://api.example.com/users/3',
  ];
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
console.log('\n=== Example 2: API Batching ===');
  
  const urls = [
    'https://api.example.com/users/1',
    'https://api.example.com/users/2',
    'https://api.example.com/users/3',
  ];
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Intent-Code Divergence

Medium
Confidence
95% confidence
Finding
The comments and output framing present Example 5 as a DataLoader-based improvement over N+1 behavior, but the postLoader batch function loops over userIds and executes mockDB.query once per ID. This contradicts the documented intent that the example demonstrates reducing N+1 queries through batching, because the implementation still issues N per-user post queries inside the batch.

Static analysis

No suspicious patterns detected.