highJun 26, 2026

SolidInvoice: IDOR in Symfony LiveComponents Allows Cross-User API Token and Notification Settings Access

Pranav Khune
Penetration Testing Team Lead, SecureLayer7

Any authenticated user inside a SolidInvoice company can read, revoke, or hijack another user's API tokens and notification credentials by supplying a victim's entity ID to unprotected Symfony LiveCom

Packagesolidinvoice/solidinvoice
Ecosystemcomposer
Affected<= 2.3.15
Fixed in2.3.16

The problem

Four LiveComponent actions in SolidInvoice accept Doctrine entity IDs from the client without verifying that the resolved entity belongs to the requesting user.

This lets any authenticated member of the same company revoke a co-worker's API token, read their token usage history (IP addresses, paths, user-agents), read their notification transport credentials (Slack, Telegram, SMS keys), or silently steal ownership of a transport setting by triggering its save action.

Proof of concept

http
# 1. Revoke another user's API token (token id = 42 belongs to victim)
POST /_components/ApiTokens/revoke
Content-Type: application/json

{"token": 42}

# 2. Read victim's API token history
POST /_components/ApiTokenHistory
Content-Type: application/json

{"token": 42}

# 3. Read victim's notification transport credentials (setting id = 7)
POST /_components/NotificationIntegrations
Content-Type: application/json

{"setting": 7}

# 4. Steal ownership of victim's transport setting
POST /_components/NotificationTransportConfiguration/save
Content-Type: application/json

{"setting": 7}

Symfony LiveComponents resolve `#[LiveArg]` and writable `#[LiveProp]` values directly from the client-supplied JSON, running a Doctrine `find()` on the provided integer ID. The app uses a `CompanyFilter` for tenant isolation but has no per-user ownership check at the action level.

The listing methods (`apiTokens()`, `enabledIntegrations()`) do filter by user, but the mutating actions (`revoke()`, `save()`) and data-fetching props (`$token`, `$setting`) do not, creating a read/write gap. The highest-severity case is `NotificationTransportConfiguration::save()`, which calls `$setting->setUser($user)` on the victim's entity, reassigning its stored third-party credentials to the attacker.

The fix adds an ownership assertion before any operation: `if ($entity->getUser() !== $this->security->getUser()) { throw $this->createAccessDeniedException(); }` (CWE-639, CWE-862).

The fix

Upgrade to SolidInvoice 2.3.16. The patch adds an explicit ownership check in each affected LiveAction and LiveProp initializer, throwing `AccessDeniedException` when the resolved entity's user does not match the authenticated user.

Reporter not attributed.

References: [1][2]