User Graph Traversal
When a user creates a Warp account, their computer's anonymous terminal ID is linked to their user ID. This creates a graph: multiple accounts that have logged into the same machine share terminal IDs and can be connected via 2-hop traversal.
Schema config
The bq query below uses the default logical mapping: project YOUR_GCP_PROJECT
and tables core_user_facts, int_user_id_to_terminal_anonymous_id_mapping,
core_ai_cycles_clean in the prod dataset. Replace them with your physical names
from config/schema.json (see SCHEMA.md).
Query (seed by email)
bq query --use_legacy_sql=false --format=prettyjson '
with
seed as (
select user_id
from `YOUR_GCP_PROJECT.prod.core_user_facts`
where user_email = "USER_EMAIL_HERE"
)
, seed_anon_ids as (
select distinct terminal_anonymous_id
from `YOUR_GCP_PROJECT.prod.int_user_id_to_terminal_anonymous_id_mapping`
where user_id in (select user_id from seed)
and terminal_anonymous_id != ""
)
, users_hop1 as (
select distinct user_id
from `YOUR_GCP_PROJECT.prod.int_user_id_to_terminal_anonymous_id_mapping`
where terminal_anonymous_id in (select terminal_anonymous_id from seed_anon_ids)
and terminal_anonymous_id != ""
)
, anon_ids_hop2 as (
select distinct terminal_anonymous_id
from `YOUR_GCP_PROJECT.prod.int_user_id_to_terminal_anonymous_id_mapping`
where user_id in (select user_id from users_hop1)
and terminal_anonymous_id not in (select terminal_anonymous_id from seed_anon_ids)
and terminal_anonymous_id != ""
)
, users_hop2 as (
select distinct user_id
from `YOUR_GCP_PROJECT.prod.int_user_id_to_terminal_anonymous_id_mapping`
where terminal_anonymous_id in (select terminal_anonymous_id from anon_ids_hop2)
and terminal_anonymous_id != ""
and user_id not in (select user_id from users_hop1)
)
, connected_users as (
select user_id from users_hop1
union distinct
select user_id from users_hop2
)
, current_cycles as (
select
user_id
, total_credits_used as total_requests_used
, plan_type_at_cycle_end
from `YOUR_GCP_PROJECT.prod.core_ai_cycles_clean`
where is_current_cycle
)
select
cu.user_id as firebase_uid
, cuf.user_email as email
, cuf.signup_timestamp as created_at
, coalesce(cc.total_requests_used, 0) as total_requests_used
, case
when coalesce(cc.plan_type_at_cycle_end, "free") = "free" then "free"
else "active"
end as paid_status
, cuf.reputation_status
from connected_users cu
join `YOUR_GCP_PROJECT.prod.core_user_facts` cuf
using (user_id)
left join current_cycles cc
using (user_id)
order by total_requests_used desc
'
Output
| Column | Description |
|---|---|
firebase_uid |
User ID |
email |
Account email |
created_at |
Signup timestamp |
total_requests_used |
AI requests used in current cycle |
paid_status |
free or active (paid) |
reputation_status |
e.g. unreputable, reputable, reputable_from_override |
Output Guidance
Filter by reputation status first. Skip any user with reputation_status of reputable or reputable_from_override — these have already been cleared and are not worth investigating. Focus only on users with reputation_status = unreputable or null.
After running the query, report:
Summary stats
- Total connected users, broken down as free vs. active (e.g., "Found 47 connected users: 44 free, 3 active")
- Total AI requests consumed by free accounts in the cluster
Active (paid) users — list each with email and requests used. These are the most suspicious:
- Many free accounts surrounding a paid account may mean the paid account used a stolen credit card to fund credit farming
- Flag: "Users X, Y, Z are 'active' — worth checking for credit card fraud"
Signup timeline — scan the created_at values for bursts:
- Multiple accounts created on the same day = strong fraud signal
- Flag any days with 3+ signups (e.g., "10 accounts signed up on 2025-11-03")
Fraud signal strength — summarize your read:
- 🔴 High: many free accounts, signup bursts, active accounts present
- 🟡 Medium: moderate cluster size, no clear bursts
- 🟢 Low: small cluster, organic signup spread, matches a household/team