Info disclosure: public /offerings endpoint leaks etransfer_email #32
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Severity: Medium — unauthenticated PII / payment-destination disclosure.
Problem
GET /wp-json/us-scheduler/v1/offeringshaspermission_callback => '__return_true'(src/Offering/OfferingEndpoint.php:16-31) andOffering::toArray()includesetransfer_email(src/Offering/Offering.php:89).Impact
Any anonymous visitor can retrieve every offering's e-transfer destination email. This is:
Fix
Strip
etransfer_emailfrom the public serialization. It is only needed in the authenticatedcreateIntentresponse (where it already appears). Either drop the field fromtoArray()and add it only where authorized, or gate the field behind login.Resolution — gated behind auth rather than just stripping the field.
Investigation confirmed there is no anonymous consumer of the public listing endpoints. Both front-end pages (
BookingPage,GroupClassPage) hard-gate onis_user_logged_in()+book_lessonbefore their JS loads, and the JS only calls REST with anX-WP-Noncefrom a logged-in session. Admin management uses repositories directly, not these GETs.So instead of only removing
etransfer_emailfrom the public output, the read endpoints now require the booking capability (matchingAvailabilityEndpoint::index) — removing the disclosure surface entirely:OfferingEndpoint::index→canBook(was__return_true)QuestionEndpoint::index→canBook(closes the public registration-questions exposure too)PolicyEndpoint::index→canBook(the signup gate renders its policies server-side, not via REST)Defense-in-depth retained:
Offering::toArray()still omitsetransfer_emailfrom the listing (includeEtransferEmail: false); the relevant address is delivered only via the authenticatedcreateIntentresponse for the student's own registration.composer test(200 tests),composer lint,composer csall green.Verified resolved on main (
061d09e, PR #38): GET /offerings now requires login + book_lesson (OfferingEndpoint::canBook()), and the listing serializes with Offering::toArray(includeEtransferEmail: false) so the e-transfer destination is omitted. It is only exposed in the authenticated payments/intent response for the paying student. Re-confirmed during the 2026-06-10 security review pass.