- security
- android
- reverse-engineering
- privacy
- mobile-security
- robinhood
Reverse engineering the Trump Accounts Android app - the Robinhood wrapper
Filip Luchianenco
Founder at CanITrustThat
The "Trump Accounts: Official App" is published on Google Play under the developer name U.S. Department of the Treasury, package gov.trumpaccounts.goldeneagle, in the Finance category. It is the client for a Treasury program that opens a tax-deferred investing account for a child born 2025–2028, seeded with a one-time $1,000 Treasury contribution. Family and employer contributions are capped at $5,000 a year, and the money is generally locked until the beneficiary turns 18. At the time of the scan the listing showed 100K+ downloads and 3.4 stars, released 2026-05-27.
On 2026-04-06 the Treasury designated The Bank of New York Mellon as a financial agent for the program. BNY brought in Robinhood as brokerage and initial trustee, and Robinhood built and now runs app behind it while BNY holds the assets. Robinhood told investors it expected to spend around $100M standing up the infrastructure, and Treasury put enrollment near 6 million children ahead of the 2026-07-04 nationwide rollout. The app shipped as a custom white-label product for Treasury, with no Robinhood or BNY branding on it. That arrangement is public record.
The setup
Analysis was performed on a device I own, against the public APK pulled from Google Play. The static pass was a 21-agent scrutiny workflow run over the full 760 MB decompiled tree. Nine finding-verifiers, seven adversarial refuters aimed at the HIGH and MEDIUM findings, four spot-checks on the strengths, and one legal-and-framing reviewer. Sideloaded the apk on a rooted Pixel 6a running Android 16, traffic read through mitmproxy with a system-store CA. No requests sent to Robinhood or Treasury servers.
The basics
| Package | gov.trumpaccounts.goldeneagle |
| Version analyzed | 2026.27.2 (build 20262702) |
| Listed developer | U.S. Department of the Treasury |
| What it is | Treasury IRA program for children born 2025–2028; parent-managed, $1,000 initial contribution |
| Platform | Android (no iOS build of this app exists) |
| Provenance | Robinhood's own app, compiled as the internal "Golden Eagle" flavor |
| Scan | 7d2a3556-7c78-4eb2-95b8-2cfcaaf45f62, completed 2026-07-06, 191,927 classes |
| Scores | Overall 75 · Security 75 · Privacy 76 |
| Static findings (original) | 0 critical, 2 high, 5 medium, 2 low, 5 info |
| Method | 21-agent static scrutiny over 760 MB decompiled tree + one runtime capture (Pixel 6a, Android 16, Magisk) |
Robinhood under the hood
Nearly all of the ~192,000 classes sit in the com.robinhood.* namespace: the auth stack, the crypto vault, the networking layer, the analytics logger, the deeplink resolver, the screen-protect manager.
The Golden Eagle codename is stamped throughout the build: the package itself is gov.trumpaccounts.goldeneagle, the home screen is GoldenEagleHomeActivity, the local database is golden-eagle.db, the dependency-injection root is GoldenEagleAppComponent.
Robinhood's private, internal frameworks are present/ Hammer, their dependency-injection framework; Duxo, their state-management framework, which appears directly as PinDuxo; and Microgram, their in-house mini-app WebView runtime.
Every one of the ten certificate-pinned domains is a *.robinhood.com host:
api.robinhood.com bonfire.robinhood.com cashier.robinhood.com
crumbs.robinhood.com nummus.robinhood.com minerva.robinhood.com
midlands.robinhood.com atlas.robinhood.com dora.robinhood.com
identi.robinhood.com
There is no treasury.gov API in the pin set or anywhere in the app. Robinhood Securities, LLC is the named IRA trustee and custodian on the store listing.
Breaking down the CITT findings
The advertising ID is always EMPTY
The scan headlined that the device advertising ID was attached to every analytics event. That's true, but it's always empty. AdIdProvider is bound only to NoOpAdIdProviderModule, whose getAdId() returns an empty string, with a single binding and no release-flavor override:
// NoOpAdIdProviderModule.java:76-85 — the only binding for AdIdProvider in this flavor
public Observable<String> getAdId() {
return Observable.just(""); // bound once at GoldenEagleAppComponent.java:14615
}
BaseEventLogger then writes that value onto device.adid for every analytics event (BaseEventLogger.java:169,192), so the field ships empty. The Google Advertising ID is never read into Robinhood's analytics Device proto. The related adjust_device_id field stays empty too, because there is no Adjust SDK in the APK. firebase_app_instance_id is app-scoped Firebase Analytics, which does not cross app boundaries.
Guessing the 4-digit pin
The optional local app-lock is a 4-digit PIN; PinDuxo caps length at 4 and auto-submits, so the keyspace is 10,000 combinations. The PIN value itself is Vault/AndroidKeyStore-encrypted (AuthLockPrefsModule.java:457-461), but the failed-attempt counter that throttles guessing is stored as a plain IntPreference in @UserPrefs, not the Vault:
// AuthLockPrefsModule.java:470-473 - the throttle counter, stored unencrypted
provideFailedAttemptsPref(...) {
return new IntPreference(preferences, "failedPinAttempts", 0, 4, null);
}
// PinManager.java:35 — ALLOWED_ATTEMPTS = 3; unlock is a local areEqual compare, no server throttle (:217-231)
A user with root can reset that counter and brute-force all 10,000 combinations offline. This is not deemed as an issue for typical daily users, but it's worth mentioning this as an architectural decision.
The hardcoded AES fallback key
A legacy vault path has a fixed key and a fixed IV in the release build:
// LegacyVaultWorkerV1.java:26-27 — fixed key, fixed IV, AES/CBC
private static final String SECURE_KEY = "hNnS24Qi3b3n8n2owu12EYxrp2ckMvrXL4TVwPzf";
private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
// key = SHA-256(SECURE_KEY); IV = first 16 bytes of SECURE_KEY
This runs only when the primary AndroidKeyStore AES-256-GCM worker throws a VaultException which a silent try/catch fallback for a corrupted keystore. Though a healthy device would never reach that point, it's a good safety mechanism to fallback to. It will be invalidated on the next successful run anyways, so it's not an issue.
Unencrypted local database
The unencrypted local database. golden-eagle.db is built with a plain FrameworkSQLiteOpenHelperFactory, with no SQLCipher:
factory.databaseBuilder(app, GoldenEagleRoomDatabase.class, ROOM_DB_NAME)
.fallbackToDestructiveMigration(false)
.build();
InvestedDashboard and InvestedPortfolioBreakdown store an accountNumber alongside portfolio protos; AccountProfileState stores first and last name plus an account ID in cleartext. Reading them requires root or a forensic image, and file-based encryption mitigates it (the device must be unlocked at extraction). Also, no SSN is stored in golden-eagle.db. Would have been nice to see such sensitive information encrypted.
FLAG_SECURE is absent from most screens.
Only four security screens set it: MFA settings, email verification, phone verification, and password change.
The dashboard, account balance, portfolio breakdown, statement viewer, and contribution history do not set it, and neither does BaseActivity.
The realistic exposure is during a third-party screen-recording app, or a remote screen-share support-call social-engineering attempt.
A separate blockScreenSharing warning modal is remote-gated by the account-security-block-screen-sharing-allow-list experiment, but that gate governs only the warning fragment, not setFlagSecure.
The inherited ad-attribution code
The build carries Robinhood's commercial marketing-attribution stack, standard platform code that stayed dormant through every part of the lifecycle the capture could reach.
The stack is the Singular SDK v12.9.0, Robinhood's Meta App ID 674753905956192, and the two Android Privacy Sandbox permissions ACCESS_ADSERVICES_ATTRIBUTION and ACCESS_ADSERVICES_AD_ID.
The Singular initialization key robinhood_acd79726 and the Meta App ID are in RealSingularSdkWrapper.java:43-46. The one reachable attribution element is a funding-milestone event: FirstFundedEventLogger.logIfNeeded(isRhsAccount) fires a one-shot first_funded event to Singular on first RHS-account funding, gated by a one-shot attemptedPref and the SingularDisableTrackingPref opt-out.
first_funded is gated on isRhsAccount - generic Robinhood funding-conversion instrumentation. Singular initialization itself is not gated by "GDPR marketing consent," as the original report claimed; it is gated by a remote killswitch experiment, acq-singular-enabled (SingularEnabledExperiment). Marketing consent only sets limitDataSharing.
That Meta App ID is owned by Robinhood. It can be verified using a single GET https://graph.facebook.com/674753905956192 which returns {"category":"Utilities","link":"https://robinhood.com/","name":"Robinhood","id":"674753905956192"}. No Facebook SDK is bundled though, and sources/com/facebook/ holds zero files.
The App ID is used only by Singular in SLMetaReferrer.fetchReferrer() and DeviceInfo.java:250 read from an installed Meta app through the content://com.facebook.katana.provider.* providers and forward what they find to Singular (sdk-api-v1.singular.net). That read only fires if a Meta app is installed, behind the same killswitch and opt-out. So the observable flow is device to Singular, not device to Facebook.
Security Strengths to call out
Certificate pinning is unconditional: CertificatePinningModule hardcodes certificatePinner(true), wired into the production DI graph, with SHA-256 pins for the ten *.robinhood.com hosts listed above (third-party endpoints are unpinned).
OAuth tokens are sealed with AES-256-GCM under an AndroidKeyStore key aliased robinhoodSecretKey, with a random per-call IV and AAD {1} (Api23VaultWorkerV1.java:32-37,87-90,101-126); this is the primary token path, the one the hardcoded fallback above only backs up.
There is no cleartext HTTP. targetSdkVersion is 35, with no usesCleartextTraffic and no networkSecurityConfig, the EXTERNAL release build has DEBUG=false, and the only http://127.0.0.1 strings are debug test-backend fallbacks unreachable in release.
allowBackup="false" is set on the sole <application> tag (AndroidManifest.xml:110), reinforced by fullBackupContent="false".
The runtime session
The split APK was sideloaded onto a rooted Pixel 6a (Android 16, Magisk). PairIP's license check was cleared by spoofing the installer package (adb install-multiple -i com.android.vending), which sets installerPackageName to the Play Store value.
Traffic was read through mitmproxy with an APEX-store system CA (144 certs across the zygote and system_server namespaces); non-pinned third-party SDKs get decrypted while Robinhood's pinned domains are passed through untouched.
Frida unpinning was not usable. Frida-server 16.7.19's agent is killed on injection under SELinux Enforcing on this Android 16 build, and permissive mode crash-looped the device. So Robinhood's own pinned traffic was left opaque. Its device.adid="" is established statically.
Across launch and the reachable onboarding screens, three third-party telemetry endpoints fired: Sentry (o62437.ingest.us.sentry.io), bitdrift (api.bitdrift.io), and Firebase logging (firebaselogging-pa.googleapis.com).
There were zero connections to singular.net or sdk-api-v1.singular.net during the session; Singular.init() produced no observable egress. There was zero egress to facebook.com or graph.facebook.com, and the Meta app was not installed, so the content-provider read could not fire regardless. Grepping the full raw capture for the device's real AAID, android_id value, serial, and WiFi MAC returned zero hits on any decryptable endpoint. Every regex flag was a false positive on an SDK event ID, the Sentry project ID in a URL path, or the package@version release string.
Account creation could not be completed: the email/create-user step returns a generic "Something went wrong." Robinhood's servers (identi, crumbs, api.robinhood.com) connect fine through passthrough. The wall is almost certainly Play Integrity / device attestation rejecting a rooted device, and the capture is thick with Google attestation traffic (geller-pa ×46, plus ondevicesafety-pa, pixelonboarding-pa, remoteprovisioning). Through the pre-account lifecycle the app is cleaner than its static capability suggests, and what the attribution code does after funding stays unconfirmed.
Compliant vs grey zone
| Behavior | Verdict | Basis |
|---|---|---|
Robinhood white-label provenance (Golden Eagle flavor, *.robinhood.com pins, private frameworks) | Within policy | Matches the publicly announced BNY/Robinhood arrangement |
| Certificate pinning on 10 robinhood.com hosts | Within policy | certificatePinner(true), wired into prod DI |
| OAuth tokens AES-256-GCM under AndroidKeyStore | Within policy | robinhoodSecretKey, random IV, primary path |
No cleartext HTTP; allowBackup=false | Within policy | targetSdk 35, no cleartext config, backup disabled |
PIN lockout counter in plaintext @UserPrefs | Acceptable | Offline brute-force of an optional local lock; needs root |
| Hardcoded AES fallback key | Acceptable | Runs only on AndroidKeyStore failure |
Unencrypted golden-eagle.db | Acceptable | Cleartext account/portfolio data; FBE-mitigated; no SSN in this DB |
| FLAG_SECURE absent on dashboard/portfolio | Grey zone | Third-party screen-record and remote screen-share exposure |
| Singular signing key/secret hardcoded | Within policy | Public client-side key; forgery of attribution events only, keyed SHA-1 |
| FINE_LOCATION declared | Within policy | Persona KYC SDK reads GPS as a standard identity fraud signal |
| Inherited Singular + AdServices attribution stack | Within Policy | Standard Robinhood code dormant at runtime |
What remains unconfirmed
The post-funding path was never exercised, because device attestation blocked account creation, and that is where first_funded and Singular attribution would fire. Whether the Singular SDK's own OAID/AIFA helpers attach a GAID on Singular's separate channel after funding is likewise unconfirmed. The bundled Persona KYC SDK contains a code path that reads GPS via FINE during identity verification (GpsUtilsKt.java:22-25, from InquiryApiHelper.java:570), standard fraud-signal behavior, but that screen was never reached, so it was never observed running.
Methodology
Static analysis ran over the 760 MB decompiled tree of the public APK, version 2026.27.2, via a 21-agent scrutiny workflow. The single runtime capture used a rooted Pixel 6a on Android 16, the split APK sideloaded, PairIP cleared by installer spoofing, and mitmproxy with a system-store CA reading the non-pinned SDK traffic. Coverage stops at account creation, which device attestation refused on a modified device. No Robinhood or Treasury server was pinged. This documents publicly observable behavior of the app on a device I own.
The honest bottom line
The "Trump Accounts: Official App" is a white-labeled Robinhood app. The code confirms the publicly announced arrangement: the build names itself GOLDEN_EAGLE, pins only *.robinhood.com, and carries Robinhood's private frameworks. After static review and one live network capture there are no confirmed security or privacy vulnerabilities specific to this app. The runtime capture showed nothing but ordinary crash and diagnostics telemetry leaving the device.
The build does inherit Robinhood's commercial ad-attribution code: Singular, Robinhood's Meta App ID, the AdServices permissions. It stayed dormant through the reachable lifecycle, and nothing from it was observed leaving the device. What it does after an account is funded is unconfirmed.