Skip to content

Prove Android push for apps that identify users by userId - #34

Open
franco-zalamena-iterable wants to merge 2 commits into
mainfrom
fix/android-userid-proof
Open

franco-zalamena-iterable wants to merge 2 commits into
mainfrom
fix/android-userid-proof

Conversation

@franco-zalamena-iterable

Copy link
Copy Markdown
Contributor

Summary

The Android push proof only knew one kind of user: an email address. An app that signs users in with setUserId() and has no email couldn't be proved, and the tool's step 4 told the agent to "make the app use the same value" as an email.

In a Cursor trial on a bare React Native app, the agent first wrote the right integration: setUserId(<the app's user ID>), following the skill's identity rules. It then hit step 4, searched bin/ for userId support and found none, and rewrote the app to sign every user in as <id>@<app>.local. When Iterable rejected that as an invalid email, it switched to a subdomain of the app's real website, a domain somebody else owns. The proof then passed, against a fabricated identity that every real user of the app would have had.

This PR makes the Android proof handle a userId the way the iOS proof already does:

  • Recording it:
    • Step 4 takes an email or a user ID.
    • agent set ITBL_USER_ID=… records a user ID (up to 128 characters, no spaces or quotes).
    • The step's typed answer goes through ITBL_IDENTITY, which files it as ITBL_EMAIL when it's shaped like an email and as ITBL_USER_ID otherwise. Iterable's User ID rules say a userId never holds an email.
    • Recording one identity clears the other.
  • Using it:
    • G15 reads /api/users/byUserId/{userId}.
    • G17 reads /api/events/byUserId/{userId}.
    • The proof send uses recipientUserId, which TemplateProofRequest accepts in Iterable's published spec.
    • A 400 saying "not found" counts as "no user yet", as ios-verify already does.
  • Reading it off the device: app_identity picks up a "userId" from the SDK's own IterableRequest log as well as an "email".
  • React Native iOS: import-from-android carries a recorded user ID across as identity mode user-id.
  • Wording:
    • Step 4, the run-app screen and iterable-provision now say: use the identity the app already sets, and never change it, or invent an email address, to fit the proof.
    • iterable-android-verify names target.user_id.

The email path is unchanged. With ITBL_EMAIL set, every request is byte-identical: the same getByEmail and /api/events/{email} paths, the same proof body and the same "no user exists" check. ITBL_EMAIL wins if both are somehow set. The new suite pins this.

Version 26.10.7-beta.

How it was tested

  • New suite: tests/android-userid-proof.sh (13 checks, with stubbed curl and adb). It covers:

    • the proof body, for a userId and for an email;
    • G15 by userId, including a path-encoded ID and an unknown user;
    • G15 by email;
    • G17 by userId;
    • agent set routing, clearing and refusal;
    • reading a userId from logcat;
    • the step 4 screen.

    Run against main, every userId check fails and the email checks pass.

  • Updated: tests/agent-questions.sh now expects the new step 4 wording and the ITBL_IDENTITY free-text command, and reads step 4 with a userId recorded too.

  • Full offline suite: tests/run-all.sh passes locally.

Not in this PR

  • The same trial's agent operated the emulator itself (pm grant POST_NOTIFICATIONS, am force-stop, input keyevent). It also never asked the Step 0 questions. Both are separate findings.

🤖 Generated with Claude Code

The Android ladder only knew ITBL_EMAIL, and its step 4 told the agent to
make the app use the same value. A trial agent with a correct setUserId
integration rewrote the app to sign every user in under an invented email
address so the proof would run.

ITBL_USER_ID now sits beside ITBL_EMAIL: G15 and G17 read the byUserId
endpoints, the proof send uses recipientUserId, app_identity reads a
logged userId, and step 4 takes either, filing a typed answer by its
shape. The React Native iOS import carries a userId across. Step 4 and
iterable-provision say never to change the app's identity, or invent an
email, to fit the proof. With ITBL_EMAIL set every request is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant