Skip to content

Predefined kit checkout bypasses FMCS company isolation

Moderate
snipe published GHSA-g7jx-p4xf-m777 Aug 26, 2026

Package

No package listed

Affected versions

<=v8.6.3

Patched versions

8.7.2

Description

Predefined-kit checkout in Snipe-IT does not enforce Full Multiple Company Support (FMCS) tenant isolation on the checkout target. A multi-company non-superuser with assets.checkout permission can route a company-A asset through a predefined kit to a company-B-only user, even though the same actor / asset / target combination is correctly rejected on every other checkout sink (single, bulk, API, accessory, license, consumable).

Severity

Medium. CVSS 3.1: CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N (5.4).

CWE

CWE-863: Incorrect Authorization.

Affected versions

>= 8.6.3, < <PATCHED_VERSION>. Runtime-verified on v8.6.3, code-inspected on v8.7.1. The PredefinedKitCheckoutService was created in 2019 and has never gained a company-isolation check, so earlier versions running with FMCS enabled almost certainly have the same gap, but only the range explicitly verified against a runtime is claimed here.

Description

With FMCS on (Setting::getSettings()->full_multiple_companies_support = 1), every checkout sink Snipe-IT exposes gates the target user against the item's company via $item->canCheckoutTo($target) (defined on App\Models\Traits\CompanyableTrait):

  • Single-asset checkout: AssetCheckoutController::store at app/Http/Controllers/Assets/AssetCheckoutController.php:130 emits general.error_checkout_company_mismatch on failure.
  • Bulk asset checkout, API asset checkout, accessory checkout, consumable checkout, license checkout: all call canCheckoutTo before persisting.

App\Services\PredefinedKitCheckoutService did not. getAssetsToAdd, getLicenseSeatsToAdd, getConsumablesToAdd, and getAccessoriesToAdd each ran only the actor-vs-item policy check ($this->authorize('checkout', $item)) and availableForCheckout(). saveToDb then called $asset->checkOut($user, ...) directly.

Result: an actor who belongs to more than one company can locate a kit whose model has an available asset in company A, POST to /kits/{kit}/checkout with user_id set to a user who belongs only to company B, and the kit-checkout path will assign the company-A asset to the company-B user without triggering the mismatch error. The same actor calling POST /hardware/{asset}/checkout with the same asset and target is blocked.

Actor user-lookup is company-scoped elsewhere, so a single-company actor cannot User::find() a cross-company victim in the first place. This bypass therefore only applies to actors who are members of BOTH the source and destination companies, which is a common MSP / multi-company admin shape.

The reported impact is cross-tenant asset assignment, not remote code execution or authentication bypass. The security invariant that FMCS exists to guarantee (an item belonging to company X cannot be checked out to a user who does not belong to company X or an allowed hierarchy relationship) is violated on the kit path specifically.

Preconditions

  • full_multiple_companies_support = 1.
  • Actor is not a superuser.
  • Actor is a member of at least two companies.
  • Actor has assets.checkout permission.
  • A predefined kit exists whose model has an available asset in company A, and the checkout target user belongs only to company B.

Reproduction

Verified against snipe/snipe-it:v8.6.3 with MariaDB and FMCS enabled. Actor in companies A + B, super = 0. Asset X in company A. victimB in company B only. allyA in company A. Kit contains that asset's model.

  1. As actor, POST /hardware/{X}/checkout with target victimB → blocked. Flash shows general.error_checkout_company_mismatch. assets.assigned_to remains NULL.
  2. POST /kits/{kit}/checkout with user_id = victimB → success. assets.assigned_to set to victimB.
  3. Same kit checkout to allyA succeeds (same-company, expected control).

Remediation

App\Services\PredefinedKitCheckoutService now calls $item->canCheckoutTo($user) for every asset candidate, license, consumable, and accessory in the kit before adding it to the batch that reaches the DB. For assets (where a kit model can have many candidate assets) cross-company candidates are skipped and, if the model can no longer fulfill its quantity from same-company candidates, the operator sees the mismatch error naming the specific blocked asset rather than the generic "none available" one. For the single-item item types (license, consumable, accessory) the mismatch error fires immediately.

Regression tests under tests/Feature/PredefinedKits/Ui/CheckoutKitFmcsTest.php cover all four item types against a multi-company non-superuser targeting a wrong-company user and assert that the target row / pivot is not written.

Fix commit

f590af4c87b8086a160c8997cd3cd8787b757d29

Severity

Moderate

CVSS overall score

This score calculates overall vulnerability severity from 0 to 10 and is based on the Common Vulnerability Scoring System (CVSS).
/ 10

CVSS v3 base metrics

Attack vector
Network
Attack complexity
Low
Privileges required
Low
User interaction
None
Scope
Unchanged
Confidentiality
Low
Integrity
Low
Availability
None

CVSS v3 base metrics

Attack vector: More severe the more the remote (logically and physically) an attacker can be in order to exploit the vulnerability.
Attack complexity: More severe for the least complex attacks.
Privileges required: More severe if no privileges are required.
User interaction: More severe when no user interaction is required.
Scope: More severe when a scope change occurs, e.g. one vulnerable component impacts resources in components beyond its security scope.
Confidentiality: More severe when loss of data confidentiality is highest, measuring the level of data access available to an unauthorized user.
Integrity: More severe when loss of data integrity is the highest, measuring the consequence of data modification possible by an unauthorized user.
Availability: More severe when the loss of impacted component availability is highest.
CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N

CVE ID

No known CVE

Weaknesses

Incorrect Authorization

The product performs an authorization check when an actor attempts to access a resource or perform an action, but it does not correctly perform the check. Learn more on MITRE.

Credits