Org Web Adapter

hungryroot/jira/be_7373_sms_opt_in_opt_out_endpoints_allow_subscribing_arbitrary_phone_numbers_via_braze.org

ID
ef3e755c-46a6-4a0f-8fc3-a09e6a213f09

BE-7373 SMS opt-in/opt-out endpoints allow subscribing arbitrary phone numbers via Braze

- source :: https://hungryroot.atlassian.net/browse/BE-7373

- tags :: Hungryroot API Project

- epic :: EP-608 Compliance on Checkout

- sprint :: HR BE Sprint 105

- status :: Status - Complete

Tasks

DONE Come up with a competent way to test that checking numbers works

hungryrootworkfeaturesmsconsentbe7373ep608sprint105
ID
8139ba8b-7cca-5f9f-63a6-122482efe4c6

DONE Open a cherry pick to get this change on staging tomorrow

hungryrootworkcherrypickfeaturesmsconsentbe7373ep608sprint105
ID
adee4abc-1c9d-06f5-1661-8a3b1a655af2

Description

[[https://docs.google.com/document/d/1CmhVeSbP_5iiBRaoeKXUXKl0d4-UaXxJHWxvulna6Uw/edit?tab=t.0#heading=h.yo83yokvtl52

][Google Doc]]

Problem

The POST /api/v2/customers/self/sms_opt_in/ endpoint and the PATCH

/api/v2/customers/{id}/address/ endpoint (with sms_opt_in: true) both issue calls

to Braze to opt-in or opt-out a phone number without verifying that the

authenticated customer actually owns the phone number in our system.

This means any authenticated user (including free quiz signups with just email +

password, no payment) can subscribe or unsubscribe arbitrary phone numbers.

Attack Vectors

Vector 1: Mass Opt-In (Both Dynamic Config ON & OFF States)

An attacker can enumerate through phone numbers and subscribe them to SMS

without the phone number owner's knowledge.

Steps:

1. Progress through the quiz and create an account (email + password only, no

payment)

2. With dynamic config ENABLE_BRAZE_SMS_OPTIN_ENDPOINT ON: Call POST

/api/v2/customers/self/sms_opt_in/ with any valid 10-digit US phone and

sms_opt_in: true

3. With config OFF: Call PATCH /api/v2/customers/{id}/address/ with valid address

data including sms_opt_in: true

4. The phone is immediately subscribed in Braze

Protections in place: None meaningful. Authentication is required but a free

quiz signup qualifies.

Proposed Fix

Before issuing calls to Braze for sms_opt_out or sms_opt_in, validate that the

phone number provided in the request matches a phone number already associated

with the customer in our database (e.g. on their Address record).

Affected Code

app/rest/customer.py - sms_opt_in action (lines ~1293-1327): calls

braze.sms_opt_in() / braze.sms_opt_out() without checking phone ownership

app/rest/address.py - address update path with sms_opt_in field

Larger Issue (Note)

There is a broader problem: we don't currently validate that the person signing

up owns the phone number they are providing us. There is no phone verification

(e.g. OTP/SMS confirmation) during account creation or address entry. This

ticket addresses the immediate fix of checking existing phone ownership before

issuing Braze calls; the broader phone verification gap should be tracked

separately.

Acceptance Criteria

- [ ] sms_opt_in endpoint validates the phone number belongs to the customer

before calling Braze

- [ ] Address update path with sms_opt_in validates phone ownership before calling

Braze

- [ ] If the phone number doesn't match the customer's stored phone, return a 400

error

- [ ] Unit tests cover both the happy path and the rejection path