T09 · Insecure Skill Coding Practices
Warning
- Location
- index.js:280
- Finding
- Concurrent quota updates allow provider limits to be exceeded<![CDATA[ ## Vulnerability Details **File Location**: `index.js:280-349`, `index.js:867-872`, and `index.js:897-899` **Vulnerability Type**: Race condition caused by non-atomic quota reservation and update **Risk Level**: Medium ### Vulnerable Code ```js // writes quota object to file with lock async function saveQuota(quota) { let release; try { release = await lockfile.lock(quotaPath, { retries: 3, minTimeout: 100, stale: 5000 }); } catch (_) { throw new Error('Quota file is locked. Try again.'); } try { fs.writeFileSync(quotaPath, JSON.stringify(quota, null, 2)); console.error('[quota] Saved.'); } finally { await release(); } } ``` ```js let quota = await loadQuota(); quota = resetIfNewDay(quota, config); quota = reconcileConfig(config, quota); await saveQuota(quota); ``` ```js if (result.updatedQuota) { await saveQuota(result.updatedQuota); delete result.updatedQuota; } ``` ### Technical Analysis The inter-process lock is acquired only while writing the final quota object. It does not protect the complete read-check-reserve-update transaction. A search operation performs the following actions outside a shared lock: 1. Reads the quota file. 2. Checks whether quota is available. 3. Calls the external provider. 4. Deducts quota in its private in-memory copy. 5. Acquires the lock only to overwrite the quota file. Consequently, multiple processes can read the same initial state and independently conclude that quota is available. Each process can then make an API request. Their final writes are serialized, but they contain stale snapshots, so a later write can overwrite counters written by an earlier process. This is a classic lost-update race. Locking only the write operation does not make the surrounding read-modify-write sequence atomic. ### Attack Path 1. Configure a provider with one or a small number of remaining calls. 2. Submit multiple `smart_search` invocations concurrently using separate Skill processe ...[truncated 1103 chars]
- Remediation
- <![CDATA[ ## Remediation Suggestions - Protect the entire quota reservation transaction with one inter-process lock: 1. Acquire the quota-file lock. 2. Read and validate the latest quota state while holding the lock. 3. Reconcile configuration and check availability. 4. Reserve or deduct one quota unit. 5. Atomically persist the updated state. 6. Release the lock. - Reserve quota before making the provider request. If the provider request fails, reacquire the lock and refund the reservation where appropriate. - Write to a temporary file in the same directory and atomically rename it over the quota file to prevent partial writes. - Add a request or reservation identifier so retries and refunds can be made idempotent. - Add concurrency tests that launch multiple processes against a quota of one and verify that no more than one provider call is authorized. - Consider replacing file-based accounting with a transactional store if high concurrency is expected. ]]>
