Skip to main content
Vulnerability Database/CVE-2026-86744

CVE-2026-86744: Snipe-IT Race Condition Vulnerability

CVE-2026-86744 is a race condition flaw in Snipe-IT that allows concurrent asset checkouts to corrupt audit trails and reporting data. This article covers the technical details, affected versions, and mitigation strategies.

Published:

CVE-2026-86744 Overview

CVE-2026-86744 is a race condition [CWE-362] in Snipe-IT, an open-source IT asset management application. Versions 8.6.3 and earlier, along with pre-release develop commits before the fix, contain a time-of-check to time-of-use flaw in the asset checkout paths. Concurrent checkout requests for the same asset can both pass the availability check and both commit, producing duplicate checkout-history rows and a doubled checkout_counter. The asset's final assigned_to value remains singular, so the visible assignment stays intact, but audit and utilization reporting are corrupted. The issue is fixed in Snipe-IT 8.7.0.

Critical Impact

Authenticated users with assets.checkout permission can corrupt audit trail integrity and reconciliation reporting through precisely timed concurrent requests.

Affected Products

  • Snipe-IT 8.6.3 and earlier stable releases
  • Snipe-IT develop branch pre-release commits prior to the fix
  • Snipe-IT deployments exposing Api\AssetsController::checkout() or Assets\AssetCheckoutController::store()

Discovery Timeline

  • 2026-09-09 - CVE-2026-86744 published to NVD
  • 2026-09-09 - Last updated in NVD database

Technical Details for CVE-2026-86744

Vulnerability Analysis

The flaw exists in Snipe-IT's asset checkout flow. Both Api\AssetsController::checkout() and Assets\AssetCheckoutController::store() invoke Asset::availableForCheckout() outside the mutation path. They then call Asset::checkOut() without taking a row lock or re-checking availability against a locked snapshot.

When two authenticated requests arrive for the same available asset within a narrow timing window, both observe the asset as available. Both proceed to commit, generating two checkout-history rows, incrementing checkout_counter twice, and emitting two CheckoutableCheckedOut events for a single-assignment asset.

The final assigned_to field ends up holding one value, so the user-visible assignment appears normal. Downstream audit logs, utilization dashboards, and reconciliation reports are silently corrupted.

Root Cause

The controllers implement a classic check-then-act pattern without transactional isolation. The availability read is performed outside any DB::transaction() boundary and without lockForUpdate(). No re-validation occurs after acquiring the row for mutation, allowing concurrent workers to interleave.

Attack Vector

Exploitation requires an authenticated session holding the assets.checkout permission or superuser rights. The attacker submits two or more concurrent checkout requests targeting the same asset ID with precise timing. Successful exploitation requires the requests to reach availableForCheckout() before either commits. The related fix for accessory checkout demonstrates the corrected concurrency pattern:

php
 use Illuminate\Contracts\View\View;
 use Illuminate\Http\RedirectResponse;
 use Illuminate\Http\Request;
+use Illuminate\Support\Facades\DB;

 class AccessoryCheckoutController extends Controller
 {
php
         $accessory->checkout_qty = $request->input('checkout_qty', 1);
         $payload = null;
+        $overAllocated = false;
+
+        // Concurrency guard. AccessoryCheckoutRequest validated
+        // number_remaining_after_checkout >= 0 with an unlocked read of
+        // numRemaining(), so two simultaneous checkout requests could both
+        // pass validation, both attach rows, and land the register at -1.
+        // Re-fetch the parent row under lockForUpdate INSIDE the
+        // transaction, re-check availability against the locked snapshot,
+        // and only then write. Mirrors the License checkout locking
+        // pattern.
+        DB::transaction(function () use ($accessory, $request, $target, &$payload, &$overAllocated): void {
+            $locked = Accessory::whereKey($accessory->id)->lockForUpdate()->first();
+
+            if (! $locked || $locked->numRemaining() < $accessory->checkout_qty) {
+                $overAllocated = true;
+
+                return;
+            }

-        // Keep checkout rows and checkout log/event atomic to avoid ghost assignments.
-        DB::transaction(function () use ($accessory, $request, $target, &$payload): void {
             for ($i = 0; $i < $accessory->checkout_qty; $i++) {

                 $accessory_checkout = new AccessoryCheckout([

Source: Snipe-IT commit f71806b1

Detection Methods for CVE-2026-86744

Indicators of Compromise

  • Duplicate checkout-history rows for the same asset ID with timestamps within milliseconds of each other
  • checkout_counter values on an asset that exceed the count of distinct assigned_to transitions in the audit log
  • Two CheckoutableCheckedOut event records emitted for a single-assignment asset in the same request window
  • Application access logs showing near-simultaneous POST requests to /api/v1/hardware/{id}/checkout from the same session

Detection Strategies

  • Query the asset_logs table for pairs of checkout entries sharing an item_id with sub-second timestamp differences
  • Correlate web server access logs with database write logs to identify overlapping checkout requests
  • Alert on any asset whose lifetime checkout_counter diverges from the count of distinct assignment records

Monitoring Recommendations

  • Enable Laravel query logging in staging to profile checkout endpoint transaction boundaries
  • Forward Snipe-IT application logs and web access logs to a centralized SIEM for correlation
  • Baseline normal checkout request rates per authenticated user and alert on burst patterns

How to Mitigate CVE-2026-86744

Immediate Actions Required

  • Upgrade Snipe-IT to version 8.7.0 or later, which introduces lockForUpdate() inside a DB::transaction() block
  • Audit existing checkout-history and asset_logs tables for duplicate entries indicating prior exploitation
  • Review and minimize accounts granted the assets.checkout permission and superuser role

Patch Information

The fix is delivered in Snipe-IT 8.7.0 via commit f71806b1e0efbd3bc2b6be61994ad2a5d5d6c206. The patch wraps checkout mutations in a database transaction, re-fetches the parent row with lockForUpdate(), and re-checks availability against the locked snapshot before writing. Full details are in the GitHub Security Advisory GHSA-h992-9438-26v8 and the VulnCheck Advisory on Snipe-IT.

Workarounds

  • Restrict the assets.checkout permission to a minimal set of trusted operators until the upgrade is applied
  • Place a reverse proxy rate limit on the /api/v1/hardware/*/checkout endpoint to reduce concurrency windows
  • Schedule periodic reconciliation jobs to detect and flag duplicate checkout-history entries
bash
# Upgrade Snipe-IT to the patched release
cd /var/www/snipe-it
git fetch --tags
git checkout v8.7.0
composer install --no-dev --prefer-source
php artisan migrate --force
php artisan config:clear
php artisan cache:clear

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.